break components into files

This commit is contained in:
Joshua Seigler 2024-04-07 21:26:47 -04:00
parent df6c850b3f
commit 6e15a05126
10 changed files with 8714 additions and 8453 deletions

16503
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -8,6 +8,7 @@
},
"dependencies": {
"@preact/signals": "^1.2.3",
"localforage": "^1.10.0",
"preact": "^10.13.1",
"preact-iso": "^2.6.2",
"preact-render-to-string": "^6.4.1",

View file

@ -0,0 +1,42 @@
import { ligatures } from "./store";
export default function DualText({ children }: { children: string }) {
const words: string[] = [];
children.split(/\n/).forEach((line, index, lines) => {
line.split(/\b(?=\w)/).forEach((word) => {
words.push(word);
});
if (index < lines.length - 1) {
words.push("\n");
}
});
return (
<span>
{words.map((word) => {
if (word === "\n") {
return <br />;
}
const letters: string[] = [];
for (let i = 0; i < word.length; i += 1) {
const nextTwoCharacters = word.slice(i, i + 2);
if (ligatures.value.includes(nextTwoCharacters.toLowerCase())) {
letters.push(nextTwoCharacters);
i += 1;
} else {
letters.push(nextTwoCharacters[0]);
}
}
return (
<span class="dualtext-word">
<span class="aurebesh">{word}</span>
<div className="dualtext-help">
{letters.map((character) => {
return <span data-character={character} />;
})}
</div>
</span>
);
})}
</span>
);
}

View file

@ -0,0 +1,22 @@
import localforage from "localforage";
import { fontNames, selectedFont } from "./store";
export default function FontPicker() {
return (
<select
onChange={(event) => {
const newValue = event.currentTarget.value;
selectedFont.value = newValue;
localforage.setItem("aurebesh-font", newValue);
}}
>
{fontNames.map((font) => {
return (
<option value={font} selected={font === selectedFont.value}>
{font}
</option>
);
})}
</select>
);
}

202
src/components/Main.tsx Normal file
View file

@ -0,0 +1,202 @@
import { computed, effect, signal } from "@preact/signals";
import localforage from "localforage";
import { useEffect } from "preact/hooks";
import ReadingBox from "./ReadingBox";
const fonts: Record<string, { ligatures: string[]; lowercase: boolean }> = {
"AB-Equinox": {
ligatures: ["ch", "sh", "th", "ae", "eo", "kh", "ng", "oo"],
lowercase: false,
},
"AurebeshAF-Canon": {
ligatures: [],
lowercase: false,
},
"AurebeshAF-CanonTech": {
ligatures: [],
lowercase: false,
},
"AurebeshAF-Legends": {
ligatures: ["ch", "sh", "th", "ae", "eo", "kh", "ng", "oo"],
lowercase: false,
},
"AurebeshAF-LegendsTech": {
ligatures: ["ch", "sh", "th", "ae", "eo", "kh", "ng", "oo"],
lowercase: false,
},
"Aurebesh_Rodian-Oblique": {
ligatures: [],
lowercase: false,
},
"Aurebesh_Rodian-OblqOutline": {
ligatures: [],
lowercase: false,
},
Aurebesh_Rodian: {
ligatures: [],
lowercase: false,
},
"Aurebesh_Rodian-Outline": {
ligatures: [],
lowercase: false,
},
"AurebeshTypewriter-Light": {
ligatures: [],
lowercase: true,
},
"AurebeshTypewriter-Regular": {
ligatures: [],
lowercase: true,
},
Droidobesh: {
ligatures: [],
lowercase: false,
},
LaptiNekAF: {
ligatures: [],
lowercase: false,
},
Maulobesh: {
ligatures: [],
lowercase: false,
},
Nirvanabesh: {
ligatures: [],
lowercase: false,
},
Skyhook: {
ligatures: [],
lowercase: false,
},
};
const fontNames = Object.keys(fonts);
const selectedFont = signal("AurebeshAF-Legends");
effect(() => {
if (typeof window !== "undefined") {
document.documentElement.style.setProperty(
"--font-aurebesh",
selectedFont.value
);
}
});
const ligatures = computed(() => {
return fonts[selectedFont.value].ligatures;
});
const lowercase = computed(() => {
return fonts[selectedFont.value].lowercase;
});
export default function Main() {
useEffect(() => {
localforage
.getItem("aurebesh-font")
.then((value) => {
if (value != null) {
selectedFont.value = `${value}`;
}
})
.catch((error) => {
console.warn("Couldn't fetch font preference: ", error);
});
}, []);
return (
<>
<header>
<h1>
<DualText>Learn Aurebesh</DualText>
</h1>
</header>
<main>
<div class="content">
<FontPicker />
<ReadingBox />
</div>
<Reference />
</main>
</>
);
}
function FontPicker() {
return (
<select
onChange={(event) => {
const newValue = event.currentTarget.value;
selectedFont.value = newValue;
localforage.setItem("aurebesh-font", newValue);
}}
>
{fontNames.map((font) => {
return (
<option value={font} selected={font === selectedFont.value}>
{font}
</option>
);
})}
</select>
);
}
function DualText({ children }: { children: string }) {
const words: string[] = [];
children.split(/\n/).forEach((line, index, lines) => {
line.split(/\b(?=\w)/).forEach((word) => {
words.push(word);
});
if (index < lines.length - 1) {
words.push("\n");
}
});
return (
<span>
{words.map((word) => {
if (word === "\n") {
return <br />;
}
const letters: string[] = [];
for (let i = 0; i < word.length; i += 1) {
const nextTwoCharacters = word.slice(i, i + 2);
if (ligatures.value.includes(nextTwoCharacters.toLowerCase())) {
letters.push(nextTwoCharacters);
i += 1;
} else {
letters.push(nextTwoCharacters[0]);
}
}
return (
<span class="dualtext-word">
<span class="aurebesh">{word}</span>
<div className="dualtext-help">
{letters.map((character) => {
return <span data-character={character} />;
})}
</div>
</span>
);
})}
</span>
);
}
function Reference() {
return (
<aside class="reference">
<DualText>
{lowercase.value ? "Aa Bb Cc Dd Ee Ff Gg Hh Ii" : "A B C D E F G H I"}
</DualText>
<DualText>
{lowercase.value ? "Jj Kk Ll Mm Nn Oo Pp Qq Rr" : "J K L M N O P Q R"}
</DualText>
<DualText>
{lowercase.value ? "Ss Tt Uu Vv Ww Xx Yy Zz" : "S T U V W X Y Z"}
</DualText>
<DualText>0 1 2 3 4 5 6 7 8 9</DualText>
{ligatures.value.length > 0 && (
<DualText>{ligatures.value.join(" ")}</DualText>
)}
<DualText>{`, . ? ! : ; ' " ( )`}</DualText>
</aside>
);
}

View file

@ -0,0 +1,58 @@
import { useSignal } from "@preact/signals";
import localforage from "localforage";
import { useEffect } from "preact/hooks";
import DualText from "./DualText";
export default function ReadingBox() {
const isEditing = useSignal(false);
const contents =
useSignal(`It is a period of civil war. Rebel spaceships, striking from a hidden base, have won their first victory against the evil Galactic Empire.
During the battle, Rebel spies managed to steal secret plans to the Empire's ultimate weapon, the DEATH STAR, an armored space station with enough power to destroy an entire planet.
Pursued by the Empire's sinister agents, Princess Leia races home aboard her starship, custodian of the stolen plans that can save her people and restore freedom to the galaxy...`);
useEffect(() => {
localforage
.getItem("aurebesh-text")
.then((value) => {
if (value != null) {
contents.value = `${value}`;
}
})
.catch((error) => {
console.warn("Couldn't fetch stored text: ", error);
});
}, []);
return (
<div class="readingbox">
<span>
<label htmlFor="edit-toggle">Edit</label>
<input
type="checkbox"
id="edit-toggle"
selected={isEditing}
onChange={(event) => {
isEditing.value = event.currentTarget.checked;
}}
/>
</span>
{isEditing.value ? (
<textarea
class="readingbox-textarea"
id="reading-material"
value={contents}
onChange={(event) => {
const newValue = event.currentTarget.value;
contents.value = newValue;
localforage.setItem("aurebesh-text", newValue);
}}
spellCheck={false}
/>
) : (
<div class="readingbox-text aurebesh">
<DualText>{contents.value}</DualText>
</div>
)}
</div>
);
}

View file

@ -0,0 +1,23 @@
import DualText from "./DualText";
import { ligatures, lowercase } from "./store";
export default function Reference() {
return (
<aside class="reference">
<DualText>
{lowercase.value ? "Aa Bb Cc Dd Ee Ff Gg Hh Ii" : "A B C D E F G H I"}
</DualText>
<DualText>
{lowercase.value ? "Jj Kk Ll Mm Nn Oo Pp Qq Rr" : "J K L M N O P Q R"}
</DualText>
<DualText>
{lowercase.value ? "Ss Tt Uu Vv Ww Xx Yy Zz" : "S T U V W X Y Z"}
</DualText>
<DualText>0 1 2 3 4 5 6 7 8 9</DualText>
{ligatures.value.length > 0 && (
<DualText>{ligatures.value.join(" ")}</DualText>
)}
<DualText>{`, . ? ! : ; ' " ( )`}</DualText>
</aside>
);
}

89
src/components/store.ts Normal file
View file

@ -0,0 +1,89 @@
import { computed, effect, signal } from "@preact/signals";
export const fonts: Record<
string,
{ ligatures: string[]; lowercase: boolean }
> = {
"AB-Equinox": {
ligatures: ["ch", "sh", "th", "ae", "eo", "kh", "ng", "oo"],
lowercase: false,
},
"AurebeshAF-Canon": {
ligatures: [],
lowercase: false,
},
"AurebeshAF-CanonTech": {
ligatures: [],
lowercase: false,
},
"AurebeshAF-Legends": {
ligatures: ["ch", "sh", "th", "ae", "eo", "kh", "ng", "oo"],
lowercase: false,
},
"AurebeshAF-LegendsTech": {
ligatures: ["ch", "sh", "th", "ae", "eo", "kh", "ng", "oo"],
lowercase: false,
},
"Aurebesh_Rodian-Oblique": {
ligatures: [],
lowercase: false,
},
"Aurebesh_Rodian-OblqOutline": {
ligatures: [],
lowercase: false,
},
Aurebesh_Rodian: {
ligatures: [],
lowercase: false,
},
"Aurebesh_Rodian-Outline": {
ligatures: [],
lowercase: false,
},
"AurebeshTypewriter-Light": {
ligatures: [],
lowercase: true,
},
"AurebeshTypewriter-Regular": {
ligatures: [],
lowercase: true,
},
Droidobesh: {
ligatures: [],
lowercase: false,
},
LaptiNekAF: {
ligatures: [],
lowercase: false,
},
Maulobesh: {
ligatures: [],
lowercase: false,
},
Nirvanabesh: {
ligatures: [],
lowercase: false,
},
Skyhook: {
ligatures: [],
lowercase: false,
},
};
export const fontNames = Object.keys(fonts);
export const selectedFont = signal("AurebeshAF-Legends");
effect(() => {
if (typeof window !== "undefined") {
document.documentElement.style.setProperty(
"--font-aurebesh",
selectedFont.value
);
}
});
export const ligatures = computed(() => {
return fonts[selectedFont.value].ligatures;
});
export const lowercase = computed(() => {
return fonts[selectedFont.value].lowercase;
});

View file

@ -1,230 +1,10 @@
import { hydrate, prerender as ssr } from "preact-iso";
import "./style.css";
import { computed, effect, signal, useSignal } from "@preact/signals";
const fonts: Record<string, { ligatures: string[]; lowercase: boolean }> = {
"AB-Equinox": {
ligatures: ["ch", "sh", "th", "ae", "eo", "kh", "ng", "oo"],
lowercase: false,
},
"AurebeshAF-Canon": {
ligatures: [],
lowercase: false,
},
"AurebeshAF-CanonTech": {
ligatures: [],
lowercase: false,
},
"AurebeshAF-Legends": {
ligatures: ["ch", "sh", "th", "ae", "eo", "kh", "ng", "oo"],
lowercase: false,
},
"AurebeshAF-LegendsTech": {
ligatures: ["ch", "sh", "th", "ae", "eo", "kh", "ng", "oo"],
lowercase: false,
},
"Aurebesh_Rodian-Oblique": {
ligatures: [],
lowercase: false,
},
"Aurebesh_Rodian-OblqOutline": {
ligatures: [],
lowercase: false,
},
Aurebesh_Rodian: {
ligatures: [],
lowercase: false,
},
"Aurebesh_Rodian-Outline": {
ligatures: [],
lowercase: false,
},
"AurebeshTypewriter-Light": {
ligatures: [],
lowercase: true,
},
"AurebeshTypewriter-Regular": {
ligatures: [],
lowercase: true,
},
Droidobesh: {
ligatures: [],
lowercase: false,
},
LaptiNekAF: {
ligatures: [],
lowercase: false,
},
Maulobesh: {
ligatures: [],
lowercase: false,
},
Nirvanabesh: {
ligatures: [],
lowercase: false,
},
Skyhook: {
ligatures: [],
lowercase: false,
},
};
const fontNames = Object.keys(fonts);
const selectedFont = signal("AurebeshAF-Legends");
effect(() => {
if (typeof window !== "undefined") {
document.documentElement.style.setProperty(
"--font-aurebesh",
selectedFont.value
);
}
});
const ligatures = computed(() => {
return fonts[selectedFont.value].ligatures;
});
const lowercase = computed(() => {
return fonts[selectedFont.value].lowercase;
});
import Main from "./components/Main";
export function App() {
return (
<>
<header>
<h1>
<DualText>Learn Aurebesh</DualText>
</h1>
</header>
<main>
<div class="content">
<FontPicker />
<ReadingBox />
</div>
<Reference />
</main>
</>
);
}
function ReadingBox() {
const isEditing = useSignal(false);
const contents =
useSignal(`It is a period of civil war. Rebel spaceships, striking from a hidden base, have won their first victory against the evil Galactic Empire.
During the battle, Rebel spies managed to steal secret plans to the Empire's ultimate weapon, the DEATH STAR, an armored space station with enough power to destroy an entire planet.
Pursued by the Empire's sinister agents, Princess Leia races home aboard her starship, custodian of the stolen plans that can save her people and restore freedom to the galaxy...`);
return (
<div class="readingbox">
<span>
<label htmlFor="edit-toggle">Edit</label>
<input
type="checkbox"
id="edit-toggle"
selected={isEditing}
onChange={(event) => {
isEditing.value = event.currentTarget.checked;
}}
/>
</span>
{isEditing.value ? (
<textarea
class="readingbox-textarea"
id="reading-material"
value={contents}
onChange={(event) => {
contents.value = event.currentTarget.value;
}}
spellCheck={false}
/>
) : (
<div class="readingbox-text aurebesh">
<DualText>{contents.value}</DualText>
</div>
)}
</div>
);
}
function FontPicker() {
return (
<select
onChange={(event) => {
selectedFont.value = event.currentTarget.value;
}}
>
{fontNames.map((font) => {
return (
<option value={font} selected={font === selectedFont.value}>
{font}
</option>
);
})}
</select>
);
}
function DualText({ children }: { children: string }) {
const words: string[] = [];
children.split(/\n/).forEach((line, index, lines) => {
line.split(/\b(?=\w)/).forEach((word) => {
words.push(word);
});
if (index < lines.length - 1) {
words.push("\n");
}
});
return (
<span>
{words.map((word) => {
if (word === "\n") {
return <br />;
}
const letters: string[] = [];
for (let i = 0; i < word.length; i += 1) {
const nextTwoCharacters = word.slice(i, i + 2);
if (ligatures.value.includes(nextTwoCharacters.toLowerCase())) {
letters.push(nextTwoCharacters);
i += 1;
} else {
letters.push(nextTwoCharacters[0]);
}
}
return (
<span class="dualtext-word">
<span class="aurebesh">{word}</span>
<div className="dualtext-help">
{letters.map((character) => {
return <span data-character={character} />;
})}
</div>
</span>
);
})}
</span>
);
}
function Reference() {
return (
<aside class="reference">
<DualText>
{lowercase.value ? "Aa Bb Cc Dd Ee Ff Gg Hh Ii" : "A B C D E F G H I"}
</DualText>
<DualText>
{lowercase.value ? "Jj Kk Ll Mm Nn Oo Pp Qq Rr" : "J K L M N O P Q R"}
</DualText>
<DualText>
{lowercase.value ? "Ss Tt Uu Vv Ww Xx Yy Zz" : "S T U V W X Y Z"}
</DualText>
<DualText>0 1 2 3 4 5 6 7 8 9</DualText>
{ligatures.value.length > 0 && (
<DualText>{ligatures.value.join(" ")}</DualText>
)}
<DualText>{`, . ? ! : ; ' " ( )`}</DualText>
</aside>
);
return <Main />;
}
if (typeof window !== "undefined") {

View file

@ -86,7 +86,7 @@
--font-aurebesh: Droidobesh;
--font-standard: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
font-size: clamp(100%, 1rem + 2vw, 24px);
font-size: clamp(100%, 1.5rem + 3vw, 350%);
box-sizing: border-box;
}
@ -172,6 +172,7 @@ h1 {
min-width: 100%;
font-family: var(--font-standard);
font-size: clamp(0.75rem, 0.25em, 1rem);
line-height: 1;
font-weight: 400;
color: var(--color-text-light);
}