From c96348e888ee3142d1ac7657275ce1fe8629ec21 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Tue, 22 Oct 2024 16:31:52 +0200 Subject: [PATCH] still working on saving files --- examples/emmer/namespace/nl.json | 0 src/components/filetree.tsx | 4 +- src/features/menu/index.tsx | 35 +++------------- src/routes/(editor).tsx | 7 ++-- src/routes/(editor)/edit.tsx | 68 ++++++++++++++++++++++---------- src/routes/editor.module.css | 7 ++++ 6 files changed, 67 insertions(+), 54 deletions(-) create mode 100644 examples/emmer/namespace/nl.json create mode 100644 src/routes/editor.module.css diff --git a/examples/emmer/namespace/nl.json b/examples/emmer/namespace/nl.json new file mode 100644 index 0000000..e69de29 diff --git a/src/components/filetree.tsx b/src/components/filetree.tsx index e74803f..2d84649 100644 --- a/src/components/filetree.tsx +++ b/src/components/filetree.tsx @@ -11,6 +11,8 @@ export interface FileEntry { id: string; kind: 'file'; meta: File; + handle: FileSystemFileHandle; + directory: FileSystemDirectoryHandle; } export interface FolderEntry { @@ -37,7 +39,7 @@ export async function* walk(directory: FileSystemDirectoryHandle, filters: RegEx const id = await handle.getUniqueId(); if (handle.kind === 'file') { - yield { name: handle.name, id, kind: 'file', meta: await handle.getFile() }; + yield { name: handle.name, id, kind: 'file', meta: await handle.getFile(), handle, directory }; } else { yield { name: handle.name, id, kind: 'folder', entries: await Array.fromAsync(walk(handle, filters, depth + 1)) }; diff --git a/src/features/menu/index.tsx b/src/features/menu/index.tsx index 807da9a..b83c751 100644 --- a/src/features/menu/index.tsx +++ b/src/features/menu/index.tsx @@ -247,12 +247,6 @@ export const CommandPalette: Component<{ api?: (api: CommandPaletteApi) => any, } }); - // temp debug code - createEffect(() => { - search()?.searchFor('c'); - setOpen(true); - }); - const onSubmit = (command: CommandType) => { setOpen(false); props.onSubmit?.(command); @@ -296,7 +290,7 @@ interface SearchableListProps { function SearchableList(props: SearchableListProps): JSX.Element { const [term, setTerm] = createSignal(''); const [input, setInput] = createSignal(); - const [selected, setSelected] = createSignal(); + const [selected, setSelected] = createSignal(0); const id = createUniqueId(); const results = createMemo(() => { @@ -309,20 +303,7 @@ function SearchableList(props: SearchableListProps): JSX.Element { return props.items.filter(item => props.filter ? props.filter(item, search) : props.keySelector(item).includes(search)); }); - const value = createMemo(() => { - const index = selected(); - - if (index === undefined) { - return undefined; - } - - return results().at(index); - }); - const inputValue = createMemo(() => { - const v = value(); - - return v !== undefined ? props.keySelector(v) : term(); - }); + const value = createMemo(() => results().at(selected())); const ctx = { filter: term, @@ -333,7 +314,7 @@ function SearchableList(props: SearchableListProps): JSX.Element { }, clear() { setTerm(''); - setSelected(undefined); + setSelected(0); }, }; @@ -349,13 +330,13 @@ function SearchableList(props: SearchableListProps): JSX.Element { const onKeyDown = (e: KeyboardEvent) => { if (e.key === 'ArrowUp') { - setSelected(current => current !== undefined && current > 0 ? current - 1 : undefined); + setSelected(current => Math.max(0, current - 1)); e.preventDefault(); } if (e.key === 'ArrowDown') { - setSelected(current => current !== undefined ? Math.min(results().length - 1, current + 1) : 0); + setSelected(current => Math.min(results().length - 1, current + 1)); e.preventDefault(); } @@ -364,10 +345,6 @@ function SearchableList(props: SearchableListProps): JSX.Element { const onSubmit = (e: SubmitEvent) => { e.preventDefault(); - if (selected() === undefined && term() !== '') { - setSelected(0); - } - const v = value(); if (v === undefined) { @@ -379,7 +356,7 @@ function SearchableList(props: SearchableListProps): JSX.Element { }; return
- setTerm(e.target.value)} placeholder="start typing for command" autofocus /> + setTerm(e.target.value)} placeholder="start typing for command" autofocus autocomplete="off" /> { diff --git a/src/routes/(editor).tsx b/src/routes/(editor).tsx index 2554f22..9fabd87 100644 --- a/src/routes/(editor).tsx +++ b/src/routes/(editor).tsx @@ -1,11 +1,12 @@ import { Title } from "@solidjs/meta"; -import { Component, createEffect, createMemo, createSignal, For, ParentProps, Show } from "solid-js"; +import { createSignal, ParentProps, Show } from "solid-js"; import { BsTranslate } from "solid-icons/bs"; import { FilesProvider } from "~/features/file"; -import { CommandPalette, CommandPaletteApi, MenuProvider, asMenuRoot, useMenu } from "~/features/menu"; +import { CommandPalette, CommandPaletteApi, MenuProvider, asMenuRoot } from "~/features/menu"; import { isServer } from "solid-js/web"; import { A } from "@solidjs/router"; import { createCommand, Modifier } from "~/features/command"; +import css from "./editor.module.css"; asMenuRoot // prevents removal of import @@ -22,7 +23,7 @@ export default function Editor(props: ParentProps) { return Translation-Tool -
+
diff --git a/src/routes/(editor)/edit.tsx b/src/routes/(editor)/edit.tsx index 67a5004..e74424a 100644 --- a/src/routes/(editor)/edit.tsx +++ b/src/routes/(editor)/edit.tsx @@ -1,4 +1,4 @@ -import { children, createEffect, createMemo, createResource, createSignal, onMount, ParentProps } from "solid-js"; +import { children, createEffect, createMemo, createResource, createSignal, createUniqueId, onMount, ParentProps } from "solid-js"; import { MutarionKind, splitAt } from "~/utilities"; import { Sidebar } from "~/components/sidebar"; import { emptyFolder, FolderEntry, walk as fileTreeWalk, Tree, FileEntry, Entry } from "~/components/filetree"; @@ -132,7 +132,7 @@ export default function Edit(props: ParentProps) { filesContext.set('root', directory); mutate(directory); }), - save: createCommand('save', () => { + save: createCommand('save', async () => { const mutations = api()?.mutations() ?? []; if (mutations.length === 0) { @@ -154,8 +154,8 @@ export default function Edit(props: ParentProps) { // 3) When a file has 0 keys, we can remove it. for (const mutation of mutations) { - const [key, lang] = splitAt(mutation.key, mutation.key.lastIndexOf('.')); - const entry = _entries.get(key); + const [k, lang] = splitAt(mutation.key, mutation.key.lastIndexOf('.')); + const entry = _entries.get(k); const localEntry = entry?.[lang]; // TODO :: try to resolve to a file @@ -178,36 +178,62 @@ export default function Edit(props: ParentProps) { throw new Error('invalid edge case???'); } - if (localEntry.id === undefined) { - const [, alternativeLocalEntry] = Object.entries(entry).find(([l, e]) => l !== lang && e.id !== undefined) ?? []; + const [handle, path = []] = await (async () => { + if (localEntry.id === undefined) { + const [, alternativeLocalEntry] = Object.entries(entry).find(([l, e]) => l !== lang && e.id !== undefined) ?? []; - if (alternativeLocalEntry === undefined) { - // unable to find alternative. show a picker instead? - return; + const { directory, path } = alternativeLocalEntry ? findFile(tree(), alternativeLocalEntry.id) ?? {} : {}; + + // Short circuit if the mutation type is delete. + // Otherwise we would create a new file handle, + // and then immediately remove it again. + if (mutation.kind === MutarionKind.Delete) { + return [undefined, path] as const; + } + + const handle = await window.showSaveFilePicker({ + suggestedName: `${lang}.json`, + startIn: directory ?? root(), + excludeAcceptAllOption: true, + types: [ + { accept: { 'application/json': ['.json'] }, description: 'JSON' }, + ] + }); + + return [handle, path] as const; } - const file = findFile(tree(), alternativeLocalEntry.id); + const { handle, path } = findFile(tree(), localEntry.id) ?? {}; - console.log('alt', file); - } + return [handle, path] as const; + })(); - const file = findFile(tree(), localEntry.id); - const fileExists = file !== undefined; + console.log(k, path.join('.')); - console.log(key, file?.path.join('.')); + const createNewFile = async (lang: string, directory: FileSystemDirectoryHandle) => { + const handle = await window.showSaveFilePicker({ + suggestedName: `${lang}.json`, + startIn: directory, + excludeAcceptAllOption: true, + types: [ + { accept: { 'application/json': ['.json'] }, description: 'JSON' }, + ] + }); + }; - const fileLocalKey = key.slice(file?.path.join('.')); + const key = k.slice(path.join('.').length); + const { handle, path } = findFile(tree(), localEntry.id) ?? {}; - const result = match([fileExists, mutation.kind]) - .with([true, MutarionKind.Create], () => ({ action: MutarionKind.Create, key, value: rows[key][lang], file: file?.meta })) + const result = match([handle !== undefined, mutation.kind]) + .with([true, MutarionKind.Create], () => ({ action: MutarionKind.Create, key, value: rows[key][lang], handle })) .with([false, MutarionKind.Create], () => '2') - .with([true, MutarionKind.Update], () => ({ action: MutarionKind.Update, key, value: rows[key][lang], file: file?.meta })) + .with([true, MutarionKind.Update], () => ({ action: MutarionKind.Update, key, value: rows[key][lang], handle })) .with([false, MutarionKind.Update], () => '4') - .with([true, MutarionKind.Delete], () => ({ action: MutarionKind.Delete, key, file: file?.meta })) + .with([true, MutarionKind.Delete], () => ({ action: MutarionKind.Delete, key, handle })) .with([false, MutarionKind.Delete], () => '6') .exhaustive(); - console.log(mutation, key, lang, entry, file, result); + console.log(mutation, key, lang, entry, result); } // for (const fileId of files) { diff --git a/src/routes/editor.module.css b/src/routes/editor.module.css new file mode 100644 index 0000000..a524524 --- /dev/null +++ b/src/routes/editor.module.css @@ -0,0 +1,7 @@ +.layout { + display: grid; + grid: auto minmax(0, 1fr) / 100%; + inline-size: 100%; + block-size: 100%; + overflow: clip; +} \ No newline at end of file