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