implemented feature

TODOs:
- extract logic to feature file to simplify component
- add unit test
- add end-to-end tests
This commit is contained in:
Chris Kruining 2024-12-02 16:26:00 +01:00
parent 316e3158db
commit 4e98849e07
No known key found for this signature in database
GPG key ID: EB894A3560CCCAD2
3 changed files with 101 additions and 20 deletions

View file

@ -1,5 +1,5 @@
import { Component, createEffect, createMemo, createSignal, For, onMount, ParentProps, Setter, Show } from "solid-js";
import { filter, MutarionKind, Mutation, splitAt } from "~/utilities";
import { Created, filter, MutarionKind, Mutation, splitAt } from "~/utilities";
import { Sidebar } from "~/components/sidebar";
import { emptyFolder, FolderEntry, walk as fileTreeWalk, Tree } from "~/components/filetree";
import { Menu } from "~/features/menu";
@ -91,7 +91,8 @@ const Editor: Component<{ root: FileSystemDirectoryHandle }> = (props) => {
const [active, setActive] = createSignal<string>();
const [contents, setContents] = createSignal<Map<string, Map<string, string>>>(new Map());
const [tree, setFiles] = createSignal<FolderEntry>(emptyFolder);
const [prompt, setPrompt] = createSignal<PromptApi>();
const [newKeyPrompt, setNewKeyPrompt] = createSignal<PromptApi>();
const [newLanguagePrompt, setNewLanguagePrompt] = createSignal<PromptApi>();
const tab = createMemo(() => {
const name = active();
@ -99,7 +100,7 @@ const Editor: Component<{ root: FileSystemDirectoryHandle }> = (props) => {
return tabs().find(t => t.handle.name === name);
});
const api = createMemo(() => tab()?.api());
const mutations = createMemo<(Mutation & { file?: { value: string, handle: FileSystemFileHandle, id: string } })[]>(() => tabs().flatMap(tab => {
const mutations = createMemo<(Mutation & { lang: string, file?: { value: string, handle: FileSystemFileHandle, id: string } })[]>(() => tabs().flatMap(tab => {
const entries = tab.entries();
const files = tab.files();
const mutations = tab.api()?.mutations() ?? [];
@ -109,11 +110,19 @@ const Editor: Component<{ root: FileSystemDirectoryHandle }> = (props) => {
case MutarionKind.Update: {
const [key, lang] = splitAt(m.key, m.key.lastIndexOf('.'));
return { kind: MutarionKind.Update, key, file: entries.get(key)?.[lang] };
return { kind: MutarionKind.Update, key, lang, file: entries.get(key)?.[lang] };
}
case MutarionKind.Create: {
return Object.entries(m.value).map(([lang, value]) => ({ kind: MutarionKind.Create, key: m.key, file: files.get(lang)!, value }));
if (typeof m.value === 'object') {
return Object.entries(m.value).map(([lang, value]) => {
return ({ kind: MutarionKind.Create, key: m.key, lang, file: files.get(lang)!, value });
});
}
const [key, lang] = splitAt(m.key, m.key.lastIndexOf('.'));
return { kind: MutarionKind.Create, key, lang, file: undefined, value: m.value };
}
case MutarionKind.Delete: {
@ -137,8 +146,35 @@ const Editor: Component<{ root: FileSystemDirectoryHandle }> = (props) => {
}
const groupedByFileId = Object.groupBy(muts, m => m.file?.id ?? 'undefined');
const newFiles = Object.entries(Object.groupBy((groupedByFileId['undefined'] ?? []) as (Created & { lang: string, file: undefined })[], m => m.lang)).map(([lang, mutations]) => {
const data = mutations!.reduce((aggregate, { key, value }) => {
let obj = aggregate;
const i = key.lastIndexOf('.');
return entries.map(({ id, handle }) => {
if (i !== -1) {
const [k, lastPart] = splitAt(key, i);
for (const part of k.split('.')) {
if (!Object.hasOwn(obj, part)) {
obj[part] = {};
}
obj = obj[part];
}
obj[lastPart] = value;
}
else {
obj[key] = value;
}
return aggregate;
}, {} as Record<string, any>);
return [{ existing: false, name: lang }, data] as const;
})
const existingFiles = entries.map(({ id, handle }) => {
const existing = new Map(files.get(id)!);
const mutations = groupedByFileId[id]!;
@ -158,7 +194,7 @@ const Editor: Component<{ root: FileSystemDirectoryHandle }> = (props) => {
}
return [
handle,
{ existing: true, handle },
existing.entries().reduce((aggregate, [key, value]) => {
let obj = aggregate;
const i = key.lastIndexOf('.');
@ -183,9 +219,15 @@ const Editor: Component<{ root: FileSystemDirectoryHandle }> = (props) => {
return aggregate;
}, {} as Record<string, any>)
] as const;
}).toArray();
}).toArray() as (readonly [({ existing: true, handle: FileSystemFileHandle } | { existing: false, name: string }), Record<string, any>])[];
return existingFiles.concat(newFiles);
});
// createEffect(() => {
// console.log(mutatedData());
// });
createEffect(() => {
const directory = props.root;
@ -208,7 +250,10 @@ const Editor: Component<{ root: FileSystemDirectoryHandle }> = (props) => {
filesContext.remove(id);
}, { key: 'w', modifier: Modifier.Control | (isInstalledPWA ? Modifier.None : Modifier.Alt) }),
save: createCommand('save', async () => {
await Promise.allSettled(mutatedData().map(async ([handle, data]) => {
await Promise.allSettled(mutatedData().map(async ([file, data]) => {
// TODO :: add the newly created file to the known files list to that the save file picker is not shown again on subsequent saves
const handle = file.existing ? file.handle : await window.showSaveFilePicker({ suggestedName: file.name, excludeAcceptAllOption: true, types: [{ description: 'JSON file', accept: { 'application/json': ['.json'] } }] });
const stream = await handle.createWritable({ keepExistingData: false });
stream.write(JSON.stringify(data, null, 4));
@ -246,7 +291,7 @@ const Editor: Component<{ root: FileSystemDirectoryHandle }> = (props) => {
remove(Object.keys(selection()));
}, { key: 'delete', modifier: Modifier.None }),
inserNewKey: createCommand('insert new key', async () => {
const formData = await prompt()?.showModal();
const formData = await newKeyPrompt()?.showModal();
const key = formData?.get('key')?.toString();
if (!key) {
@ -255,7 +300,18 @@ const Editor: Component<{ root: FileSystemDirectoryHandle }> = (props) => {
api()?.insert(key);
}),
inserNewLanguage: noop.withLabel('insert new language'),
inserNewLanguage: createCommand('insert new language', async () => {
const formData = await newLanguagePrompt()?.showModal();
const language = formData?.get('locale')?.toString();
if (!language) {
return;
}
console.log(language);
api()?.addColumn(language);
}),
} as const;
return <div class={css.root}>
@ -295,8 +351,12 @@ const Editor: Component<{ root: FileSystemDirectoryHandle }> = (props) => {
<Menu.Item command={noop.withLabel('view')} />
</Menu.Root>
<Prompt api={setPrompt} title="Which key do you want to create?" description={<>hint: use <code>.</code> to denote nested keys,<br /> i.e. <code>this.is.some.key</code> would be a key that is four levels deep</>}>
<input name="key" value="this.is.an.awesome.key" placeholder="name of new key ()" />
<Prompt api={setNewKeyPrompt} title="Which key do you want to create?" description={<>hint: use <code>.</code> to denote nested keys,<br /> i.e. <code>this.is.some.key</code> would be a key that is four levels deep</>}>
<input name="key" placeholder="name of new key ()" value="keyF.some.deeper.nested.value" />
</Prompt>
<Prompt api={setNewLanguagePrompt}>
<input name="locale" placeholder="locale code, i.e. en-GB" value="fr-FR" />
</Prompt>
<Sidebar as="aside" label={tree().name} class={css.sidebar}>