still working on saving files
This commit is contained in:
parent
e363ee1844
commit
c96348e888
6 changed files with 67 additions and 54 deletions
0
examples/emmer/namespace/nl.json
Normal file
0
examples/emmer/namespace/nl.json
Normal 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)) };
|
||||||
|
|
|
@ -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()}>{
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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???');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const [handle, path = []] = await (async () => {
|
||||||
if (localEntry.id === undefined) {
|
if (localEntry.id === undefined) {
|
||||||
const [, alternativeLocalEntry] = Object.entries(entry).find(([l, e]) => l !== lang && e.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 file = findFile(tree(), alternativeLocalEntry.id);
|
const handle = await window.showSaveFilePicker({
|
||||||
|
suggestedName: `${lang}.json`,
|
||||||
|
startIn: directory ?? root(),
|
||||||
|
excludeAcceptAllOption: true,
|
||||||
|
types: [
|
||||||
|
{ accept: { 'application/json': ['.json'] }, description: 'JSON' },
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
console.log('alt', file);
|
return [handle, path] as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
const file = findFile(tree(), localEntry.id);
|
const { handle, path } = findFile(tree(), localEntry.id) ?? {};
|
||||||
const fileExists = file !== undefined;
|
|
||||||
|
|
||||||
console.log(key, file?.path.join('.'));
|
return [handle, path] as const;
|
||||||
|
})();
|
||||||
|
|
||||||
const fileLocalKey = key.slice(file?.path.join('.'));
|
console.log(k, path.join('.'));
|
||||||
|
|
||||||
const result = match([fileExists, mutation.kind])
|
const createNewFile = async (lang: string, directory: FileSystemDirectoryHandle) => {
|
||||||
.with([true, MutarionKind.Create], () => ({ action: MutarionKind.Create, key, value: rows[key][lang], file: file?.meta }))
|
const handle = await window.showSaveFilePicker({
|
||||||
|
suggestedName: `${lang}.json`,
|
||||||
|
startIn: directory,
|
||||||
|
excludeAcceptAllOption: true,
|
||||||
|
types: [
|
||||||
|
{ accept: { 'application/json': ['.json'] }, description: 'JSON' },
|
||||||
|
]
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const key = k.slice(path.join('.').length);
|
||||||
|
const { handle, path } = findFile(tree(), localEntry.id) ?? {};
|
||||||
|
|
||||||
|
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([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) {
|
||||||
|
|
7
src/routes/editor.module.css
Normal file
7
src/routes/editor.module.css
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
.layout {
|
||||||
|
display: grid;
|
||||||
|
grid: auto minmax(0, 1fr) / 100%;
|
||||||
|
inline-size: 100%;
|
||||||
|
block-size: 100%;
|
||||||
|
overflow: clip;
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue