Merge pull request 'Make it possible to open .md files with ation from system' (#2) from feature/open-with into development

Reviewed-on: MassiveDynamic/ation#2
feature/settings-window
Michael Ochmann 3 years ago
commit 61cc5524b7
  1. 4
      contextAPI.js
  2. 2
      package-lock.json
  3. 13
      package.json
  4. 24
      src/Ation.js
  5. 10
      src/MainMenu.js
  6. 15
      src/WindowManager.js
  7. 6
      src/ui/src/assets/css/_toolbar.scss
  8. 16
      src/ui/src/assets/images/logo_ation.svg
  9. 20
      src/ui/src/components/Ation.js
  10. 5
      src/ui/src/components/KeyboardControl.js
  11. 5
      src/ui/src/components/Toolbar.js
  12. 42
      src/ui/src/higherOrderComponents/withDrop.js
  13. 5
      src/ui/src/index.js

@ -3,5 +3,7 @@
const {contextBridge, ipcRenderer} = require("electron");
contextBridge.exposeInMainWorld("api", {
openFile : async () => ipcRenderer.invoke("WindowManager::openFile")
openFileDialog : () => ipcRenderer.send("WindowManager::openFileDialog"),
onFileOpen : callback => ipcRenderer.on("Ation::openFile", (_, presentation) => callback(presentation)),
openFile : filePath => ipcRenderer.send("WindowManager::openFile", filePath)
});

2
package-lock.json generated

@ -1,6 +1,6 @@
{
"name": "ation",
"version": "0.1.0",
"version": "0.2.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

@ -1,6 +1,6 @@
{
"name": "ation",
"version": "0.1.0",
"version": "0.2.0",
"description": "a simple presentation software",
"main": "main.js",
"scripts": {
@ -35,7 +35,16 @@
"dmg"
],
"icon": "assets/app.icns",
"darkModeSupport": true
"darkModeSupport": true,
"fileAssociations": [
{
"ext": [
"md"
],
"description": "Markdown files",
"role": "Viewer"
}
]
},
"dmg": {
"background": "assets/dmg_background.png",

@ -10,14 +10,22 @@ const {parser} = require("./Parser");
class Ation {
windowManager;
mainMenu;
fileToOpen;
constructor() {
if (Ation.Instances > 0)
throw new Error("Only one Instance of Ation possible");
this.fileToOpen = null;
this.windowManager = new WindowManager(this);
this.mainMenu = new MainMenu(this);
app.on("open-file", (_, path) => {
this.fileToOpen = path;
});
app.whenReady().then(async () => {
if (this.fileToOpen)
dialog.showMessageBox(this.windowManager.mainWindow, {message : this.fileToOpen});
protocol.registerFileProtocol("slideimg", (request, callback) => {
const uri = request.url.replace(/^slideimg:\/\//, "");
const extension = path.extname(uri).replace(/^\./, "");
@ -28,11 +36,12 @@ class Ation {
});
}
async openFile() {
async openFile(filePath = null) {
if (!this.windowManager.mainWindow)
return null;
const result = await dialog.showOpenDialog(this.mainWindow, {
if (!filePath) {
const result = await dialog.showOpenDialog(this.windowManager.mainWindow, {
title : "open file",
filters : [
{
@ -43,13 +52,16 @@ class Ation {
});
if (result.canceled || result.filePaths.length < 1)
return null;
return;
filePath = result.filePaths[0];
}
const fileContents = await fs.readFile(result.filePaths[0], {encoding : "utf-8"});
const basePath = path.dirname(result.filePaths[0]);
const fileContents = await fs.readFile(filePath, {encoding : "utf-8"});
const basePath = path.dirname(filePath);
const data = parser(fileContents);
return [basePath, data];
this.windowManager.mainWindow.send("Ation::openFile", [basePath, data]);
}
}
Ation.Instances = 0;

@ -15,6 +15,16 @@ class MainMenu {
...(process.platform === "darwin" ? [{
role : "appMenu"
}] : []),
{
label : "File",
submenu : [
{
label : "Open",
accelerator : "CommandOrControl+O",
click : () => this.app.openFile()
}
]
},
{
role : "windowMenu"
}

@ -1,8 +1,7 @@
"use strict";
const {app, BrowserWindow, ipcMain, dialog, Menu} = require("electron");
const {app, BrowserWindow, ipcMain, globalShortcut} = require("electron");
const path = require("path");
const fs = require("fs/promises");
const util = require("util");
const {isDevelopment} = require("./Util");
@ -18,7 +17,8 @@ class WindowManager {
app.whenReady().then(() => this.init());
ipcMain.handle("WindowManager::openFile", async () => this.app.openFile());
ipcMain.on("WindowManager::openFileDialog", () => this.app.openFile());
ipcMain.on("WindowManager::openFile", (_, path) => this.app.openFile(path));
}
init() {
@ -28,6 +28,15 @@ class WindowManager {
titleBarStyle : "hiddenInset"
});
if (isDevelopment) {
globalShortcut.register("CommandOrControl+I", () => {
this.mainWindow.toggleDevTools();
});
globalShortcut.register("CommandOrControl+R", () => {
this.mainWindow.reload();
});
}
if (isDevelopment())
this.mainWindow.loadURL("http://localhost:3000");
else

@ -23,4 +23,10 @@
margin: 0;
}
}
> svg {
max-height: 90%;
width: auto;
margin: 0 2rem;
}
}

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.5.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 128 128" style="enable-background:new 0 0 128 128;" xml:space="preserve">
<style type="text/css">
.st0{fill:url(#SVGID_1_);}
</style>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="63.7328" y1="126.4761" x2="63.7328" y2="1.0838">
<stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.5"/>
<stop offset="0.3394" style="stop-color:#FFFFFF"/>
</linearGradient>
<path class="st0" d="M112.3,21H78.7l-4-20H52.8l-4,20H15.2c-4.5,0-8.1,3.6-8.1,8.1v56.6c0,4.5,3.6,8.1,8.1,8.1h15H34l-6.6,32.6h19.4
l5.1-28h23.5l5.1,28h19.4l-6.6-32.6h3.8h15c4.5,0,8.1-3.6,8.1-8.1V29.1C120.4,24.7,116.7,21,112.3,21z M54.9,82.1L62,42.9
c0.2-1.6,0.5-3.2,0.8-4.9c0.3-1.6,0.6-3.3,0.9-4.9c0.3,1.6,0.7,3.2,0.9,4.9c0.3,1.6,0.5,3.3,0.8,4.9l7.2,39.2H54.9z M15.2,85.8V29.1
h31.9L35.6,85.8H15.2z M112.3,85.8H91.8L80.3,29.1h31.9V85.8z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -18,19 +18,25 @@ const Ation = () => {
const [basePath, setBasePath] = useState("");
const [showTips, setShowTips] = useState(false);
const openFile = async () => {
const [basePath, slideDeck] = await window.api.openFile();
useEffect(() => {
window.api.onFileOpen(presentation => {
const [basePath, slideDeck] = presentation;
if (!slideDeck)
return;
setBasePath(basePath);
setDeck(slideDeck);
}
});
}, []);
if (deck.length < 1)
return <NoFile openFile={openFile} />;
const openFile = () => {
window.api.openFileDialog();
}
return (
<>
{deck.length < 1 ?
<NoFile openFile={openFile} />
: (
<SlideContext.Provider value={{slide, setSlide, mode, setMode, basePath}}>
<section className={`window${mode === Mode.PRESENT ? " fullscreen" : ""}`}>
<Toolbar openFile={openFile} setShowTips={setShowTips} />
@ -43,6 +49,8 @@ const Ation = () => {
<Blackout show={mode === Mode.BLACKOUT} />
<KeyboardControl mode={mode} setMode={setMode} deck={deck} openFile={openFile} setShowTips={setShowTips} />
</SlideContext.Provider>
)}
</>
);
};

@ -19,11 +19,6 @@ const KeyboardControl = ({openFile, mode, setMode, deck, setShowTips}) => {
const keyHandler = event => {
switch(event.key) {
case "o":
if (!event.metaKey && !event.ctrlKey)
return;
openFile();
break;
case "Escape":
setShowTips(false);
break;

@ -5,6 +5,8 @@ import {Folder2Open, Cast, InfoCircle} from "react-bootstrap-icons";
import SlideContext from "../shared/SlideContext";
import Mode from "../models/Mode";
import {ReactComponent as Logo} from "../assets/images/logo_ation.svg";
const Toolbar = ({openFile, setShowTips}) => {
const {setMode, setSlide} = useContext(SlideContext);
@ -16,8 +18,9 @@ const Toolbar = ({openFile, setShowTips}) => {
return (
<nav className="toolbar">
<button onClick={openFile} title="Open file [Ctrl+O]"><Folder2Open /></button>
<button onClick={openFile} title="Open file [+O]"><Folder2Open /></button>
<button onClick={present} title="Start presentation [F5]"><Cast /></button>
<Logo />
<button onClick={() => setShowTips(true)} title="Show tips [TAB]"><InfoCircle /></button>
</nav>
);

@ -0,0 +1,42 @@
import React, {useState} from "react";
const extension = filename => {
const parts = filename.split('.');
return parts[parts.length - 1];
};
const withDrop = Component => () => {
const [drag, setDrag] = useState(false);
const handleDragOver = event => {
event.preventDefault();
event.stopPropagation();
setDrag(true);
};
const handleDragLeave = event => {
event.preventDefault();
event.stopPropagation();
setDrag(false);
}
const handleDrop = event => {
event.preventDefault();
event.stopPropagation();
const file = event.dataTransfer?.items[0].getAsFile();
if (extension(file.name.toLowerCase()) === "md") {
window.api.openFile(file.path);
}
setDrag(false);
};
return (
<section onDragLeave={handleDragLeave} onDragOver={handleDragOver} onDrop={handleDrop} style={{opacity : drag ? 0.2 : 1}}>
<Component />
</section>
);
};
export default withDrop;

@ -2,12 +2,15 @@ import React from "react";
import ReactDOM from "react-dom/client";
import Ation from "./components/Ation";
import withDrop from "./higherOrderComponents/withDrop";
import "./assets/css/ation.scss";
const App = withDrop(Ation);
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<Ation />
<App />
</React.StrictMode>
);
Loading…
Cancel
Save