refactor dataset to standalone feature and update components accordingly
This commit is contained in:
parent
3bd17306f2
commit
9ace9b9f4f
7 changed files with 109 additions and 34 deletions
|
@ -1,6 +1,7 @@
|
|||
import { Accessor, createContext, createEffect, createMemo, createSignal, JSX, useContext } from "solid-js";
|
||||
import { Mutation } from "~/utilities";
|
||||
import { SelectionMode, Table, Column as TableColumn, TableApi, DataSet, CellRenderer as TableCellRenderer } from "~/components/table";
|
||||
import { SelectionMode, Table, Column as TableColumn, TableApi, CellRenderer as TableCellRenderer } from "~/components/table";
|
||||
import { DataSet } from "~/features/dataset";
|
||||
import css from './grid.module.css';
|
||||
|
||||
export interface CellRenderer<T extends Record<string, any>, K extends keyof T> {
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
|
||||
export type { Column, TableApi, CellRenderer, CellRenderers } from './table';
|
||||
export type { DataSet, DataSetGroupNode, DataSetRowNode, DataSetNode, SortingFunction, SortOptions, GroupingFunction, GroupOptions } from './dataset';
|
||||
export { SelectionMode, Table } from './table';
|
||||
export { createDataSet } from './dataset';
|
|
@ -1,6 +1,6 @@
|
|||
import { Accessor, createContext, createEffect, createMemo, createSignal, For, JSX, Match, Show, Switch, useContext } from "solid-js";
|
||||
import { selectable, SelectionItem, SelectionProvider, useSelection } from "~/features/selectable";
|
||||
import { DataSetRowNode, DataSetNode, DataSet } from './dataset';
|
||||
import { DataSetRowNode, DataSetNode, DataSet } from '~/features/dataset';
|
||||
import { FaSolidAngleDown, FaSolidSort, FaSolidSortDown, FaSolidSortUp } from "solid-icons/fa";
|
||||
import css from './table.module.css';
|
||||
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
import { describe, expect, it } from "bun:test";
|
||||
import { createDataSet } from "./dataset";
|
||||
import { createDataSet } from "./index";
|
||||
import { createSignal } from "solid-js";
|
||||
|
||||
interface DataEntry {
|
||||
id: string;
|
||||
name: string;
|
||||
amount: number;
|
||||
};
|
||||
const defaultData: DataEntry[] = [
|
||||
const [defaultData] = createSignal<DataEntry[]>([
|
||||
{ id: '1', name: 'a first name', amount: 30 },
|
||||
{ id: '2', name: 'a second name', amount: 20 },
|
||||
{ id: '3', name: 'a third name', amount: 10 },
|
||||
];
|
||||
]);
|
||||
|
||||
describe('dataset', () => {
|
||||
describe('createDataset', () => {
|
|
@ -1,8 +1,7 @@
|
|||
import { Accessor, createEffect, createMemo } from "solid-js";
|
||||
import { createStore, NotWrappable, produce, StoreSetter, unwrap } from "solid-js/store";
|
||||
import { Accessor, createEffect, createMemo, untrack } from "solid-js";
|
||||
import { createStore, produce } from "solid-js/store";
|
||||
import { CustomPartial } from "solid-js/store/types/store.js";
|
||||
import { deepCopy, deepDiff, Mutation } from "~/utilities";
|
||||
|
||||
import { deepCopy, deepDiff, MutarionKind, Mutation } from "~/utilities";
|
||||
|
||||
export type DataSetRowNode<K, T> = { kind: 'row', key: K, value: T }
|
||||
export type DataSetGroupNode<K, T> = { kind: 'group', key: K, groupedBy: keyof T, nodes: DataSetNode<K, T>[] };
|
||||
|
@ -37,7 +36,6 @@ export type Setter<T> =
|
|||
| ((prevState: T) => T | CustomPartial<T>);
|
||||
|
||||
export interface DataSet<T extends Record<string, any>> {
|
||||
data: T[];
|
||||
nodes: Accessor<DataSetNode<keyof T, T>[]>;
|
||||
mutations: Accessor<Mutation[]>;
|
||||
readonly value: (T | undefined)[];
|
||||
|
@ -59,10 +57,10 @@ function defaultGroupingFunction<T>(groupBy: keyof T): GroupingFunction<number,
|
|||
.map(([key, nodes]) => ({ kind: 'group', key, groupedBy: groupBy, nodes: nodes! } as DataSetGroupNode<K, T>));
|
||||
}
|
||||
|
||||
export const createDataSet = <T extends Record<string, any>>(data: T[], initialOptions?: { sort?: SortOptions<T>, group?: GroupOptions<T> }): DataSet<T> => {
|
||||
export const createDataSet = <T extends Record<string, any>>(data: Accessor<T[]>, initialOptions?: { sort?: SortOptions<T>, group?: GroupOptions<T> }): DataSet<T> => {
|
||||
const [state, setState] = createStore<DataSetState<T>>({
|
||||
value: deepCopy(data),
|
||||
snapshot: data,
|
||||
value: deepCopy(data()),
|
||||
snapshot: data(),
|
||||
sorting: initialOptions?.sort,
|
||||
grouping: initialOptions?.group,
|
||||
});
|
||||
|
@ -101,8 +99,79 @@ export const createDataSet = <T extends Record<string, any>>(data: T[], initialO
|
|||
return deepDiff(state.snapshot, state.value).toArray();
|
||||
});
|
||||
|
||||
const apply = (data: T[], mutations: Mutation[]) => {
|
||||
for (const mutation of mutations) {
|
||||
const path = mutation.key.split('.');
|
||||
|
||||
switch (mutation.kind) {
|
||||
case MutarionKind.Create: {
|
||||
let v: any = data;
|
||||
for (const part of path.slice(0, -1)) {
|
||||
if (v[part] === undefined) {
|
||||
v[part] = {};
|
||||
}
|
||||
|
||||
v = v[part];
|
||||
}
|
||||
|
||||
v[path.at(-1)!] = mutation.value;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case MutarionKind.Delete: {
|
||||
let v: any = data;
|
||||
for (const part of path.slice(0, -1)) {
|
||||
if (v === undefined) {
|
||||
break;
|
||||
}
|
||||
|
||||
v = v[part];
|
||||
}
|
||||
|
||||
if (v !== undefined) {
|
||||
delete v[path.at(-1)!];
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case MutarionKind.Update: {
|
||||
let v: any = data;
|
||||
for (const part of path.slice(0, -1)) {
|
||||
if (v === undefined) {
|
||||
break;
|
||||
}
|
||||
|
||||
v = v[part];
|
||||
}
|
||||
|
||||
if (v !== undefined) {
|
||||
v[path.at(-1)!] = mutation.value;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
createEffect(() => {
|
||||
const next = data();
|
||||
const nextValue = apply(deepCopy(next), untrack(() => mutations()));
|
||||
|
||||
setState('value', nextValue);
|
||||
setState('snapshot', next);
|
||||
;
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
console.log('dataset', mutations());
|
||||
});
|
||||
|
||||
const set: DataSet<T> = {
|
||||
data,
|
||||
nodes,
|
||||
get value() {
|
||||
return state.value;
|
||||
|
@ -148,5 +217,5 @@ export const createDataSet = <T extends Record<string, any>>(data: T[], initialO
|
|||
},
|
||||
};
|
||||
|
||||
return set
|
||||
return set;
|
||||
};
|
|
@ -1,7 +1,7 @@
|
|||
import { Accessor, Component, createEffect, createMemo, createSignal, JSX } from "solid-js";
|
||||
import { Accessor, Component, createEffect, createMemo, createSignal, JSX, untrack } from "solid-js";
|
||||
import { decode, Mutation } from "~/utilities";
|
||||
import { Column, GridApi as GridCompApi, Grid as GridComp } from "~/components/grid";
|
||||
import { createDataSet, DataSetNode, DataSetRowNode } from "~/components/table";
|
||||
import { createDataSet, DataSetNode, DataSetRowNode } from "~/features/dataset";
|
||||
import { SelectionItem } from "../selectable";
|
||||
import { useI18n } from "../i18n";
|
||||
import { debounce } from "@solid-primitives/scheduled";
|
||||
|
@ -35,7 +35,7 @@ export function Grid(props: { class?: string, rows: Entry[], locales: string[],
|
|||
const { t } = useI18n();
|
||||
|
||||
const [addedLocales, setAddedLocales] = createSignal<string[]>([]);
|
||||
const rows = createMemo(() => createDataSet<Entry>(props.rows, { group: { by: 'key', with: groupBy } }));
|
||||
const rows = createDataSet<Entry>(() => props.rows, { group: { by: 'key', with: groupBy } });
|
||||
const locales = createMemo(() => [...props.locales, ...addedLocales()]);
|
||||
const columns = createMemo<Column<Entry>[]>(() => [
|
||||
{
|
||||
|
@ -47,7 +47,7 @@ export function Grid(props: { class?: string, rows: Entry[], locales: string[],
|
|||
id: lang,
|
||||
label: lang,
|
||||
renderer: ({ row, column, value, mutate }) => {
|
||||
const entry = rows().value[row]!;
|
||||
const entry = rows.value[row]!;
|
||||
|
||||
return <TextArea row={row} key={entry.key} lang={String(column)} value={value ?? ''} oninput={e => mutate(e.data ?? '')} />;
|
||||
},
|
||||
|
@ -56,22 +56,28 @@ export function Grid(props: { class?: string, rows: Entry[], locales: string[],
|
|||
|
||||
const [api, setApi] = createSignal<GridCompApi<Entry>>();
|
||||
|
||||
// Normalize dataset in order to make sure all the files have the correct structure
|
||||
createEffect(() => {
|
||||
const r = rows();
|
||||
const l = addedLocales();
|
||||
// For tracking
|
||||
props.rows
|
||||
const value = untrack(() => rows.value);
|
||||
|
||||
r.mutateEach(({ key, ...rest }) => ({ key, ...rest, ...Object.fromEntries(l.map(locale => [locale, rest[locale] ?? ''])) }));
|
||||
rows.mutateEach(({ key, ...locales }) => ({ key, ...Object.fromEntries(Object.entries(locales).map(([locale, value]) => [locale, value ?? ''])) }))
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
const r = rows();
|
||||
const l = addedLocales();
|
||||
|
||||
rows.mutateEach(({ key, ...rest }) => ({ key, ...rest, ...Object.fromEntries(l.map(locale => [locale, rest[locale] ?? ''])) }));
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
props.api?.({
|
||||
mutations: r.mutations,
|
||||
mutations: rows.mutations,
|
||||
selection: createMemo(() => api()?.selection() ?? []),
|
||||
remove: r.remove,
|
||||
remove: rows.remove,
|
||||
addKey(key) {
|
||||
r.insert({ key, ...Object.fromEntries(locales().map(l => [l, ''])) });
|
||||
rows.insert({ key, ...Object.fromEntries(locales().map(l => [l, ''])) });
|
||||
},
|
||||
addLocale(locale) {
|
||||
setAddedLocales(locales => new Set([...locales, locale]).values().toArray())
|
||||
|
@ -85,7 +91,7 @@ export function Grid(props: { class?: string, rows: Entry[], locales: string[],
|
|||
});
|
||||
});
|
||||
|
||||
return <GridComp data={rows()} columns={columns()} api={setApi} />;
|
||||
return <GridComp data={rows} columns={columns()} api={setApi} />;
|
||||
};
|
||||
|
||||
const TextArea: Component<{ row: number, key: string, lang: string, value: string, oninput?: (event: InputEvent) => any }> = (props) => {
|
||||
|
|
|
@ -40,8 +40,6 @@ async function* walk(directory: FileSystemDirectoryHandle, path: string[] = []):
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
// interface Entries extends Map<string, Record<string, { value: string, handle: FileSystemFileHandle, id: string }>> { };
|
||||
interface Entries extends Map<string, { key: string, } & Record<string, { value: string, handle: FileSystemFileHandle, id: string }>> { };
|
||||
|
||||
export default function Edit(props: ParentProps) {
|
||||
|
@ -123,7 +121,7 @@ const Editor: Component<{ root: FileSystemDirectoryHandle }> = (props) => {
|
|||
}
|
||||
|
||||
const entry = entries.get(index as any)!;
|
||||
return { kind: MutarionKind.Create, key: entry.key, lang, file: undefined, value: m.value };
|
||||
return { kind: MutarionKind.Create, key: entry.key, lang, file: files.get(lang), value: m.value };
|
||||
}
|
||||
|
||||
case MutarionKind.Delete: {
|
||||
|
@ -148,6 +146,8 @@ const Editor: Component<{ root: FileSystemDirectoryHandle }> = (props) => {
|
|||
}
|
||||
|
||||
const groupedByFileId = Object.groupBy(muts, m => m.file?.id ?? 'undefined');
|
||||
|
||||
console.log(files, muts, groupedByFileId);
|
||||
const newFiles = Object.entries(Object.groupBy((groupedByFileId['undefined'] ?? []) as (Created & { lang: string, file: undefined })[], m => m.lang)).map(([lang, mutations]) => {
|
||||
const data = mutations!.reduce((aggregate, { key, value }) => {
|
||||
let obj = aggregate;
|
||||
|
@ -387,7 +387,7 @@ const Content: Component<{ directory: FileSystemDirectoryHandle, api?: Setter<Gr
|
|||
const [contents] = createResource(() => files.latest, (files) => Promise.all(Object.entries(files).map(async ([id, { file, handle }]) => ({ id, handle, lang: file.name.split('.').at(0)!, entries: (await load(file))! }))), { initialValue: [] });
|
||||
|
||||
const [entries, rows] = destructure(() => {
|
||||
const template = contents.latest.map(({ lang, handle }) => [lang, { handle, value: '' }]);
|
||||
const template = contents.latest.map(({ lang, handle }) => [lang, { handle, value: null }]);
|
||||
const merged = contents.latest.reduce((aggregate, { id, handle, lang, entries }) => {
|
||||
for (const [key, value] of entries.entries()) {
|
||||
if (!aggregate.has(key)) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue