fix(webui): Notes aren't duplicated (manifested as unreliable saving)

also rework semantics of `WidgetChange`
feat/tables
Tomáš Mládek 2024-02-06 22:32:10 +01:00
parent f1b608f824
commit 9d6ebfc31c
4 changed files with 64 additions and 79 deletions

View File

@ -233,40 +233,42 @@
async function onChange(ev: CustomEvent<WidgetChange>) { async function onChange(ev: CustomEvent<WidgetChange>) {
dbg('onChange', ev.detail); dbg('onChange', ev.detail);
const change = ev.detail; const changes = Array.isArray(ev.detail) ? ev.detail : [ev.detail];
switch (change.type) { for (const change of changes) {
case 'create': switch (change.type) {
await api.putEntry({ case 'create':
entity: address, await api.putEntry({
attribute: change.attribute, entity: address,
value: change.value attribute: change.attribute,
}); value: change.value
break; });
case 'delete': break;
await api.deleteEntry(change.address); case 'delete':
break; await api.deleteEntry(change.address);
case 'update': break;
await api.putEntityAttribute(address, change.attribute, change.value); case 'upsert':
break; await api.putEntityAttribute(address, change.attribute, change.value);
case 'entry-add': break;
await api.putEntry({ case 'entry-add':
entity: change.address, await api.putEntry({
attribute: ATTR_IN, entity: change.address,
value: { t: 'Address', c: address } attribute: ATTR_IN,
}); value: { t: 'Address', c: address }
break; });
case 'entry-delete': { break;
const inEntry = $entity?.attr[`~${ATTR_IN}`]?.find((e) => e.entity === change.address); case 'entry-delete': {
if (inEntry) { const inEntry = $entity?.attr[`~${ATTR_IN}`]?.find((e) => e.entity === change.address);
await api.deleteEntry(inEntry.address); if (inEntry) {
} else { await api.deleteEntry(inEntry.address);
console.warn("Couldn't find IN entry for entity %s?!", change.address); } else {
console.warn("Couldn't find IN entry for entity %s?!", change.address);
}
break;
} }
break; default:
console.error('Unimplemented AttributeChange', change);
return;
} }
default:
console.error('Unimplemented AttributeChange', change);
return;
} }
revalidate(); revalidate();
} }

View File

@ -2,45 +2,24 @@
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
import { useEntity } from '$lib/entity'; import { useEntity } from '$lib/entity';
import type { AttributeCreate, AttributeUpdate } from '$lib/types/base'; import type { WidgetChange } from '$lib/types/base';
import type { UpEntry } from '@upnd/upend';
import LabelBorder from './LabelBorder.svelte'; import LabelBorder from './LabelBorder.svelte';
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher<{ change: WidgetChange }>();
export let address: string; export let address: string;
$: ({ entity } = useEntity(address)); $: ({ entity } = useEntity(address));
let noteEntry: UpEntry | undefined; $: notes = $entity?.get('NOTE')?.toString();
let notes: string | undefined = undefined;
$: {
if ($entity?.attr['NOTE']?.length && $entity?.attr['NOTE'][0]?.value?.c) {
noteEntry = $entity?.attr['NOTE'][0];
notes = String(noteEntry.value.c);
} else {
noteEntry = undefined;
notes = undefined;
}
}
let contentEl: HTMLDivElement; let contentEl: HTMLDivElement;
const update = debounce(() => { const update = debounce(() => {
if (noteEntry) { dispatch('change', {
dispatch('change', { type: 'upsert',
type: 'update', attribute: 'NOTE',
address: noteEntry.address, value: { t: 'String', c: contentEl.innerText }
attribute: 'NOTE', });
value: { t: 'String', c: contentEl.innerText }
} as AttributeUpdate);
} else {
dispatch('change', {
type: 'create',
address: address,
attribute: 'NOTE',
value: { t: 'String', c: contentEl.innerText }
} as AttributeCreate);
}
}, 500); }, 500);
</script> </script>

View File

@ -4,7 +4,7 @@
import Ellipsis from '../utils/Ellipsis.svelte'; import Ellipsis from '../utils/Ellipsis.svelte';
import UpObject from '../display/UpObject.svelte'; import UpObject from '../display/UpObject.svelte';
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
import type { AttributeUpdate, WidgetChange } from '$lib/types/base'; import type { WidgetChange } from '$lib/types/base';
import type { UpEntry, UpListing } from '@upnd/upend'; import type { UpEntry, UpListing } from '@upnd/upend';
import IconButton from '../utils/IconButton.svelte'; import IconButton from '../utils/IconButton.svelte';
import Selector, { type SelectorValue, selectorValueAsValue } from '../utils/Selector.svelte'; import Selector, { type SelectorValue, selectorValueAsValue } from '../utils/Selector.svelte';
@ -18,7 +18,7 @@
import UpLink from '../display/UpLink.svelte'; import UpLink from '../display/UpLink.svelte';
import { ATTR_ADDED, ATTR_LABEL } from '@upnd/upend/constants'; import { ATTR_ADDED, ATTR_LABEL } from '@upnd/upend/constants';
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher<{ change: WidgetChange }>();
export let columns: string | undefined = undefined; export let columns: string | undefined = undefined;
export let header = true; export let header = true;
@ -63,7 +63,7 @@
type: 'create', type: 'create',
attribute, attribute,
value: await selectorValueAsValue(value) value: await selectorValueAsValue(value)
} as WidgetChange); });
newEntryAttribute = ''; newEntryAttribute = '';
newEntryValue = undefined; newEntryValue = undefined;
} }
@ -72,13 +72,18 @@
dispatch('change', { type: 'delete', address } as WidgetChange); dispatch('change', { type: 'delete', address } as WidgetChange);
} }
} }
async function updateEntry(address: string, attribute: string, value: SelectorValue) { async function updateEntry(oldEntry: UpEntry, value: SelectorValue) {
dispatch('change', { dispatch('change', [
type: 'update', {
address, type: 'delete',
attribute, address: oldEntry.address
value: await selectorValueAsValue(value) },
} as AttributeUpdate); {
type: 'create',
attribute: oldEntry.attribute,
value: await selectorValueAsValue(value)
}
]);
} }
// Labelling // Labelling
@ -268,10 +273,7 @@
class="cell value mark-value" class="cell value mark-value"
data-address={entry.value.t === 'Address' ? entry.value.c : undefined} data-address={entry.value.t === 'Address' ? entry.value.c : undefined}
> >
<Editable <Editable value={entry.value} on:edit={(ev) => updateEntry(entry, ev.detail)}>
value={entry.value}
on:edit={(ev) => updateEntry(entry.address, entry.attribute, ev.detail)}
>
{#if entry.value.t === 'Address'} {#if entry.value.t === 'Address'}
<UpObject <UpObject
link link

View File

@ -1,20 +1,22 @@
import type { IValue } from '@upnd/upend/types'; import type { IValue } from '@upnd/upend/types';
export type WidgetChange = export type WidgetOperation =
| AttributeCreate | AttributeCreate
| AttributeUpdate | AttributeUpsert
| AttributeDelete | AttributeDelete
| EntryInAdd | EntryInAdd
| EntryInDelete; | EntryInDelete;
export type WidgetChange = WidgetOperation | WidgetOperation[];
export interface AttributeCreate { export interface AttributeCreate {
type: 'create'; type: 'create';
attribute: string; attribute: string;
value: IValue; value: IValue;
} }
export interface AttributeUpdate { export interface AttributeUpsert {
type: 'update'; type: 'upsert';
attribute: string; attribute: string;
value: IValue; value: IValue;
} }