feat(webui): turn groups view into a column, allow selection
also add `forceDetail` to BrowseColumnrefactor/errors
parent
52098758a1
commit
851b21ce81
|
@ -10,7 +10,6 @@
|
|||
import AddModal from "./components/AddModal.svelte";
|
||||
import Store from "./views/Store.svelte";
|
||||
import Surface from "./views/Surface.svelte";
|
||||
import Groups from "./views/Groups.svelte";
|
||||
|
||||
import "./styles/main.scss";
|
||||
|
||||
|
@ -40,10 +39,6 @@
|
|||
<Store />
|
||||
</Route>
|
||||
|
||||
<Route path="/groups">
|
||||
<Groups />
|
||||
</Route>
|
||||
|
||||
<Footer />
|
||||
|
||||
<AddModal />
|
||||
|
|
|
@ -13,10 +13,11 @@
|
|||
export let index: number;
|
||||
export let only: boolean;
|
||||
export let background = "var(--background-lighter)";
|
||||
export let forceDetail = false;
|
||||
|
||||
let detail = only;
|
||||
let detail = only || forceDetail;
|
||||
let detailChanged = false;
|
||||
$: if (!detailChanged) detail = only;
|
||||
$: if (!detailChanged) detail = only || forceDetail;
|
||||
$: if (detailChanged) tick().then(() => dispatch("detail", detail));
|
||||
|
||||
let indexStore = writable(index);
|
||||
|
@ -75,16 +76,20 @@
|
|||
Detach
|
||||
</IconButton>
|
||||
{/if}
|
||||
<IconButton
|
||||
name={detail ? "zoom-out" : "zoom-in"}
|
||||
on:click={() => {
|
||||
detail = !detail;
|
||||
detailChanged = true;
|
||||
}}
|
||||
active={detail}
|
||||
>
|
||||
Detail
|
||||
</IconButton>
|
||||
{#if !forceDetail}
|
||||
<IconButton
|
||||
name={detail ? "zoom-out" : "zoom-in"}
|
||||
on:click={() => {
|
||||
detail = !detail;
|
||||
detailChanged = true;
|
||||
}}
|
||||
active={detail}
|
||||
>
|
||||
Detail
|
||||
</IconButton>
|
||||
{:else}
|
||||
<div class="noop"></div>
|
||||
{/if}
|
||||
{#if address}
|
||||
<IconButton
|
||||
name="intersect"
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
import { ATTR_IN, ATTR_LABEL } from "@upnd/upend/constants";
|
||||
import api from "../lib/api";
|
||||
import { i18n } from "../i18n";
|
||||
import Spinner from "../components/utils/Spinner.svelte";
|
||||
import UpObject from "../components/display/UpObject.svelte";
|
||||
import Spinner from "./utils/Spinner.svelte";
|
||||
import UpObject from "./display/UpObject.svelte";
|
||||
|
||||
const groups = (async () => {
|
||||
const data = await api.query(`(matches ? "${ATTR_IN}" ?)`);
|
||||
|
@ -57,59 +57,80 @@
|
|||
duplicateGroups: duplicates,
|
||||
};
|
||||
})();
|
||||
|
||||
let clientWidth: number;
|
||||
</script>
|
||||
|
||||
<div class="groups">
|
||||
<h1>{$i18n.t("Groups")}</h1>
|
||||
{#await groups}
|
||||
<Spinner centered />
|
||||
{:then data}
|
||||
<ul>
|
||||
{#each data.groups as group}
|
||||
<li class="group">
|
||||
<UpObject link address={group.address} labels={group.labels} />
|
||||
<div class="count">{group.count}</div>
|
||||
</li>
|
||||
{:else}
|
||||
<li>No groups?</li>
|
||||
{/each}
|
||||
{#if data.groups && data.total > data.groups.length}
|
||||
<li>+ {data.total - data.groups.length}...</li>
|
||||
{/if}
|
||||
</ul>
|
||||
{#if data.duplicateGroups.length > 0}
|
||||
<h2>{$i18n.t("Duplicate groups")}</h2>
|
||||
<div class="groups" bind:clientWidth class:small={clientWidth < 600}>
|
||||
<h2>{$i18n.t("Groups")}</h2>
|
||||
<div class="main">
|
||||
{#await groups}
|
||||
<Spinner centered />
|
||||
{:then data}
|
||||
<ul>
|
||||
{#each data.duplicateGroups as { label, groups }}
|
||||
<li class="duplicate">
|
||||
<div class="label">{label}</div>
|
||||
<ul>
|
||||
{#each groups as group}
|
||||
<li>
|
||||
<UpObject link address={group} backpath={2} />
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{#each data.groups as group}
|
||||
<li class="group" data-address={group.address}>
|
||||
<UpObject link address={group.address} labels={group.labels} />
|
||||
<div class="count">{group.count}</div>
|
||||
</li>
|
||||
{:else}
|
||||
<li>No groups?</li>
|
||||
{/each}
|
||||
{#if data.groups && data.total > data.groups.length}
|
||||
<li>+ {data.total - data.groups.length}...</li>
|
||||
{/if}
|
||||
</ul>
|
||||
{/if}
|
||||
{/await}
|
||||
{#if data.duplicateGroups.length > 0}
|
||||
<h3>{$i18n.t("Duplicate groups")}</h3>
|
||||
<ul class="duplicate">
|
||||
{#each data.duplicateGroups as { label, groups }}
|
||||
<li class="duplicate-group">
|
||||
<div class="label">{label}</div>
|
||||
<ul>
|
||||
{#each groups as group}
|
||||
<li>
|
||||
<UpObject link address={group} backpath={2} />
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
{/await}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@use "../styles/colors";
|
||||
|
||||
.groups {
|
||||
text-align: center;
|
||||
|
||||
flex-grow: 1;
|
||||
height: 0;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.main {
|
||||
overflow: hidden auto;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top: -0.66em;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0 2rem;
|
||||
margin: 0;
|
||||
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5em;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.group {
|
||||
|
@ -128,11 +149,26 @@
|
|||
}
|
||||
|
||||
.duplicate {
|
||||
margin-bottom: 1em;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.duplicate-group {
|
||||
flex-basis: 49%;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--foreground);
|
||||
padding: 1em;
|
||||
|
||||
padding: .5rem;
|
||||
overflow-x: auto;
|
||||
max-width: 100%;
|
||||
|
||||
ul {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.groups.small {
|
||||
ul {
|
||||
flex-direction: column;
|
||||
}
|
|
@ -16,6 +16,7 @@
|
|||
import { i18n } from "../../i18n";
|
||||
import api from "../../lib/api";
|
||||
import { ATTR_IN, ATTR_LABEL, HIER_ROOT_ADDR } from "@upnd/upend/constants";
|
||||
import { selected } from "../EntitySelect.svelte";
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
|
@ -25,6 +26,7 @@
|
|||
export let banner = false;
|
||||
export let resolve = !(labels || []).length || banner;
|
||||
export let backpath = 0;
|
||||
export let select = true;
|
||||
|
||||
let entity: Readable<UpObject> = readable(undefined);
|
||||
let entityInfo: Readable<EntityInfo> = writable(undefined);
|
||||
|
@ -152,6 +154,7 @@
|
|||
class="upobject"
|
||||
class:left-active={address == $addresses[$index - 1]}
|
||||
class:right-active={address == $addresses[$index + 1]}
|
||||
class:selected={select && $selected.includes(address)}
|
||||
>
|
||||
<div
|
||||
class="address"
|
||||
|
@ -298,4 +301,15 @@
|
|||
color: var(--active-color, var(--primary));
|
||||
}
|
||||
}
|
||||
|
||||
.upobject {
|
||||
transition:
|
||||
margin 0.2s ease,
|
||||
box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.selected {
|
||||
margin: 0.12rem;
|
||||
box-shadow: 0 0 0.1rem 0.11rem colors.$red;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
export let labels: string[] | undefined = undefined;
|
||||
export let thumbnail = true;
|
||||
export let banner = true;
|
||||
export let select = true;
|
||||
</script>
|
||||
|
||||
<div class="upobjectcard">
|
||||
|
@ -21,7 +22,7 @@
|
|||
</div>
|
||||
{/if}
|
||||
<div class="label">
|
||||
<UpObject {address} {labels} {banner} on:resolved />
|
||||
<UpObject {address} {labels} {banner} {select} on:resolved />
|
||||
</div>
|
||||
</div>
|
||||
</UpLink>
|
||||
|
|
|
@ -175,7 +175,6 @@
|
|||
data-select-mode={select}
|
||||
use:observe
|
||||
class="item"
|
||||
class:selected={select === "add" && $selected.includes(entity)}
|
||||
>
|
||||
{#if visible.has(entity)}
|
||||
{#if thumbnails}
|
||||
|
@ -183,6 +182,7 @@
|
|||
address={entity}
|
||||
labels={sortKeys[entity]}
|
||||
banner={false}
|
||||
select={select === "add"}
|
||||
on:resolved={(event) => {
|
||||
addSortKeys(entity, event.detail, true);
|
||||
}}
|
||||
|
@ -200,6 +200,7 @@
|
|||
link
|
||||
address={entity}
|
||||
labels={sortKeys[entity]}
|
||||
select={select === "add"}
|
||||
on:resolved={(event) => {
|
||||
addSortKeys(entity, event.detail, true);
|
||||
}}
|
||||
|
@ -336,15 +337,4 @@
|
|||
.entitylist.style-grid .add {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.item {
|
||||
transition:
|
||||
margin 0.2s ease,
|
||||
box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.selected {
|
||||
margin: 0.12rem;
|
||||
box-shadow: 0 0 0.1rem 0.11rem colors.$red;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
import SelectedColumn from "../components/SelectedColumn.svelte";
|
||||
import Inspect from "../components/Inspect.svelte";
|
||||
import CombineColumn from "../components/CombineColumn.svelte";
|
||||
import GroupColumn from "../components/GroupColumn.svelte";
|
||||
const navigate = useNavigate();
|
||||
const params = useParams();
|
||||
|
||||
|
@ -108,6 +109,15 @@
|
|||
>
|
||||
<SelectedColumn />
|
||||
</BrowseColumn>
|
||||
{:else if address === "groups"}
|
||||
<BrowseColumn
|
||||
{index}
|
||||
{only}
|
||||
on:close={() => close(index)}
|
||||
on:detail={(ev) => onDetailChanged(index, ev)}
|
||||
>
|
||||
<GroupColumn />
|
||||
</BrowseColumn>
|
||||
{:else}
|
||||
<BrowseColumn
|
||||
{address}
|
||||
|
|
|
@ -178,7 +178,7 @@
|
|||
</section>
|
||||
|
||||
<section class="groups">
|
||||
<h2><Link to="/groups">{$i18n.t("Groups")}</Link></h2>
|
||||
<h2><Link to="/browse/groups">{$i18n.t("Groups")}</Link></h2>
|
||||
{#await groups}
|
||||
<Spinner centered />
|
||||
{:then data}
|
||||
|
|
Loading…
Reference in New Issue