feat: polish

This commit is contained in:
Anatoly Antonov 2026-04-22 01:27:38 +09:00
parent 2e6177fe74
commit 4bd927bb4e
137 changed files with 6357 additions and 137560 deletions

View file

@ -0,0 +1,140 @@
<script lang="ts">
import { onMount } from 'svelte';
import { FormGroup, Label, Input } from '@sveltestrap/sveltestrap';
import type { SavedPoint } from '$domain';
import { pointsApi } from '$api';
import { addToast, ListEditor } from '$ui';
import type { ListEditorApi, ListEditorConfig } from '$ui';
import { t } from '$i18n';
import { pointsStore } from './pointsStore';
interface Props {
isOpen?: boolean;
onClose?: () => void;
onSave?: (p: SavedPoint) => void;
onSelectPoint?: (p: SavedPoint | null) => void;
point?: SavedPoint | null;
}
let {
isOpen = $bindable(false),
onClose = () => {},
onSave = (_: SavedPoint) => {},
onSelectPoint = (_: SavedPoint | null) => {},
point = null,
}: Props = $props();
let editorRef: ListEditor<SavedPoint> | null = $state(null);
onMount(async () => {
if ($pointsStore.length === 0) {
try {
pointsStore.set(await pointsApi.list());
} catch (err: unknown) {
addToast({
header: $t('common.error'),
body: (err as Error).message,
color: 'danger',
});
}
}
});
$effect(() => {
if (point && editorRef) editorRef.open(point);
});
const config: ListEditorConfig = {
showTable: true,
closeOnSave: false,
closeOnDelete: false,
searchBy: ['name'],
labels: {
item: 'Point',
itemGenitive: 'point',
items: 'Points',
add: 'Add',
edit: 'Edit',
save: 'Save',
update: 'Update',
delete: 'Delete',
cancel: 'Cancel',
close: 'Close',
searchPlaceholder: 'Search by name...',
},
};
const api: ListEditorApi<SavedPoint> = {
save: (p) => pointsApi.create(p),
update: (p) => pointsApi.update(p),
delete: (p) => pointsApi.delete(p.id),
};
const factory = (): SavedPoint => ({ id: 0, name: '', lat: 0, lon: 0, alt: 0 });
let items = $state<SavedPoint[]>($pointsStore);
$effect(() => {
items = $pointsStore;
});
$effect(() => {
pointsStore.set(items);
});
export function open(p: SavedPoint | null = null, showTable: boolean = config.showTable ?? true) {
editorRef?.open(p, showTable);
}
</script>
<ListEditor
bind:this={editorRef}
bind:isOpen
bind:items
{api}
{config}
itemFactory={factory}
onClose={() => onClose()}
onSave={(p) => onSave(p)}
onSelect={(p) => onSelectPoint(p)}>
{#snippet tableHeader()}
<tr>
<th>{$t('points.name')}</th>
<th>{$t('points.lat')}</th>
<th>{$t('points.lon')}</th>
<th>{$t('points.alt')}</th>
<th class="fit"></th>
</tr>
{/snippet}
{#snippet tableRow({ row })}
<td>{row.name}</td>
<td>{row.lat.toFixed(5)} °</td>
<td>{row.lon.toFixed(5)} °</td>
<td>{row.alt} м</td>
{/snippet}
{#snippet formFields({ item })}
<div class="mb-2">
<Label class="small">{$t('points.name')}</Label>
<Input class="form-control-sm" type="text" bind:value={item.name} required />
</div>
<div class="d-flex gap-2">
<FormGroup class="flex-grow-1">
<Label class="small">{$t('points.lat')}</Label>
<Input class="form-control-sm" type="number" step="any" bind:value={item.lat} required />
<span class="form-text">{$t('points.degrees')}</span>
</FormGroup>
<FormGroup class="flex-grow-1">
<Label class="small">{$t('points.lon')}</Label>
<Input class="form-control-sm" type="number" step="any" bind:value={item.lon} required />
<span class="form-text">{$t('points.degrees')}</span>
</FormGroup>
<FormGroup class="flex-grow-1">
<Label class="small">{$t('points.alt')}</Label>
<Input class="form-control-sm" type="number" step="any" bind:value={item.alt} required />
<span class="form-text">{$t('points.metersAsl')}</span>
</FormGroup>
</div>
{/snippet}
</ListEditor>