diff --git a/contextAPI.js b/contextAPI.js index 568d22c..f371b6e 100644 --- a/contextAPI.js +++ b/contextAPI.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) }); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 0ce05b1..ea128c2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "ation", - "version": "0.1.0", + "version": "0.2.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index b5538a1..34f337a 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/Ation.js b/src/Ation.js index 43852cb..a77ab50 100644 --- a/src/Ation.js +++ b/src/Ation.js @@ -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,28 +36,32 @@ class Ation { }); } - async openFile() { + async openFile(filePath = null) { if (!this.windowManager.mainWindow) return null; - const result = await dialog.showOpenDialog(this.mainWindow, { - title : "open file", - filters : [ - { - name : "Markdown files", - extensions : [".md"] - } - ] - }); + if (!filePath) { + const result = await dialog.showOpenDialog(this.windowManager.mainWindow, { + title : "open file", + filters : [ + { + name : "Markdown files", + extensions : [".md"] + } + ] + }); - if (result.canceled || result.filePaths.length < 1) - return null; + if (result.canceled || result.filePaths.length < 1) + 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; diff --git a/src/MainMenu.js b/src/MainMenu.js index ba37ac9..86ddf79 100644 --- a/src/MainMenu.js +++ b/src/MainMenu.js @@ -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" } diff --git a/src/WindowManager.js b/src/WindowManager.js index 11a6967..5a0ca7b 100644 --- a/src/WindowManager.js +++ b/src/WindowManager.js @@ -1,9 +1,8 @@ "use strict"; -const {app, BrowserWindow, ipcMain, dialog, Menu} = require("electron"); -const path = require("path"); -const fs = require("fs/promises"); -const util = require("util"); +const {app, BrowserWindow, ipcMain, globalShortcut} = require("electron"); +const path = require("path"); +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 diff --git a/src/ui/src/assets/css/_toolbar.scss b/src/ui/src/assets/css/_toolbar.scss index 52f02dd..36d4a37 100644 --- a/src/ui/src/assets/css/_toolbar.scss +++ b/src/ui/src/assets/css/_toolbar.scss @@ -23,4 +23,10 @@ margin: 0; } } + + > svg { + max-height: 90%; + width: auto; + margin: 0 2rem; + } } \ No newline at end of file diff --git a/src/ui/src/assets/images/logo_ation.svg b/src/ui/src/assets/images/logo_ation.svg new file mode 100644 index 0000000..4681f8f --- /dev/null +++ b/src/ui/src/assets/images/logo_ation.svg @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/src/ui/src/components/Ation.js b/src/ui/src/components/Ation.js index bb3562f..b2ae917 100644 --- a/src/ui/src/components/Ation.js +++ b/src/ui/src/components/Ation.js @@ -18,31 +18,39 @@ const Ation = () => { const [basePath, setBasePath] = useState(""); const [showTips, setShowTips] = useState(false); - const openFile = async () => { - const [basePath, slideDeck] = await window.api.openFile(); - - if (!slideDeck) - return; - setBasePath(basePath); - setDeck(slideDeck); + useEffect(() => { + window.api.onFileOpen(presentation => { + const [basePath, slideDeck] = presentation; + if (!slideDeck) + return; + setBasePath(basePath); + setDeck(slideDeck); + }); + }, []); + + const openFile = () => { + window.api.openFileDialog(); } - if (deck.length < 1) - return ; - return ( - -
- - -
- -
- -
- - -
+ <> + {deck.length < 1 ? + + : ( + +
+ + +
+ +
+ +
+ + +
+ )} + ); }; diff --git a/src/ui/src/components/KeyboardControl.js b/src/ui/src/components/KeyboardControl.js index 4f1212f..4dfb240 100644 --- a/src/ui/src/components/KeyboardControl.js +++ b/src/ui/src/components/KeyboardControl.js @@ -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; diff --git a/src/ui/src/components/Toolbar.js b/src/ui/src/components/Toolbar.js index 0cfc5c5..1ee8616 100644 --- a/src/ui/src/components/Toolbar.js +++ b/src/ui/src/components/Toolbar.js @@ -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 ( ); diff --git a/src/ui/src/higherOrderComponents/withDrop.js b/src/ui/src/higherOrderComponents/withDrop.js new file mode 100644 index 0000000..ed5734c --- /dev/null +++ b/src/ui/src/higherOrderComponents/withDrop.js @@ -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 ( +
+ +
+ ); +}; + + +export default withDrop; \ No newline at end of file diff --git a/src/ui/src/index.js b/src/ui/src/index.js index 2db8a76..a545a68 100644 --- a/src/ui/src/index.js +++ b/src/ui/src/index.js @@ -1,13 +1,16 @@ import React from "react"; import ReactDOM from "react-dom/client"; -import Ation from "./components/Ation"; +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( - + ); \ No newline at end of file