more polishing for `v0.1.0`

pull/2/head v0.1.0
Michael Ochmann 3 years ago
parent ed35abf4d2
commit 48eec34628
  1. 2
      package.json
  2. 10
      src/Ation.js
  3. 3
      src/WindowManager.js
  4. 1
      src/ui/src/assets/css/_blackout.scss
  5. 20
      src/ui/src/assets/css/_forms.scss
  6. 74
      src/ui/src/assets/css/_slide.scss
  7. 4
      src/ui/src/assets/css/_slidesList.scss
  8. 3
      src/ui/src/assets/css/_variables.scss
  9. 14
      src/ui/src/components/Ation.js
  10. 15
      src/ui/src/components/KeyboardControl.js
  11. 3
      src/ui/src/components/NoFile.js
  12. 4
      src/ui/src/components/Slide.js
  13. 25
      src/ui/src/components/SlideItem.js
  14. 4
      src/ui/src/components/SlidesList.js
  15. 1
      src/ui/src/shared/SlideContext.js

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

@ -1,4 +1,6 @@
"use strict"; "use strict";
const {app, protocol} = require("electron");
const url = require("url");
const WindowManager = require("./WindowManager"); const WindowManager = require("./WindowManager");
@ -9,6 +11,14 @@ class Ation {
if (Ation.Instances > 0) if (Ation.Instances > 0)
throw new Error("Only one Instance of Ation possible"); throw new Error("Only one Instance of Ation possible");
this.windowManager = new WindowManager(); this.windowManager = new WindowManager();
app.whenReady().then(async () => {
protocol.registerFileProtocol("slideimg", (request, callback) => {
const path = request.url.replace(/^slideimg:\/\//, "");
console.log(path);
callback(path);
});
});
} }
} }
Ation.Instances = 0; Ation.Instances = 0;

@ -36,9 +36,10 @@ class WindowManager {
return null; return null;
const fileContents = await fs.readFile(result.filePaths[0], {encoding : "utf-8"}); const fileContents = await fs.readFile(result.filePaths[0], {encoding : "utf-8"});
const basePath = path.dirname(result.filePaths[0]);
const data = parser(fileContents); const data = parser(fileContents);
return data; return [basePath, data];
}); });
} }

@ -3,6 +3,7 @@
top: 0; top: 0;
left: 0; left: 0;
width: 100vw; width: 100vw;
cursor: none;
height: 100vh; height: 100vh;
z-index: 1000; z-index: 1000;
background: black; background: black;

@ -21,4 +21,24 @@ button {
margin-right: 0.3rem; margin-right: 0.3rem;
vertical-align: middle; vertical-align: middle;
} }
}
.checkbox {
display: inline-block;
width: 2vw;
height: 2vw;
border: solid 1px color(hightlight);
color: color(hightlight);
position: relative;
&.checked {
&::before {
content: "X";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 1.8vw;
}
}
} }

@ -1,33 +1,93 @@
.slide { .slide {
font-family: "Iosevka", sans-serif; font-family: "Iosevka", sans-serif;
font-size: 2rem; font-size: 4vw;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
user-select: none; user-select: none;
padding: 8vw;
text-align: center;
aspect-ratio: 1.333; aspect-ratio: 1.333;
background: color(background); background: color(background);
color: color(foreground); color: color(foreground);
ul, ol, pre {
text-align: left;
}
ul, ol {
li.task {
list-style: none;
margin-left: -0.8em;
}
}
blockquote {
opacity: 0.5;
font-style: italic;
position: relative;
&::before, &::after {
position: absolute;
font-size: 8vw;
opacity: 0.1;
}
&::before {
top: 0;
left: 0;
margin-left: -4vw;
content: "";
}
&::after {
bottom: 0;
right: 0;
margin-right: -4vw;
content: "";
}
}
figure {
img {
max-width: 95%;
height: auto;
}
figcaption {
font-size: 1.8vw;
opacity: 0.6;
font-style: italic;
font-weight: normal;
}
}
&.title { &.title {
p { p {
font-style: italic; font-style: italic;
font-size: 1.8rem; font-size: 3.5vw;
color: color(scrollbar); color: color(scrollbar);
&:nth-of-type(2) { &:nth-of-type(2) {
font-size: 1.2rem; font-size: 2.8vw;
} }
} }
} }
h1 { code:not([class*=language-]) {
font-size: 4rem; font-weight: normal;
font-size: 0.9em;
color: color(hightlight);
} }
code { pre {
font-size: 1.2rem; font-size: 1.5vw;
width: 90%; width: 90%;
padding: 4vw !important;
* {
font-weight: normal !important;
}
} }
} }

