added basic editor

master
Michael Ochmann 1 year ago
parent 3c3b81c585
commit 6073710de5
  1. 1
      contextAPI.js
  2. 9
      src/Ation.js
  3. 63
      src/ui/package-lock.json
  4. 1
      src/ui/package.json
  5. 9
      src/ui/src/assets/css/_window.scss
  6. 17
      src/ui/src/components/Ation.js
  7. 53
      src/ui/src/components/Editor.js
  8. 7
      src/ui/src/components/KeyboardControl.js
  9. 2
      src/ui/src/components/Tips.js
  10. 5
      src/ui/src/components/Toolbar.js
  11. 3
      src/ui/src/models/Mode.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"),

@ -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) {

@ -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",

@ -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",

@ -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 {

@ -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 = () => {
<section className={`window${mode === Mode.PRESENT ? " fullscreen" : ""}`}>
<Toolbar openFile={openFile} setShowTips={setShowTips} version={version} />
<SlidesList deck={deck} meta={meta} font={font} />
<main className="main" style={{
<main className={`main ${mode === Mode.EDIT ? "edit" : ""}`} style={{
"--color-hightlight" : meta?.color_highlight || highlightColor,
"--color-slideBackground" : meta?.color_background || backgroundColor,
"--color-slideForeground" : meta?.color_text || color
}}>
<Slide data={deck[slide] || null} style={{fontFamily : meta?.font || font}}/>
<Editor show={mode === Mode.EDIT} source={source} toggleEdit={toggleEdit} />
</main>
<Tips show={showTips} />
</section>
<Blackout show={mode === Mode.BLACKOUT} />
<KeyboardControl mode={mode} setMode={setMode} deck={deck} openFile={openFile} setShowTips={setShowTips} />
<KeyboardControl mode={mode} setMode={setMode} deck={deck} openFile={openFile} setShowTips={setShowTips} toggleEdit={toggleEdit} />
</SlideContext.Provider>
)}
</>

@ -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 (
<>
<Monaco
defaultLanguage="markdown"
height="100%"
options={{
minimap : {
enabled : false
}
}}
onMount={editorMount}
keepCurrentModel={true}
defaultValue={source}
theme="vs-dark" />
</>
);
};
export default Editor;

@ -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;
}

@ -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"],

@ -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}) => {
<Logo />
<button onClick={() => window.api.closeFile()} title="Close file [⌘+W]" disabled={slideCount < 1}><XSquare /></button>
<button onClick={() => setShowTips(true)} title="Show tips [TAB]"><InfoCircle /></button>
<button onClick={toggleEdit} title="Toggle editor [e]"><LayoutSplit /></button>
<small>v{version}</small>
</nav>
);

@ -1,7 +1,8 @@
const Mode = Object.freeze({
NORMAL : 1,
PRESENT : 2,
BLACKOUT : 3
BLACKOUT : 3,
EDIT : 4
});
export default Mode;
Loading…
Cancel
Save