finished basic implementation of settings

feature/settings-window
Michael Ochmann 3 years ago
parent 11d5983028
commit 6bf88714fa
  1. 10
      contextAPI.js
  2. 2
      package.json
  3. 4
      src/Ation.js
  4. 26
      src/MainMenu.js
  5. 6
      src/SettingsManager.js
  6. 10
      src/WindowManager.js
  7. 2
      src/ui/src/assets/css/_variables.scss
  8. 9
      src/ui/src/components/Ation.js
  9. 36
      src/ui/src/components/settings/Appearance.js
  10. 25
      src/ui/src/components/settings/General.js
  11. 8
      src/ui/src/components/settings/Navbar.js
  12. 4
      src/ui/src/components/settings/Settings.js
  13. 22
      src/ui/src/higherOrderComponents/withSettings.js
  14. 3
      src/ui/src/index.js
  15. 5
      src/ui/src/shared/SettingsContext.js

@ -4,6 +4,7 @@ const {contextBridge, ipcRenderer, webFrame} = require("electron");
let fileOpenListener = null; let fileOpenListener = null;
let closeFileListener = null; let closeFileListener = null;
let settingsChangeListener = null;
contextBridge.exposeInMainWorld("api", { contextBridge.exposeInMainWorld("api", {
openFileDialog : () => ipcRenderer.send("WindowManager::openFileDialog"), openFileDialog : () => ipcRenderer.send("WindowManager::openFileDialog"),
@ -30,5 +31,12 @@ contextBridge.exposeInMainWorld("api", {
contextBridge.exposeInMainWorld("appSettings", { contextBridge.exposeInMainWorld("appSettings", {
get : async (key, defaultValue = null) => await ipcRenderer.invoke("SettingsManager::get", key, defaultValue), get : async (key, defaultValue = null) => await ipcRenderer.invoke("SettingsManager::get", key, defaultValue),
set : async (key, value) => await ipcRenderer.invoke("SettingsManager::set", key, value) set : async (key, value) => await ipcRenderer.invoke("SettingsManager::set", key, value),
all : async () => await ipcRenderer.invoke("SettingsManager::all"),
onChange : callback => {
if (settingsChangeListener)
ipcRenderer.off("SettingsManager::change", settingsChangeListener);
settingsChangeListener = (_, settings) => callback(settings);
ipcRenderer.on("SettingsManager::change", settingsChangeListener);
}
}); });

@ -1,6 +1,6 @@
{ {
"name": "ation", "name": "ation",
"version": "0.2.0", "version": "0.3.0",
"description": "a simple presentation software", "description": "a simple presentation software",
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {

@ -1,5 +1,5 @@
"use strict"; "use strict";
const {app, protocol, dialog, ipcMain, Menu, session} = require("electron"); const {app, protocol, dialog, ipcMain, Menu} = require("electron");
const path = require("path"); const path = require("path");
const fs = require("fs/promises"); const fs = require("fs/promises");
const fsn = require("fs"); const fsn = require("fs");
@ -39,6 +39,8 @@ class Ation {
ipcMain.on("Ation::closeFile", () => this.closeFile()); ipcMain.on("Ation::closeFile", () => this.closeFile());
app.whenReady().then(async () => { app.whenReady().then(async () => {
this.settingsManager.change();
if (this.fileToOpen) if (this.fileToOpen)
this.openFile(this.fileToOpen); this.openFile(this.fileToOpen);
protocol.registerFileProtocol("slideimg", (request, callback) => { protocol.registerFileProtocol("slideimg", (request, callback) => {

@ -1,19 +1,35 @@
"use strict"; "use strict";
const {Menu} = require("electron"); const {Menu, app} = require("electron");
class MainMenu { class MainMenu {
constructor(app) { constructor(parentApp) {
this.app = app; this.app = parentApp;
this.menu = null; this.menu = null;
this.buildItems(); this.buildItems();
Menu.setApplicationMenu(this.menu); Menu.setApplicationMenu(this.menu);
} }
buildItems() { buildItems() {
const settingsItem = {
label : "Settings",
click : () => this.app.windowManager.windows.settings.show()
};
const template = [ const template = [
...(process.platform === "darwin" ? [{ ...(process.platform === "darwin" ? [{
role : "appMenu" label: app.name,
submenu: [
{ role: "about" },
{...settingsItem},
{ type: "separator" },
{ role: "services" },
{ type: "separator" },
{ role: "hide" },
{ role: "hideOthers" },
{ role: "unhide" },
{ type: "separator" },
{ role: "quit" }
]
}] : []), }] : []),
{ {
label : "File", label : "File",
@ -29,7 +45,7 @@ class MainMenu {
accelerator : "CommandOrControl+W", accelerator : "CommandOrControl+W",
click : () => this.app.closeFile(), click : () => this.app.closeFile(),
enabled : this.app.currentFile !== "" enabled : this.app.currentFile !== ""
} }, ...(process.platform !== "darwin" ? [{type : "separator"}, {...settingsItem}] : [])
] ]
}, },
{ {

@ -23,6 +23,7 @@ class SettingsManager {
ipcMain.handle("SettingsManager::resize", (_, height) => app.windowManager.windows.settings.setSize(800, height, true)); ipcMain.handle("SettingsManager::resize", (_, height) => app.windowManager.windows.settings.setSize(800, height, true));
ipcMain.handle("SettingsManager::get", (_, key, defaultValue = null) => this.get(key, defaultValue)); ipcMain.handle("SettingsManager::get", (_, key, defaultValue = null) => this.get(key, defaultValue));
ipcMain.handle("SettingsManager::set", (_, key, value) => this.set(key, value)); ipcMain.handle("SettingsManager::set", (_, key, value) => this.set(key, value));
ipcMain.handle("SettingsManager::all", () => this.data);
} }
get(key, defaultValue = null) { get(key, defaultValue = null) {
@ -36,8 +37,13 @@ class SettingsManager {
this.save(); this.save();
} }
change() {
this.app.windowManager.mainWindow.send("SettingsManager::change", this.data);
}
save() { save() {
fs.writeFile(SettingsManager.File, JSON.stringify(this.data, null, 4)); fs.writeFile(SettingsManager.File, JSON.stringify(this.data, null, 4));
this.change();
} }
static CheckFileSystem() { static CheckFileSystem() {

@ -31,7 +31,12 @@ class WindowManager {
resizable : false, resizable : false,
fullscreenable : false, fullscreenable : false,
parent : this.mainWindow, parent : this.mainWindow,
show : true show : false
}, this.mainWindow, false);
this.windows.settings.on("close", event => {
event.preventDefault();
this.windows.settings.hide();
}); });
if (isDevelopment()) { if (isDevelopment()) {
@ -54,7 +59,7 @@ class WindowManager {
} }
} }
static _CreateWindow(options = null, parent = null) { static _CreateWindow(options = null, parent = null, show = true) {
const windowOptions = { const windowOptions = {
width : 800, width : 800,
height : 600, height : 600,
@ -71,6 +76,7 @@ class WindowManager {
if (!parent) if (!parent)
delete windowOptions.parent; delete windowOptions.parent;
const window = new BrowserWindow(windowOptions); const window = new BrowserWindow(windowOptions);
if (show)
window.once("ready-to-show", () => window.show()); window.once("ready-to-show", () => window.show());
return window; return window;

@ -6,7 +6,7 @@ $colors : (
sidebarBackground : #333, sidebarBackground : #333,
mainBackground : #222, mainBackground : #222,
scrollbar : rgba(255,255,255,0.2), scrollbar : rgba(255,255,255,0.2),
hightlight : rgb(230,193,123), hightlight : #e6c17b,
active : rgba(255,255,255,0.1) active : rgba(255,255,255,0.1)
); );

@ -1,4 +1,4 @@
import React, {useEffect, useState} from "react"; import React, {useEffect, useState, useContext} from "react";
import SlidesList from "./SlidesList"; import SlidesList from "./SlidesList";
import Mode from "../models/Mode"; import Mode from "../models/Mode";
@ -10,9 +10,10 @@ import Toolbar from "./Toolbar";
import Tips from "./Tips"; import Tips from "./Tips";
import SlideContext from "../shared/SlideContext"; import SlideContext from "../shared/SlideContext";
import SettingsContext from "../shared/SettingsContext";
const Ation = () => { const Ation = () => {
const [font, setFont] = useState(""); const {font, highlightColor} = useContext(SettingsContext);
const [mode, setMode] = useState(Mode.NORMAL); const [mode, setMode] = useState(Mode.NORMAL);
const [deck, setDeck] = useState([]); const [deck, setDeck] = useState([]);
const [slide, setSlide] = useState(0); const [slide, setSlide] = useState(0);
@ -37,7 +38,7 @@ const Ation = () => {
setDeck([]); setDeck([]);
}); });
(async set => set(await window.api.appVersion()))(setVersion); (async set => set(await window.api.appVersion()))(setVersion);
(async setFont => setFont(await window.appSettings.get("font", "Iosevka")))(setFont); //(async setFont => setFont(await window.appSettings.get("font", "Iosevka")))(setFont);
}, [basePath, slide]); }, [basePath, slide]);
@ -54,7 +55,7 @@ const Ation = () => {
<section className={`window${mode === Mode.PRESENT ? " fullscreen" : ""}`}> <section className={`window${mode === Mode.PRESENT ? " fullscreen" : ""}`}>
<Toolbar openFile={openFile} setShowTips={setShowTips} version={version} /> <Toolbar openFile={openFile} setShowTips={setShowTips} version={version} />
<SlidesList deck={deck} /> <SlidesList deck={deck} />
<main className="main"> <main className="main" style={{"--color-hightlight" : highlightColor}}>
<Slide data={deck[slide] || null} style={{fontFamily : font}}/> <Slide data={deck[slide] || null} style={{fontFamily : font}}/>
</main> </main>
<Tips show={showTips} /> <Tips show={showTips} />

@ -0,0 +1,36 @@
import React, {useState, useEffect} from "react";
const Appearance = ({fonts}) => {
const [font, setFont] = useState("");
const [highlightColor, setHighlightColor] = useState("");
const changeFont = event => {
const value = event.target.value;
setFont(value);
window.appSettings.set("font", value);
};
const changeColor = event => {
const value = event.target.value;
setHighlightColor(value);
window.appSettings.set("highlightColor", value);
};
useEffect(() => {
(async setFont => setFont(await window.appSettings.get("font", "Iosevka")))(setFont);
(async setHighlightColor => setHighlightColor(await window.appSettings.get("highlightColor", "#e6c17b")))(setHighlightColor);
}, []);
return (
<section className="grid">
<label>Font in slides:</label>
<select value={font} onChange={changeFont}>
{fonts.map(font => font.trim().replace(/(^"|"$)/g, "")).map(font => <option key={font} value={font}>{font}</option>)}
</select>
<label>Global highlight color:</label>
<input type="color" value={highlightColor} onChange={changeColor} />
</section>
);
};
export default Appearance;

@ -1,27 +1,10 @@
import React, {useState, useEffect} from "react"; import React from "react";
const General = ({fonts}) => { const General = () => {
const [font, setFont] = useState("");
const changeFont = event => {
const value = event.target.value;
setFont(value);
window.appSettings.set("font", value);
};
useEffect(() => {
(async setFont => setFont(await window.appSettings.get("font", "Iosevka")))(setFont);
}, []);
return ( return (
<section className="grid"> <>
<label>Font in slides:</label> </>
<select value={font} onChange={changeFont}>
{fonts.map(font => font.trim().replace(/(^"|"$)/g, "")).map(font => <option key={font} value={font}>{font}</option>)}
</select>
<label>Global highlight color:</label>
<input type="color" />
</section>
); );
}; };

@ -1,9 +1,9 @@
import React, {forwardRef} from "react"; import React, {forwardRef} from "react";
import {NavLink} from "react-router-dom"; import {NavLink} from "react-router-dom";
import {Gear} from "react-bootstrap-icons"; import {Gear, Palette} from "react-bootstrap-icons";
const Navbar = forwardRef((props, ref) => { const Navbar = forwardRef((_, ref) => {
return ( return (
<nav className="navbar" ref={ref}> <nav className="navbar" ref={ref}>
<NavLink to="/settings" end> <NavLink to="/settings" end>
@ -11,8 +11,8 @@ const Navbar = forwardRef((props, ref) => {
<label>General</label> <label>General</label>
</NavLink> </NavLink>
<NavLink to="/settings/style" end> <NavLink to="/settings/style" end>
<Gear /> <Palette />
<label>General</label> <label>Appearance</label>
</NavLink> </NavLink>
</nav> </nav>
); );

@ -3,6 +3,7 @@ import {Routes, Route, useLocation} from "react-router-dom";
import Navbar from "./Navbar"; import Navbar from "./Navbar";
import General from "./General"; import General from "./General";
import Appearance from "./Appearance";
const Settings = () => { const Settings = () => {
const navbar = useRef(); const navbar = useRef();
@ -26,7 +27,8 @@ const Settings = () => {
<Navbar ref={navbar} /> <Navbar ref={navbar} />
<main ref={content}> <main ref={content}>
<Routes> <Routes>
<Route index element={<General fonts={fonts} />} /> <Route index element={<General />} />
<Route path="/style" element={<Appearance fonts={fonts} />} />
</Routes> </Routes>
</main> </main>
<section className="titlebar"></section> <section className="titlebar"></section>

@ -0,0 +1,22 @@
import React, {useState, useEffect} from "react";
import SettingsContext from "../shared/SettingsContext";
const withSettings = Component => () => {
const [settings, setSettings] = useState({
font : "Iosevka"
});
useEffect(() => {
(async setSettings => setSettings(await window.appSettings.all()))(setSettings);
window.appSettings.onChange(settings => setSettings(settings));
}, []);
return (
<SettingsContext.Provider value={{...settings}}>
<Component />
</SettingsContext.Provider>
);
};
export default withSettings;

@ -6,10 +6,11 @@ import {BrowserRouter as Router, Routes, Route} from "react-router-dom";
import Ation from "./components/Ation"; import Ation from "./components/Ation";
import Settings from "./components/settings/Settings"; import Settings from "./components/settings/Settings";
import withDrop from "./higherOrderComponents/withDrop"; import withDrop from "./higherOrderComponents/withDrop";
import withSettings from "./higherOrderComponents/withSettings";
import "./assets/css/ation.scss"; import "./assets/css/ation.scss";
const App = withDrop(Ation); const App = withSettings(withDrop(Ation));
const root = ReactDOM.createRoot(document.getElementById("root")); const root = ReactDOM.createRoot(document.getElementById("root"));
root.render( root.render(

@ -0,0 +1,5 @@
import {createContext} from "react";
const SettingsContext = createContext({});
export default SettingsContext;
Loading…
Cancel
Save