@ -16,5 +16,9 @@
margin: 5% 0 0 5%; margin: 5% 0 0 5%;
aspect-ratio: 1.333; aspect-ratio: 1.333;
-webkit-transform-origin: top left; -webkit-transform-origin: top left;
&.active {
border: solid 3px color(hightlight);
}
} }
} }

@ -4,7 +4,8 @@ $colors : (
foreground : #ddd, foreground : #ddd,
sidebarBackground : #333, sidebarBackground : #333,
mainBackground : #222, mainBackground : #222,
scrollbar : rgba(255,255,255,0.2) scrollbar : rgba(255,255,255,0.2),
hightlight : lawngreen
); );
:root { :root {

@ -11,15 +11,17 @@ import Toolbar from "./Toolbar";
import SlideContext from "../shared/SlideContext"; import SlideContext from "../shared/SlideContext";
const Ation = () => { const Ation = () => {
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);
const [basePath, setBasePath] = useState("");
const openFile = async () => { const openFile = async () => {
const slideDeck = await window.api.openFile(); const [basePath, slideDeck] = await window.api.openFile();
if (!slideDeck) if (!slideDeck)
return; return;
setBasePath(basePath);
setDeck(slideDeck); setDeck(slideDeck);
} }
@ -27,7 +29,7 @@ const Ation = () => {
return <NoFile openFile={openFile} />; return <NoFile openFile={openFile} />;
return ( return (
<SlideContext.Provider value={{slide, setSlide, mode, setMode}}> <SlideContext.Provider value={{slide, setSlide, mode, setMode, basePath}}>
<section className={`window${mode === Mode.PRESENT ? " fullscreen" : ""}`}> <section className={`window${mode === Mode.PRESENT ? " fullscreen" : ""}`}>
<Toolbar openFile={openFile} /> <Toolbar openFile={openFile} />
<SlidesList deck={deck} /> <SlidesList deck={deck} />
@ -36,7 +38,7 @@ const Ation = () => {
</main> </main>
</section> </section>
<Blackout show={mode === Mode.BLACKOUT} /> <Blackout show={mode === Mode.BLACKOUT} />
<KeyboardControl mode={mode} setMode={setMode} deck={deck} /> <KeyboardControl mode={mode} setMode={setMode} deck={deck} openFile={openFile} />
</SlideContext.Provider> </SlideContext.Provider>
); );
}; };

@ -3,12 +3,23 @@ import {useContext, useEffect} from "react";
import SlideContext from "../shared/SlideContext"; import SlideContext from "../shared/SlideContext";
import Mode from "../models/Mode"; import Mode from "../models/Mode";
const KeyboardControl = ({mode, setMode, deck}) => { const KeyboardControl = ({openFile, mode, setMode, deck}) => {
const {slide, setSlide} = useContext(SlideContext); const {slide, setSlide} = useContext(SlideContext);
useEffect(() => { useEffect(() => {
const keyHandler = event => { const keyHandler = event => {
console.log(event.key); switch(event.key) {
case "o":
if (!event.metaKey && !event.ctrlKey)
return;
openFile();
break;
default:
console.log(event.key);
}
if (!mode || !setMode || !deck)
return;
switch (event.key) { switch (event.key) {
case "F5": case "F5":
if (deck.length <= 0) if (deck.length <= 0)

@ -2,6 +2,8 @@ import React from "react";
import {Easel, Folder2Open} from "react-bootstrap-icons"; import {Easel, Folder2Open} from "react-bootstrap-icons";
import KeyboardControl from "./KeyboardControl";
const NoFile = ({openFile}) => { const NoFile = ({openFile}) => {
return ( return (
<section className="no-file"> <section className="no-file">
@ -14,6 +16,7 @@ const NoFile = ({openFile}) => {
<button onClick={openFile}><Folder2Open /> open</button> <button onClick={openFile}><Folder2Open /> open</button>
</p> </p>
</section> </section>
<KeyboardControl openFile={openFile} />
</section> </section>
); );
}; };

@ -2,9 +2,9 @@ import React from "react";
import SlideItem from "./SlideItem"; import SlideItem from "./SlideItem";
const Slide = ({data, ...props}) => { const Slide = ({data, className, ...props}) => {
return ( return (
<article className={`slide${data?.title ? " title" : ""}`} {...props}> <article className={`slide${data?.title ? " title" : ""} ${className}`} {...props}>
{data?.content.map((item, index) => {data?.content.map((item, index) =>
<SlideItem item={item} key={index} /> <SlideItem item={item} key={index} />
)} )}

@ -1,8 +1,10 @@
import React, {useMemo} from "react"; import React, {useMemo, useContext} from "react";
import SyntaxHighlighter from "react-syntax-highlighter" import SyntaxHighlighter from "react-syntax-highlighter"
import {monokai} from "react-syntax-highlighter/dist/esm/styles/hljs"; import {monokai} from "react-syntax-highlighter/dist/esm/styles/hljs";
import SlideContext from "../shared/SlideContext";
const Children = ({items}) => { const Children = ({items}) => {
if (items instanceof Array) if (items instanceof Array)
return <>{items.map((child, index) => <SlideItem item={child} key={index} />)}</>; return <>{items.map((child, index) => <SlideItem item={child} key={index} />)}</>;
@ -10,6 +12,8 @@ const Children = ({items}) => {
}; };
const SlideItem = ({item}) => { const SlideItem = ({item}) => {
const {basePath} = useContext(SlideContext);
const content = useMemo(() => { const content = useMemo(() => {
console.log("ITEM",item); console.log("ITEM",item);
switch (item.type) { switch (item.type) {
@ -21,12 +25,25 @@ const SlideItem = ({item}) => {
else else
return <ul><Children items={item.items} /></ul> return <ul><Children items={item.items} /></ul>
case "list_item": case "list_item":
const prefix = item.task ? <input type="checkbox" defaultChecked={item.checked} /> : null; const prefix = item.task ? <span className={`checkbox${item.checked ? " checked" : ""}`}></span> : null;
return <li>{prefix} <Children items={item.tokens} /></li> return <li className={item.task ? "task" : ""}>{prefix} <Children items={item.tokens} /></li>
case "image":
return (
<figure>
<img src={`slideimg://${basePath}/${item.href.replace(/\.\//, "")}`} alt={item.text} />
{item.text?.length > 0 ?
<figcaption>{item.text}</figcaption>
: null}
</figure>
)
case "blockquote":
return <blockquote><Children items={item.tokens} /></blockquote>
case "paragraph": case "paragraph":
return <p><Children items={item.tokens} /></p> return <p><Children items={item.tokens} /></p>
case "code": case "code":
return <SyntaxHighlighter style={monokai} language={item.lang}>{item.text}</SyntaxHighlighter> return <SyntaxHighlighter style={monokai} language={item.lang}>{item.text}</SyntaxHighlighter>
case "codespan":
return <code>{item.text}</code>
case "strong": case "strong":
return <b><Children items={item.tokens} /></b> return <b><Children items={item.tokens} /></b>
case "em": case "em":
@ -36,7 +53,7 @@ const SlideItem = ({item}) => {
default: default:
return JSON.stringify(item); return JSON.stringify(item);
} }
}, [item]); }, [item, basePath]);
return ( return (
<> <>

@ -36,9 +36,9 @@ const SlidesList = ({deck}) => {
return ( return (
<aside className="slides-list" ref={container}> <aside className="slides-list" ref={container}>
{deck.map((slide, index) => ( {deck.map((currentSlide, index) => (
<div className="slide-wrap" key={index}> <div className="slide-wrap" key={index}>
<Slide data={slide} style={{transform : `scale(${scale}) translateX(0)`}} onClick={() => setSlide(index)} /> <Slide data={currentSlide} className={index === slide ? "active" : ""} style={{transform : `scale(${scale}) translateX(0)`}} onClick={() => setSlide(index)} />
</div> </div>
))} ))}
</aside> </aside>

@ -5,6 +5,7 @@ import Mode from "../models/Mode";
const SlideContext = createContext({ const SlideContext = createContext({
slide : 0, slide : 0,
mode : Mode.NORMAL, mode : Mode.NORMAL,
basePath : "",
setMode : () => {}, setMode : () => {},
setSlide : () => {} setSlide : () => {}
}); });

Loading…
Cancel
Save