import { Accessor, Component, createContext, createEffect, createMemo, createSignal, For, onMount, ParentComponent, Show, useContext } from "solid-js"; import { createStore, produce } from "solid-js/store"; import { SelectionProvider, useSelection, selectable } from "../selectable"; import css from './grid.module.css'; selectable // prevents removal of import const debounce = void>(callback: T, delay: number): T => { let handle: ReturnType | undefined; return (...args: any[]) => { if (handle) { clearTimeout(handle); } handle = setTimeout(() => callback(...args), delay); } }; interface Leaf extends Record { } export interface Entry extends Record { } type Rows = Record; export interface GridContextType { readonly rows: Accessor; readonly selection: Accessor; mutate(prop: string, lang: string, value: string): void; } export interface GridApi { readonly rows: Accessor; selectAll(): void; clear(): void; } const GridContext = createContext(); const isLeaf = (entry: Entry | Leaf): entry is Leaf => Object.values(entry).some(v => typeof v === 'string'); const useGrid = () => useContext(GridContext)!; const GridProvider: ParentComponent<{ rows: Map }> = (props) => { const [selection, setSelection] = createSignal([]); const [state, setState] = createStore<{ rows: Rows, numberOfRows: number }>({ rows: {}, numberOfRows: 0, }); createEffect(() => { const rows = props.rows .entries() .map(([prop, entry]) => [prop, Object.fromEntries(Object.entries(entry).map(([lang, { value }]) => [lang, { original: value, value }]))]); setState('rows', Object.fromEntries(rows)); }); createEffect(() => { setState('numberOfRows', Object.keys(state.rows).length); }); const ctx: GridContextType = { rows: createMemo(() => state.rows), selection, mutate(prop: string, lang: string, value: string) { setState('rows', produce(rows => { rows[prop][lang].value = value; })); }, }; return {props.children} ; }; export const Grid: Component<{ class?: string, columns: string[], rows: Map, api?: (api: GridApi) => any }> = (props) => { const columnCount = createMemo(() => props.columns.length - 1); const root = createMemo(() => { return props.rows ?.entries() .map(([key, value]) => [key, Object.fromEntries(Object.entries(value).map(([lang, { value }]) => [lang, value]))] as const) .reduce((aggregate, [key, entry]) => { let obj: any = aggregate; const parts = key.split('.'); for (const [i, part] of parts.entries()) { if (Object.hasOwn(obj, part) === false) { obj[part] = {}; } if (i === (parts.length - 1)) { obj[part] = entry; } else { obj = obj[part]; } } return aggregate; }, {}); }); return
}; const Api: Component<{ api: undefined | ((api: GridApi) => any) }> = (props) => { const gridContext = useGrid(); const selectionContext = useSelection(); const api: GridApi = { rows: gridContext.rows, selectAll() { selectionContext.selectAll(); }, clear() { selectionContext.clear(); }, }; createEffect(() => { props.api?.(api); }); return null; }; const Head: Component<{ headers: string[] }> = (props) => { const context = useSelection(); return
0 && context.selection().length === context.length()} indeterminate={context.selection().length !== 0 && context.selection().length !== context.length()} on:input={(e: InputEvent) => e.target.checked ? context.selectAll() : context.clear()} />
{ header => {header} }
; }; const Row: Component<{ entry: Entry, path?: string[] }> = (props) => { const grid = useGrid(); return { ([key, value]) => { const values = Object.entries(value); const path = [...(props.path ?? []), key]; const k = path.join('.'); const context = useSelection(); const isSelected = context.isSelected(k); return }>
context.select([k], { append: true })} />
{key}
{ ([lang, value]) =>