still working on saving files

This commit is contained in:
Chris Kruining 2024-10-22 16:31:52 +02:00
parent e363ee1844
commit c96348e888
No known key found for this signature in database
GPG key ID: EB894A3560CCCAD2
6 changed files with 67 additions and 54 deletions

View file

View file

@ -11,6 +11,8 @@ export interface FileEntry {
id: string; id: string;
kind: 'file'; kind: 'file';
meta: File; meta: File;
handle: FileSystemFileHandle;
directory: FileSystemDirectoryHandle;
} }
export interface FolderEntry { export interface FolderEntry {
@ -37,7 +39,7 @@ export async function* walk(directory: FileSystemDirectoryHandle, filters: RegEx
const id = await handle.getUniqueId(); const id = await handle.getUniqueId();
if (handle.kind === 'file') { 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 { else {
yield { name: handle.name, id, kind: 'folder', entries: await Array.fromAsync(walk(handle, filters, depth + 1)) }; yield { name: handle.name, id, kind: 'folder', entries: await Array.fromAsync(walk(handle, filters, depth + 1)) };

View file

@ -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) => { const onSubmit = (command: CommandType) => {
setOpen(false); setOpen(false);
props.onSubmit?.(command); props.onSubmit?.(command);
@ -296,7 +290,7 @@ interface SearchableListProps<T> {
function SearchableList<T>(props: SearchableListProps<T>): JSX.Element { function SearchableList<T>(props: SearchableListProps<T>): JSX.Element {
const [term, setTerm] = createSignal<string>(''); const [term, setTerm] = createSignal<string>('');
const [input, setInput] = createSignal<HTMLInputElement>(); const [input, setInput] = createSignal<HTMLInputElement>();
const [selected, setSelected] = createSignal<number>(); const [selected, setSelected] = createSignal<number>(0);
const id = createUniqueId(); const id = createUniqueId();
const results = createMemo(() => { const results = createMemo(() => {
@ -309,20 +303,7 @@ function SearchableList<T>(props: SearchableListProps<T>): JSX.Element {
return props.items.filter(item => props.filter ? props.filter(item, search) : props.keySelector(item).includes(search)); return props.items.filter(item => props.filter ? props.filter(item, search) : props.keySelector(item).includes(search));
}); });
const value = createMemo(() => { const value = createMemo(() => results().at(selected()));
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 ctx = { const ctx = {
filter: term, filter: term,
@ -333,7 +314,7 @@ function SearchableList<T>(props: SearchableListProps<T>): JSX.Element {
}, },
clear() { clear() {
setTerm(''); setTerm('');
setSelected(undefined); setSelected(0);
}, },
}; };
@ -349,13 +330,13 @@ function SearchableList<T>(props: SearchableListProps<T>): JSX.Element {
const onKeyDown = (e: KeyboardEvent) => { const onKeyDown = (e: KeyboardEvent) => {
if (e.key === 'ArrowUp') { if (e.key === 'ArrowUp') {
setSelected(current => current !== undefined && current > 0 ? current - 1 : undefined); setSelected(current => Math.max(0, current - 1));
e.preventDefault(); e.preventDefault();
} }
if (e.key === 'ArrowDown') { 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(); e.preventDefault();
} }
@ -364,10 +345,6 @@ function SearchableList<T>(props: SearchableListProps<T>): JSX.Element {
const onSubmit = (e: SubmitEvent) => { const onSubmit = (e: SubmitEvent) => {
e.preventDefault(); e.preventDefault();
if (selected() === undefined && term() !== '') {
setSelected(0);
}
const v = value(); const v = value();
if (v === undefined) { if (v === undefined) {
@ -379,7 +356,7 @@ function SearchableList<T>(props: SearchableListProps<T>): JSX.Element {
}; };
return <form method="dialog" class={css.search} onkeydown={onKeyDown} onsubmit={onSubmit}> return <form method="dialog" class={css.search} onkeydown={onKeyDown} onsubmit={onSubmit}>
<input id={`search-${id}`} ref={setInput} value={inputValue()} oninput={(e) => setTerm(e.target.value)} placeholder="start typing for command" autofocus /> <input id={`search-${id}`} ref={setInput} value={term()} oninput={(e) => setTerm(e.target.value)} placeholder="start typing for command" autofocus autocomplete="off" />
<output for={`search-${id}`}> <output for={`search-${id}`}>
<For each={results()}>{ <For each={results()}>{

View file

@ -1,11 +1,12 @@
import { Title } from "@solidjs/meta"; 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 { BsTranslate } from "solid-icons/bs";
import { FilesProvider } from "~/features/file"; 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 { isServer } from "solid-js/web";
import { A } from "@solidjs/router"; import { A } from "@solidjs/router";
import { createCommand, Modifier } from "~/features/command"; import { createCommand, Modifier } from "~/features/command";
import css from "./editor.module.css";
asMenuRoot // prevents removal of import asMenuRoot // prevents removal of import
@ -22,7 +23,7 @@ export default function Editor(props: ParentProps) {
return <MenuProvider commands={commands}> return <MenuProvider commands={commands}>
<Title>Translation-Tool</Title> <Title>Translation-Tool</Title>
<main inert={commandPalette()?.open()}> <main class={css.layout} inert={commandPalette()?.open()}>
<nav use:asMenuRoot> <nav use:asMenuRoot>
<A class="logo" href="/"><BsTranslate /></A> <A class="logo" href="/"><BsTranslate /></A>
</nav> </nav>

View file

@ -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 { MutarionKind, splitAt } from "~/utilities";
import { Sidebar } from "~/components/sidebar"; import { Sidebar } from "~/components/sidebar";
import { emptyFolder, FolderEntry, walk as fileTreeWalk, Tree, FileEntry, Entry } from "~/components/filetree"; 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); filesContext.set('root', directory);
mutate(directory); mutate(directory);
}), }),
save: createCommand('save', () => { save: createCommand('save', async () => {
const mutations = api()?.mutations() ?? []; const mutations = api()?.mutations() ?? [];
if (mutations.length === 0) { 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. // 3) When a file has 0 keys, we can remove it.
for (const mutation of mutations) { for (const mutation of mutations) {
const [key, lang] = splitAt(mutation.key, mutation.key.lastIndexOf('.')); const [k, lang] = splitAt(mutation.key, mutation.key.lastIndexOf('.'));
const entry = _entries.get(key); const entry = _entries.get(k);
const localEntry = entry?.[lang]; const localEntry = entry?.[lang];
// TODO :: try to resolve to a file // TODO :: try to resolve to a file
@ -178,36 +178,62 @@ export default function Edit(props: ParentProps) {
throw new Error('invalid edge case???'); throw new Error('invalid edge case???');
} }
if (localEntry.id === undefined) { const [handle, path = []] = await (async () => {
const [, alternativeLocalEntry] = Object.entries(entry).find(([l, e]) => l !== lang && e.id !== undefined) ?? []; if (localEntry.id === undefined) {
const [, alternativeLocalEntry] = Object.entries(entry).find(([l, e]) => l !== lang && e.id !== undefined) ?? [];
if (alternativeLocalEntry === undefined) { const { directory, path } = alternativeLocalEntry ? findFile(tree(), alternativeLocalEntry.id) ?? {} : {};
// unable to find alternative. show a picker instead?
return; // 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); console.log(k, path.join('.'));
const fileExists = file !== undefined;
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]) const result = match([handle !== undefined, mutation.kind])
.with([true, MutarionKind.Create], () => ({ action: MutarionKind.Create, key, value: rows[key][lang], file: file?.meta })) .with([true, MutarionKind.Create], () => ({ action: MutarionKind.Create, key, value: rows[key][lang], handle }))
.with([false, MutarionKind.Create], () => '2') .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([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') .with([false, MutarionKind.Delete], () => '6')
.exhaustive(); .exhaustive();
console.log(mutation, key, lang, entry, file, result); console.log(mutation, key, lang, entry, result);
} }
// for (const fileId of files) { // for (const fileId of files) {

View file

@ -0,0 +1,7 @@
.layout {
display: grid;
grid: auto minmax(0, 1fr) / 100%;
inline-size: 100%;
block-size: 100%;
overflow: clip;
}