diff --git a/src/components/grid/grid.tsx b/src/components/grid/grid.tsx index 38b5c85..ad3f036 100644 --- a/src/components/grid/grid.tsx +++ b/src/components/grid/grid.tsx @@ -32,13 +32,12 @@ const GridContext = createContext>(); const useGrid = () => useContext(GridContext)!; type GridProps> = { class?: string, groupBy?: keyof T, columns: Column[], rows: DataSet, api?: (api: GridApi) => any }; -// type GridState> = { data: DataSet, columns: Column[], numberOfRows: number }; export function Grid>(props: GridProps) { const [table, setTable] = createSignal>(); const rows = createMemo(() => props.rows); - const columns = createMemo(() => props.columns); + const columns = createMemo(() => props.columns as TableColumn[]); const mutations = createMemo(() => rows().mutations()); const ctx: GridContextType = { diff --git a/src/components/table/table.tsx b/src/components/table/table.tsx index 70178f1..ff6b21f 100644 --- a/src/components/table/table.tsx +++ b/src/components/table/table.tsx @@ -19,7 +19,7 @@ export interface Column> { }; export interface TableApi> { - readonly selection: Accessor[]>; + readonly selection: Accessor[]>; readonly rows: Accessor>; readonly columns: Accessor[]>; selectAll(): void; @@ -29,7 +29,7 @@ export interface TableApi> { interface TableContextType> { readonly rows: Accessor>, readonly columns: Accessor[]>, - readonly selection: Accessor[]>, + readonly selection: Accessor[]>, readonly selectionMode: Accessor, readonly cellRenderers: Accessor>, } @@ -54,7 +54,7 @@ type TableProps> = { }; export function Table>(props: TableProps) { - const [selection, setSelection] = createSignal[]>([]); + const [selection, setSelection] = createSignal[]>([]); const rows = createMemo(() => props.rows); const columns = createMemo[]>(() => props.columns ?? []); @@ -97,7 +97,7 @@ function InnerTable>(props: InnerTableProps) { - { + []}>{ node => } @@ -114,7 +114,7 @@ function InnerTable>(props: InnerTableProps) { function Api>(props: { api: undefined | ((api: TableApi) => any) }) { const table = useTable(); - const selectionContext = useSelection(); + const selectionContext = useSelection(); const api: TableApi = { selection: selectionContext.selection, @@ -202,7 +202,7 @@ function Head(props: {}) { ; }; -function Node>(props: { node: DataSetNode, depth: number, groupedBy?: keyof T }) { +function Node>(props: { node: DataSetNode, depth: number, groupedBy?: keyof T }) { return { row => @@ -214,9 +214,9 @@ function Node>(props: { node: DataSetNode; } -function Row>(props: { key: keyof T, value: T, depth: number, groupedBy?: keyof T }) { +function Row>(props: { key: K, value: T, depth: number, groupedBy?: keyof T }) { const table = useTable(); - const context = useSelection(); + const context = useSelection(); const columns = table.columns; const isSelected = context.isSelected(props.key); @@ -239,7 +239,7 @@ function Row>(props: { key: keyof T, value: T, dep ; }; -function Group>(props: { key: keyof T, groupedBy: keyof T, nodes: DataSetNode[], depth: number }) { +function Group>(props: { key: K, groupedBy: keyof T, nodes: DataSetNode[], depth: number }) { const table = useTable(); return diff --git a/src/features/file/grid.tsx b/src/features/file/grid.tsx index e72de38..43f933b 100644 --- a/src/features/file/grid.tsx +++ b/src/features/file/grid.tsx @@ -2,11 +2,13 @@ import { Accessor, Component, createEffect, createMemo, createSignal } from "sol import { debounce, Mutation } from "~/utilities"; import { Column, GridApi as GridCompApi, Grid as GridComp } from "~/components/grid"; import { createDataSet, DataSetNode, DataSetRowNode } from "~/components/table"; +import { SelectionItem } from "../selectable"; import css from "./grid.module.css" export type Entry = { key: string } & { [lang: string]: string }; export interface GridApi { readonly mutations: Accessor; + readonly selection: Accessor[]>; remove(indices: number[]): void; addKey(key: string): void; addLocale(locale: string): void; @@ -25,9 +27,9 @@ const groupBy = (rows: DataSetRowNode[]) => { return group(rows.map(r => ({ ...r, _key: r.value.key }))) as any; } -export function Grid(props: { class?: string, rows: Entry[], api?: (api: GridApi) => any }) { +export function Grid(props: { class?: string, rows: Entry[], locales: string[], api?: (api: GridApi) => any }) { const rows = createMemo(() => createDataSet(props.rows, { group: { by: 'key', with: groupBy } })); - const locales = createMemo(() => Object.keys(rows().value().at(0) ?? {}).filter(k => k !== 'key')); + const locales = createMemo(() => props.locales); const columns = createMemo[]>(() => [ { id: 'key', @@ -45,11 +47,14 @@ export function Grid(props: { class?: string, rows: Entry[], api?: (api: GridApi })) ]); + const [api, setApi] = createSignal>(); + createEffect(() => { const r = rows(); props.api?.({ mutations: r.mutations, + selection: createMemo(() => api()?.selection() ?? []), remove: r.remove, addKey(key) { r.insert({ key, ...Object.fromEntries(locales().map(l => [l, ''])) }); @@ -60,7 +65,7 @@ export function Grid(props: { class?: string, rows: Entry[], api?: (api: GridApi }); }); - return ; + return ; }; const TextArea: Component<{ row: number, key: string, lang: string, value: string, oninput?: (event: InputEvent) => any }> = (props) => { diff --git a/src/features/selectable/index.tsx b/src/features/selectable/index.tsx index 1c02b12..dfecb4e 100644 --- a/src/features/selectable/index.tsx +++ b/src/features/selectable/index.tsx @@ -22,48 +22,48 @@ export interface SelectionItem { element: WeakRef; }; -export interface SelectionContextType { - readonly selection: Accessor[]>; +export interface SelectionContextType { + readonly selection: Accessor[]>; readonly length: Accessor; - select(selection: (keyof T)[], options?: Partial<{ mode: SelectionMode }>): void; + select(selection: K[], options?: Partial<{ mode: SelectionMode }>): void; selectAll(): void; clear(): void; - isSelected(key: keyof T): Accessor; + isSelected(key: K): Accessor; } -interface InternalSelectionContextType { +interface InternalSelectionContextType { readonly latest: Signal, readonly modifier: Signal, readonly selectables: Signal, - readonly keyMap: Map, - add(key: keyof T, value: Accessor, element: HTMLElement): string; + readonly keyMap: Map, + add(key: K, value: Accessor, element: HTMLElement): string; } export interface SelectionHandler { (selection: T[]): any; } -const SelectionContext = createContext>(); -const InternalSelectionContext = createContext>(); +const SelectionContext = createContext>(); +const InternalSelectionContext = createContext>(); -export function useSelection(): SelectionContextType { +export function useSelection() { const context = useContext(SelectionContext); if (context === undefined) { throw new Error('selection context is used outside of a provider'); } - return context as SelectionContextType; + return context as SelectionContextType; }; -function useInternalSelection() { - return useContext(InternalSelectionContext)! as InternalSelectionContextType; +function useInternalSelection() { + return useContext(InternalSelectionContext)! as InternalSelectionContextType; } -interface State { - selection: (keyof T)[]; - data: SelectionItem[]; +interface State { + selection: K[]; + data: SelectionItem[]; } -export function SelectionProvider(props: ParentProps<{ selection?: SelectionHandler, multiSelect?: boolean }>) { - const [state, setState] = createStore>({ selection: [], data: [] }); +export function SelectionProvider(props: ParentProps<{ selection?: SelectionHandler, multiSelect?: boolean }>) { + const [state, setState] = createStore>({ selection: [], data: [] }); const selection = createMemo(() => state.data.filter(({ key }) => state.selection.includes(key))); const length = createMemo(() => state.data.length); @@ -71,7 +71,7 @@ export function SelectionProvider(props: ParentProps<{ selecti props.selection?.(selection().map(({ value }) => value())); }); - const context: SelectionContextType = { + const context: SelectionContextType = { selection, length, select(selection, { mode = SelectionMode.Normal } = {}) { @@ -106,9 +106,9 @@ export function SelectionProvider(props: ParentProps<{ selecti }, }; - const keyIdMap = new Map(); - const idKeyMap = new Map(); - const internal: InternalSelectionContextType = { + const keyIdMap = new Map(); + const idKeyMap = new Map(); + const internal: InternalSelectionContextType = { modifier: createSignal(Modifier.None), latest: createSignal(), selectables: createSignal([]), @@ -201,9 +201,9 @@ const Root: ParentComponent = (props) => { return
{c()}
; }; -export function selectable(element: HTMLElement, options: Accessor<{ value: T, key: keyof T }>) { - const context = useSelection(); - const internal = useInternalSelection(); +export function selectable(element: HTMLElement, options: Accessor<{ value: T, key: K }>) { + const context = useSelection(); + const internal = useInternalSelection(); const key = options().key; const value = createMemo(() => options().value); @@ -211,17 +211,17 @@ export function selectable(element: HTMLElement, options: Acce const selectionKey = internal.add(key, value, element); - const createRange = (a?: HTMLElement, b?: HTMLElement): (keyof T)[] => { + const createRange = (a?: HTMLElement, b?: HTMLElement): K[] => { if (!a && !b) { return []; } if (!a) { - return [b!.dataset.selecatableKey! as keyof T]; + return [b!.dataset.selecatableKey! as K]; } if (!b) { - return [a!.dataset.selecatableKey! as keyof T]; + return [a!.dataset.selecatableKey! as K]; } if (a === b) { diff --git a/src/routes/(editor)/edit.tsx b/src/routes/(editor)/edit.tsx index 593dcda..45601f4 100644 --- a/src/routes/(editor)/edit.tsx +++ b/src/routes/(editor)/edit.tsx @@ -125,7 +125,8 @@ const Editor: Component<{ root: FileSystemDirectoryHandle }> = (props) => { } case MutarionKind.Delete: { - return files.values().map(file => ({ kind: MutarionKind.Delete, key: m.key, file })).toArray(); + const entry = entries.get(index as any)!; + return files.values().map(file => ({ kind: MutarionKind.Delete, key: entry.key, file })).toArray(); } default: throw new Error('unreachable code'); @@ -283,7 +284,7 @@ const Editor: Component<{ root: FileSystemDirectoryHandle }> = (props) => { return; } - remove(Object.keys(selection())); + remove(selection().map(s => s.key)); }, { key: 'delete', modifier: Modifier.None }), inserNewKey: createCommand('insert new key', async () => { const formData = await newKeyPrompt()?.showModal(); @@ -380,6 +381,7 @@ const Editor: Component<{ root: FileSystemDirectoryHandle }> = (props) => { const Content: Component<{ directory: FileSystemDirectoryHandle, api?: Setter, entries?: Setter }> = (props) => { const [entries, setEntries] = createSignal(new Map()); + const [locales, setLocales] = createSignal([]); const [rows, setRows] = createSignal([]); const [api, setApi] = createSignal(); @@ -412,6 +414,8 @@ const Content: Component<{ directory: FileSystemDirectoryHandle, api?: Setter [lang, { handle, value: '' }]); + setLocales(contents.map(({ lang }) => lang)); + const merged = contents.reduce((aggregate, { id, handle, lang, entries }) => { for (const [key, value] of entries.entries()) { if (!aggregate.has(key)) { @@ -429,7 +433,7 @@ const Content: Component<{ directory: FileSystemDirectoryHandle, api?: Setter; + return ; }; const Blank: Component<{ open: CommandType }> = (props) => {