upend/webui/src/routes/browse/[addresses]/+page.svelte

198 lines
5.0 KiB
Svelte

<script lang="ts">
import BrowseAdd from '$lib/components/BrowseAdd.svelte';
import BrowseColumn from '$lib/components/BrowseColumn.svelte';
import { updateTitle } from '$lib/util/title';
import EntitySelect, { selected } from '$lib/components/EntitySelect.svelte';
import SelectedColumn from '$lib/components/SelectedColumn.svelte';
import Inspect from '$lib/components/Inspect.svelte';
import CombineColumn from '$lib/components/CombineColumn.svelte';
import GroupColumn from '$lib/components/GroupColumn.svelte';
import SurfaceColumn from '$lib/components/SurfaceColumn.svelte';
import type { SelectorValue } from '$lib/components/utils/Selector.svelte';
import { page } from '$app/stores';
import { goto } from '$app/navigation';
let root: HTMLDivElement;
let identities: string[] = [];
$: addresses = $page.params.addresses.split(',');
function add(value: SelectorValue) {
if (value.t !== 'Address') return;
const address = value.c;
let _addresses = addresses.concat();
_addresses.push(address);
goto(`/browse/${_addresses.join(',')}`);
}
function close(idx: number) {
let _addresses = addresses.concat();
_addresses.splice(idx, 1);
if (_addresses.length) {
goto(`/browse/${_addresses.join(',')}`);
} else {
goto('/');
}
}
$: scrollToVisible(addresses.length - 1);
function scrollToVisible(idx: string | number) {
const target = root?.querySelector(`[data-index='${idx}']`)?.firstElementChild as HTMLElement;
if (target) {
root?.scrollTo({
left: target.offsetLeft,
behavior: 'smooth'
});
}
}
function onIdentified(index: number, ev: CustomEvent<string[]>) {
for (let i = 0; i < addresses.length; i++) {
if (!identities[i]) {
identities[i] = '?';
}
identities[index] = ev.detail.join('/');
}
identities = identities;
}
let detailMode = false;
function onDetailChanged(index: number, ev: CustomEvent<boolean>) {
if (ev.detail) {
scrollToVisible(index);
}
detailMode = addresses.length === 1 && ev.detail;
}
$: only = addresses.length === 1 && !$selected.length;
function updateAddress(index: number, address: string) {
let _addresses = addresses.concat();
_addresses[index] = address;
goto(`/browse/${_addresses.join(',')}`);
}
$: if ($selected.length && !addresses.includes('selected')) {
let _addresses = addresses.concat();
_addresses.push('selected');
goto(`/browse/${_addresses.join(',')}`);
}
function addCombine(address: string) {
let _addresses = addresses.concat();
_addresses.push(`+${address}`);
goto(`/browse/${_addresses.join(',')}`);
}
$: updateTitle('Browse', identities.join(' / '));
</script>
<div class="browser" bind:this={root} class:single={addresses.length === 1 && detailMode}>
{#each addresses as address, index}
<div class="column" data-index={index}>
{#if ['+', '-'].some((c) => address.includes(c))}
<BrowseColumn
{index}
{only}
on:close={() => close(index)}
on:detail={(ev) => onDetailChanged(index, ev)}
>
<CombineColumn spec={address} on:close={() => close(index)} />
</BrowseColumn>
{:else if address === 'selected'}
<BrowseColumn
{index}
{only}
on:close={() => {
selected.set([]);
close(index);
}}
on:detail={(ev) => onDetailChanged(index, ev)}
>
<SelectedColumn />
</BrowseColumn>
{:else if address === 'groups'}
<BrowseColumn
{index}
{only}
on:close={() => close(index)}
on:detail={(ev) => onDetailChanged(index, ev)}
>
<GroupColumn />
</BrowseColumn>
{:else if address.startsWith('surface')}
<BrowseColumn
{index}
{only}
on:close={() => close(index)}
on:detail={(ev) => onDetailChanged(index, ev)}
>
{@const x = address.split(':')[1].split(';')[0]}
{@const y = address.split(':')[1].split(';')[1]}
<SurfaceColumn
x={x !== '_' ? x : undefined}
y={y !== '_' ? y : undefined}
on:updateAddress={(ev) =>
updateAddress(
index,
`surface:${[ev.detail.x, ev.detail.y].map((v) => v || '_').join(';')}`
)}
/>
</BrowseColumn>
{:else}
<BrowseColumn
{address}
{index}
{only}
on:close={() => close(index)}
on:resolved={(ev) => onIdentified(index, ev)}
on:detail={(ev) => onDetailChanged(index, ev)}
on:combine={() => addCombine(address)}
let:detail
>
<Inspect {address} {detail} on:resolved on:close />
</BrowseColumn>
{/if}
</div>
{/each}
{#if !detailMode}
{#key addresses}
<div class="column" data-index="add">
<BrowseAdd on:input={(ev) => add(ev.detail)} on:editable={() => scrollToVisible('add')} />
</div>
{/key}
{/if}
</div>
<EntitySelect />
<style lang="scss">
.browser {
height: 100%;
padding: 1rem;
display: flex;
overflow-x: auto;
gap: 1rem;
@media screen and (max-width: 600px) {
scroll-snap-type: x mandatory;
}
}
.column {
display: flex;
justify-content: center;
scroll-snap-align: center;
}
.browser.single {
justify-content: center;
.column {
flex-grow: 1;
}
}
</style>