From 6073710de59bdcf0e349d4fc5a0092f155e25195 Mon Sep 17 00:00:00 2001 From: Michael Ochmann Date: Thu, 1 Feb 2024 23:33:25 +0100 Subject: [PATCH] added basic editor --- contextAPI.js | 1 + src/Ation.js | 9 +++- src/ui/package-lock.json | 63 ++++++++++++++++++++++++ src/ui/package.json | 1 + src/ui/src/assets/css/_window.scss | 9 ++++ src/ui/src/components/Ation.js | 17 +++++-- src/ui/src/components/Editor.js | 53 ++++++++++++++++++++ src/ui/src/components/KeyboardControl.js | 7 ++- src/ui/src/components/Tips.js | 2 + src/ui/src/components/Toolbar.js | 5 +- src/ui/src/models/Mode.js | 3 +- 11 files changed, 160 insertions(+), 10 deletions(-) create mode 100644 src/ui/src/components/Editor.js diff --git a/contextAPI.js b/contextAPI.js index c1c21f0..920ed75 100644 --- a/contextAPI.js +++ b/contextAPI.js @@ -22,6 +22,7 @@ contextBridge.exposeInMainWorld("api", { }, openFile : filePath => ipcRenderer.send("WindowManager::openFile", filePath), closeFile : () => ipcRenderer.send("Ation::closeFile"), + saveFile : async newContent => await ipcRenderer.invoke("Ation::saveFile", newContent), clearCache : () => webFrame.clearCache(), appVersion : async () => await ipcRenderer.invoke("Ation::appVersion"), diff --git a/src/Ation.js b/src/Ation.js index 738629b..5202b2f 100644 --- a/src/Ation.js +++ b/src/Ation.js @@ -37,7 +37,8 @@ class Ation { this.mainMenu = new MainMenu(this); ipcMain.handle("Ation::appVersion", () => AppInfo.version); - ipcMain.on("Ation::closeFile", () => this.closeFile()); + ipcMain.handle("Ation::saveFile", async (_, newContent) => this.saveFile(newContent)); + ipcMain.on("Ation::closeFile", () => this.closeFile()); app.on("open-file", (_, path) => { this.fileToOpen = path; @@ -67,6 +68,10 @@ class Ation { }); } + async saveFile(newContent) { + return fs.writeFile(this.currentFile, newContent, { encoding : "utf-8" }) + } + addRecentFile(filePath) { if (this.recentFiles.includes(filePath)) { const position = this.recentFiles.indexOf(filePath); @@ -119,7 +124,7 @@ class Ation { else this.openFile(this.currentFile, true); }); - this.windowManager.mainWindow.send("Ation::openFile", [basePath, data]); + this.windowManager.mainWindow.send("Ation::openFile", [basePath, data, fileContents]); this.addRecentFile(filePath); Menu.getApplicationMenu().getMenuItemById("close-file").enabled = this.currentFile !== ""; } catch (error) { diff --git a/src/ui/package-lock.json b/src/ui/package-lock.json index bec3546..c0f5a70 100644 --- a/src/ui/package-lock.json +++ b/src/ui/package-lock.json @@ -8,6 +8,7 @@ "name": "ui", "version": "0.1.0", "dependencies": { + "@monaco-editor/react": "^4.6.0", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", @@ -3025,6 +3026,30 @@ "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==" }, + "node_modules/@monaco-editor/loader": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.4.0.tgz", + "integrity": "sha512-00ioBig0x642hytVspPl7DbQyaSWRaolYie/UFNjoTdvoKPzo6xrXLhTk9ixgIKcLH5b5vDOjVNiGyY+uDCUlg==", + "dependencies": { + "state-local": "^1.0.6" + }, + "peerDependencies": { + "monaco-editor": ">= 0.21.0 < 1" + } + }, + "node_modules/@monaco-editor/react": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.6.0.tgz", + "integrity": "sha512-RFkU9/i7cN2bsq/iTkurMWOEErmYcY6JiQI3Jn+WeR/FGISH8JbHERjpS9oRuSOPvDMJI0Z8nJeKkbOs9sBYQw==", + "dependencies": { + "@monaco-editor/loader": "^1.4.0" + }, + "peerDependencies": { + "monaco-editor": ">= 0.25.0 < 1", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", @@ -12030,6 +12055,12 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/monaco-editor": { + "version": "0.45.0", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.45.0.tgz", + "integrity": "sha512-mjv1G1ZzfEE3k9HZN0dQ2olMdwIfaeAAjFiwNprLfYNRSz7ctv9XuCT7gPtBGrMUeV1/iZzYKj17Khu1hxoHOA==", + "peer": true + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -15543,6 +15574,11 @@ "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==" }, + "node_modules/state-local": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz", + "integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==" + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -19243,6 +19279,22 @@ "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==" }, + "@monaco-editor/loader": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.4.0.tgz", + "integrity": "sha512-00ioBig0x642hytVspPl7DbQyaSWRaolYie/UFNjoTdvoKPzo6xrXLhTk9ixgIKcLH5b5vDOjVNiGyY+uDCUlg==", + "requires": { + "state-local": "^1.0.6" + } + }, + "@monaco-editor/react": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.6.0.tgz", + "integrity": "sha512-RFkU9/i7cN2bsq/iTkurMWOEErmYcY6JiQI3Jn+WeR/FGISH8JbHERjpS9oRuSOPvDMJI0Z8nJeKkbOs9sBYQw==", + "requires": { + "@monaco-editor/loader": "^1.4.0" + } + }, "@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", @@ -25797,6 +25849,12 @@ "minimist": "^1.2.6" } }, + "monaco-editor": { + "version": "0.45.0", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.45.0.tgz", + "integrity": "sha512-mjv1G1ZzfEE3k9HZN0dQ2olMdwIfaeAAjFiwNprLfYNRSz7ctv9XuCT7gPtBGrMUeV1/iZzYKj17Khu1hxoHOA==", + "peer": true + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -28180,6 +28238,11 @@ "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==" }, + "state-local": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz", + "integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==" + }, "statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", diff --git a/src/ui/package.json b/src/ui/package.json index 99d532d..35a7b56 100644 --- a/src/ui/package.json +++ b/src/ui/package.json @@ -4,6 +4,7 @@ "private": true, "homepage": "./", "dependencies": { + "@monaco-editor/react": "^4.6.0", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", diff --git a/src/ui/src/assets/css/_window.scss b/src/ui/src/assets/css/_window.scss index ff53727..a5cbaa8 100644 --- a/src/ui/src/assets/css/_window.scss +++ b/src/ui/src/assets/css/_window.scss @@ -28,6 +28,15 @@ font-size: calc(3vw * 0.7); } } + + &.edit { + display: grid; + grid-template-columns: 1fr 2fr; + + & > .slide:first-child { + font-size: calc(3vw / 3 * 0.7); + } + } } &.fullscreen { diff --git a/src/ui/src/components/Ation.js b/src/ui/src/components/Ation.js index 1fa1cee..aa91ad8 100644 --- a/src/ui/src/components/Ation.js +++ b/src/ui/src/components/Ation.js @@ -8,6 +8,7 @@ import Blackout from "./Blackout"; import NoFile from "./NoFile"; import Toolbar from "./Toolbar"; import Tips from "./Tips"; +import Editor from "./Editor"; import SlideContext from "../shared/SlideContext"; import SettingsContext from "../shared/SettingsContext"; @@ -21,15 +22,17 @@ const Ation = () => { const [basePath, setBasePath] = useState(""); const [showTips, setShowTips] = useState(false); const [version, setVersion] = useState("0.0.0"); + const [source, setSource] = useState(""); useEffect(() => { window.api.onFileOpen(presentation => { window.api.clearCache(); - const [newBasePath, slideDeck] = presentation; + const [newBasePath, slideDeck, fileContents] = presentation; if (!slideDeck) return; if (slide >= slideDeck.length) setSlide(0); + setSource(fileContents); setMeta(slideDeck.metaData); setBasePath(newBasePath); setDeck(slideDeck.slides); @@ -48,6 +51,13 @@ const Ation = () => { window.api.openFileDialog(); } + const toggleEdit = () => { + if (mode === Mode.NORMAL) + setMode(Mode.EDIT); + else if (mode === Mode.EDIT) + setMode(Mode.NORMAL); + } + return ( <> {deck.length < 1 ? @@ -57,17 +67,18 @@ const Ation = () => {
-
+
- + )} diff --git a/src/ui/src/components/Editor.js b/src/ui/src/components/Editor.js new file mode 100644 index 0000000..b6bded4 --- /dev/null +++ b/src/ui/src/components/Editor.js @@ -0,0 +1,53 @@ +import React, {useEffect, useContext, useRef} from "react"; +import Monaco from "@monaco-editor/react"; + +import SlideContext from "../shared/SlideContext"; +import Mode from "../models/Mode"; + +const Editor = ({show, source}) => { + const editor = useRef(); + const {mode} = useContext(SlideContext); + + useEffect(() => { + const listener = event => { + if (!event.metaKey && !event.ctrlKey) + return; + if (mode !== Mode.EDIT || event.key !== "s") + return; + + window.api.saveFile(editor.current?.getValue()); + }; + + document.addEventListener("keydown", listener); + + return () => { + document.removeEventListener("keydown", listener); + }; + }, [mode]); + + const editorMount = (instance, _) => { + editor.current = instance; + } + + if (!show) + return <>; + + return ( + <> + + + ); +}; + +export default Editor; \ No newline at end of file diff --git a/src/ui/src/components/KeyboardControl.js b/src/ui/src/components/KeyboardControl.js index 39a68b1..314a789 100644 --- a/src/ui/src/components/KeyboardControl.js +++ b/src/ui/src/components/KeyboardControl.js @@ -3,7 +3,7 @@ import {useContext, useEffect, version} from "react"; import SlideContext from "../shared/SlideContext"; import Mode from "../models/Mode"; -const KeyboardControl = ({openFile, mode, setMode, deck, setShowTips}) => { +const KeyboardControl = ({openFile, mode, setMode, deck, setShowTips, toggleEdit}) => { const {slide, setSlide} = useContext(SlideContext); useEffect(() => { @@ -51,7 +51,7 @@ const KeyboardControl = ({openFile, mode, setMode, deck, setShowTips}) => { setSlide(slide + 1); break; case "b": // PRESENTER - if (mode === Mode.NORMAL) + if (mode === Mode.NORMAL || mode === Mode.EDIT) return; setMode(mode === Mode.BLACKOUT ? Mode.PRESENT : Mode.BLACKOUT); break; @@ -65,6 +65,9 @@ const KeyboardControl = ({openFile, mode, setMode, deck, setShowTips}) => { event.preventDefault(); setShowTips(true); break; + case "e": + toggleEdit(); + break; default: return; } diff --git a/src/ui/src/components/Tips.js b/src/ui/src/components/Tips.js index 8760045..de9c84e 100644 --- a/src/ui/src/components/Tips.js +++ b/src/ui/src/components/Tips.js @@ -5,6 +5,8 @@ const Cheatsheet = Object.freeze([ ["Stop presentation", "ESC"], ["Open file", "⌘+O"], ["Close file", "⌘+W"], + ["Save file", "⌘+S"], + ["Toggle editor", "e"], ["Next slide", "→, Page up"], ["Last slide", "←, Page down"], ["Black screen out", "B"], diff --git a/src/ui/src/components/Toolbar.js b/src/ui/src/components/Toolbar.js index ce9e2cc..2bf4a10 100644 --- a/src/ui/src/components/Toolbar.js +++ b/src/ui/src/components/Toolbar.js @@ -1,13 +1,13 @@ import React, {useContext} from "react"; -import {Folder2Open, Cast, InfoCircle, XSquare} from "react-bootstrap-icons"; +import {Folder2Open, Cast, InfoCircle, XSquare, LayoutSplit} 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, version}) => { +const Toolbar = ({openFile, setShowTips, version, toggleEdit}) => { const {setMode, setSlide, slideCount} = useContext(SlideContext); const present = () => { @@ -23,6 +23,7 @@ const Toolbar = ({openFile, setShowTips, version}) => { + v{version} ); diff --git a/src/ui/src/models/Mode.js b/src/ui/src/models/Mode.js index 034f6e2..67b7593 100644 --- a/src/ui/src/models/Mode.js +++ b/src/ui/src/models/Mode.js @@ -1,7 +1,8 @@ const Mode = Object.freeze({ NORMAL : 1, PRESENT : 2, - BLACKOUT : 3 + BLACKOUT : 3, + EDIT : 4 }); export default Mode; \ No newline at end of file