chore: refactor widgets + gallery
This commit is contained in:
parent
49b21cfd7a
commit
7bfb4f86ca
6 changed files with 226 additions and 176 deletions
|
@ -1,8 +1,7 @@
|
|||
<script lang="ts">
|
||||
import UpLink from "./display/UpLink.svelte";
|
||||
import type { Component, UpType, Widget } from "../lib/types";
|
||||
import Table from "./widgets/Table.svelte";
|
||||
import TableComponent from "./widgets/Table.svelte"; // silence false svelte(reactive-component) warnings
|
||||
import EntryList from "./widgets/EntryList.svelte";
|
||||
import type { UpEntry } from "upend";
|
||||
import Icon from "./utils/Icon.svelte";
|
||||
import IconButton from "./utils/IconButton.svelte";
|
||||
|
@ -11,6 +10,7 @@
|
|||
|
||||
export let entries: UpEntry[];
|
||||
export let type: UpType | undefined = undefined;
|
||||
export let widgets: Widget[] | undefined;
|
||||
export let title: string | undefined = undefined;
|
||||
export let editable = false;
|
||||
export let reverse = false;
|
||||
|
@ -27,11 +27,11 @@
|
|||
$: {
|
||||
availableWidgets = [
|
||||
{
|
||||
name: "table",
|
||||
name: "entrylist",
|
||||
icon: "table",
|
||||
components: [
|
||||
{
|
||||
component: TableComponent,
|
||||
component: EntryList,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -40,6 +40,11 @@
|
|||
if (type?.widgetInfo.length > 0) {
|
||||
availableWidgets = [...type.widgetInfo, ...availableWidgets];
|
||||
}
|
||||
|
||||
if (widgets?.length) {
|
||||
availableWidgets = [...widgets, ...availableWidgets];
|
||||
}
|
||||
|
||||
if (availableWidgets.map((w) => w.name).includes(initialWidget)) {
|
||||
currentWidget = initialWidget;
|
||||
} else {
|
||||
|
@ -94,14 +99,16 @@
|
|||
{#each components as component}
|
||||
<svelte:component
|
||||
this={component.component}
|
||||
{...component.props || {}}
|
||||
{...(typeof component.props === "function"
|
||||
? component.props(entries)
|
||||
: component.props) || {}}
|
||||
{entries}
|
||||
{editable}
|
||||
on:change
|
||||
/>
|
||||
{/each}
|
||||
{:else}
|
||||
<Table columns="entity, attribute" {entries} />
|
||||
<EntryList columns="entity, attribute" {entries} />
|
||||
{/if}
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
@ -1,19 +1,23 @@
|
|||
<script lang="ts">
|
||||
import { readable, type Readable } from "svelte/store";
|
||||
|
||||
import type { UpEntry, UpListing } from "upend";
|
||||
import type { UpListing } from "upend";
|
||||
import type { Address } from "upend/types";
|
||||
import { query } from "../../lib/entity";
|
||||
import { defaultEntitySort, entityValueSort } from "../../util/sort";
|
||||
import BlobPreview from "../display/BlobPreview.svelte";
|
||||
import UpLink from "../display/UpLink.svelte";
|
||||
import UpObject from "../display/UpObject.svelte";
|
||||
|
||||
export let entries: UpEntry[];
|
||||
export const editable = false;
|
||||
export let showEntities = false;
|
||||
export let entities: Address[];
|
||||
export let thumbnails = true;
|
||||
export let sort = true;
|
||||
|
||||
let style: "list" | "grid" | "flex" = "grid";
|
||||
|
||||
let clientWidth: number;
|
||||
$: style = clientWidth < 600 ? "list" : "grid";
|
||||
|
||||
// Sorting
|
||||
let sortedAttributes = entries;
|
||||
let sortedEntities = entities;
|
||||
|
||||
let sortKeys: { [key: string]: string[] } = {};
|
||||
function addSortKeys(key: string, vals: string[], resort = true) {
|
||||
|
@ -35,11 +39,7 @@
|
|||
let labelListing: Readable<UpListing> = readable(undefined);
|
||||
$: {
|
||||
const addresses = [];
|
||||
entries
|
||||
.flatMap((e) =>
|
||||
e.value.t === "Address" ? [e.entity, e.value.c] : [e.entity]
|
||||
)
|
||||
.forEach((addr) => {
|
||||
entities.forEach((addr) => {
|
||||
if (!addresses.includes(addr)) {
|
||||
addresses.push(addr);
|
||||
}
|
||||
|
@ -51,82 +51,105 @@
|
|||
}
|
||||
|
||||
function sortAttributes() {
|
||||
sortedAttributes = showEntities
|
||||
? entityValueSort(entries, sortKeys)
|
||||
: defaultEntitySort(entries, sortKeys);
|
||||
if (!sort) return;
|
||||
|
||||
sortedEntities = entities.concat();
|
||||
|
||||
sortedEntities.sort((a, b) => {
|
||||
if (!sortKeys[a]?.length || !sortKeys[b]?.length) {
|
||||
if (Boolean(sortKeys[a]?.length) && !sortKeys[b]?.length) {
|
||||
return -1;
|
||||
} else if (!sortKeys[a]?.length && Boolean(sortKeys[b]?.length)) {
|
||||
return 1;
|
||||
} else {
|
||||
return a.localeCompare(b);
|
||||
}
|
||||
} else {
|
||||
return sortKeys[a][0].localeCompare(sortKeys[b][0], undefined, {
|
||||
numeric: true,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$: {
|
||||
if ($labelListing) {
|
||||
entries.forEach((entry) => {
|
||||
addSortKeys(
|
||||
entry.entity,
|
||||
$labelListing.getObject(entry.entity).identify()
|
||||
);
|
||||
|
||||
if (entry.value.t === "Address") {
|
||||
addSortKeys(
|
||||
entry.value.c,
|
||||
$labelListing.getObject(String(entry.value.c)).identify(),
|
||||
false
|
||||
);
|
||||
}
|
||||
entities.forEach((address) => {
|
||||
addSortKeys(address, $labelListing.getObject(address).identify());
|
||||
});
|
||||
sortAttributes();
|
||||
}
|
||||
}
|
||||
|
||||
entries.forEach((entry) => {
|
||||
addSortKeys(entry.entity, entry.listing.getObject(entry.entity).identify());
|
||||
if (entry.value.t === "Address") {
|
||||
addSortKeys(
|
||||
entry.value.c,
|
||||
entry.listing.getObject(String(entry.value.c)).identify(),
|
||||
false
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
sortAttributes();
|
||||
</script>
|
||||
|
||||
<div class="gallery">
|
||||
{#each sortedAttributes as entry (entry.address)}
|
||||
<div class="thumbnail">
|
||||
<UpLink
|
||||
to={{ entity: String(showEntities ? entry.entity : entry.value.c) }}
|
||||
<div
|
||||
class="gallery style-{style}"
|
||||
class:has-thumbnails={thumbnails}
|
||||
bind:clientWidth
|
||||
>
|
||||
<BlobPreview
|
||||
address={String(showEntities ? entry.entity : entry.value.c)}
|
||||
/>
|
||||
<div class="header" />
|
||||
<div class="items">
|
||||
{#each sortedEntities as entity (entity)}
|
||||
{#if thumbnails}
|
||||
<div class="thumbnail">
|
||||
<UpLink to={{ entity }}>
|
||||
<BlobPreview address={entity} />
|
||||
<div class="label">
|
||||
<UpObject
|
||||
address={String(showEntities ? entry.entity : entry.value.c)}
|
||||
address={entity}
|
||||
on:resolved={(event) => {
|
||||
addSortKeys(String(entry.value.c), event.detail);
|
||||
addSortKeys(entity, event.detail);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</UpLink>
|
||||
</div>
|
||||
{:else}
|
||||
<UpObject link address={entity} />
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.thumbnail {
|
||||
border: 1px solid var(--foreground);
|
||||
border: 1px solid var(--foreground-lighter);
|
||||
border-radius: 4px;
|
||||
padding: 0.25em;
|
||||
}
|
||||
|
||||
.gallery {
|
||||
display: flex;
|
||||
.items {
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.gallery.has-thumbnails .items {
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
:global(.gallery.style-grid .items) {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
|
||||
:global(.gallery.style-flex .items) {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
:global(.gallery.style-list .items) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.thumbnail {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
flex-basis: 20%;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import Table from "../components/widgets/Table.svelte";
|
||||
import type { UpEntry } from "upend";
|
||||
import Gallery from "../components/widgets/Gallery.svelte";
|
||||
|
||||
export class UpType {
|
||||
|
@ -24,7 +24,9 @@ export const UNTYPED = new UpType();
|
|||
|
||||
export interface Component {
|
||||
component: any; // TODO
|
||||
props?: { [key: string]: unknown };
|
||||
props?:
|
||||
| { [key: string]: unknown }
|
||||
| ((entries: UpEntry[]) => { [key: string]: unknown });
|
||||
}
|
||||
|
||||
export interface Widget {
|
||||
|
@ -45,20 +47,32 @@ const TYPE_WIDGETS: { [key: string]: Widget[] } = {
|
|||
icon: "folder",
|
||||
components: [
|
||||
{
|
||||
component: Table,
|
||||
props: {
|
||||
columns: "value",
|
||||
header: false,
|
||||
component: Gallery,
|
||||
props: (entries) => {
|
||||
return {
|
||||
thumbnails: false,
|
||||
entities: entries
|
||||
.filter((e) => e.attribute == "HAS")
|
||||
.map((e) => String(e.value.c)),
|
||||
};
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "gallery-view",
|
||||
name: "hierarchical-listing-gallery",
|
||||
icon: "image",
|
||||
components: [
|
||||
{
|
||||
component: Gallery,
|
||||
props: (entries) => {
|
||||
return {
|
||||
thumbnails: true,
|
||||
entities: entries
|
||||
.filter((e) => e.attribute == "HAS")
|
||||
.map((e) => String(e.value.c)),
|
||||
};
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -91,90 +105,4 @@ const TYPE_WIDGETS: { [key: string]: Widget[] } = {
|
|||
],
|
||||
},
|
||||
],
|
||||
HOME_VIEW_SHORT: [
|
||||
{
|
||||
name: "list-table",
|
||||
icon: "list-ul",
|
||||
components: [
|
||||
{
|
||||
component: Table,
|
||||
props: {
|
||||
columns: "value, entity",
|
||||
columnWidths: ["6em"],
|
||||
orderByValue: true,
|
||||
header: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "gallery-view",
|
||||
icon: "image",
|
||||
components: [
|
||||
{
|
||||
component: Gallery,
|
||||
props: {
|
||||
showEntities: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
HOME_VIEW_LONG: [
|
||||
{
|
||||
name: "list-table",
|
||||
icon: "list-ul",
|
||||
components: [
|
||||
{
|
||||
component: Table,
|
||||
props: {
|
||||
columns: "value, entity",
|
||||
columnWidths: ["13em"],
|
||||
orderByValue: true,
|
||||
header: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "gallery-view",
|
||||
icon: "image",
|
||||
components: [
|
||||
{
|
||||
component: Gallery,
|
||||
props: {
|
||||
showEntities: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
SEARCH_VIEW: [
|
||||
{
|
||||
name: "list-table",
|
||||
icon: "list-ul",
|
||||
components: [
|
||||
{
|
||||
component: Table,
|
||||
props: {
|
||||
columns: "entity",
|
||||
orderByValue: true,
|
||||
header: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "gallery-view",
|
||||
icon: "image",
|
||||
components: [
|
||||
{
|
||||
component: Gallery,
|
||||
props: {
|
||||
showEntities: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
<script lang="ts">
|
||||
import EntryList from "../components/widgets/EntryList.svelte";
|
||||
import Gallery from "../components/widgets/Gallery.svelte";
|
||||
import type { Widget } from "src/lib/types";
|
||||
import { Link } from "svelte-navigator";
|
||||
import { UpListing } from "upend";
|
||||
import AttributeView from "../components/AttributeView.svelte";
|
||||
|
@ -6,7 +9,6 @@
|
|||
import Spinner from "../components/utils/Spinner.svelte";
|
||||
import { fetchRoots } from "../lib/api";
|
||||
import { query } from "../lib/entity";
|
||||
import { UpType } from "../lib/types";
|
||||
import { vaultInfo } from "../util/info";
|
||||
import { updateTitle } from "../util/title";
|
||||
|
||||
|
@ -37,11 +39,71 @@
|
|||
.sort((a, b) => (b.value.c as number) - (a.value.c as number))
|
||||
.slice(0, 25);
|
||||
|
||||
const HomeViewShortType = new UpType();
|
||||
HomeViewShortType.name = "HOME_VIEW_SHORT";
|
||||
const shortWidgets: Widget[] = [
|
||||
{
|
||||
name: "list-table",
|
||||
icon: "list-ul",
|
||||
components: [
|
||||
{
|
||||
component: EntryList,
|
||||
props: {
|
||||
columns: "value, entity",
|
||||
columnWidths: ["6em"],
|
||||
orderByValue: true,
|
||||
header: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "gallery-view",
|
||||
icon: "image",
|
||||
components: [
|
||||
{
|
||||
component: Gallery,
|
||||
props: (entries) => {
|
||||
return {
|
||||
entities: entries.map((e) => e.entity),
|
||||
sort: false,
|
||||
};
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const HomeViewLongType = new UpType();
|
||||
HomeViewLongType.name = "HOME_VIEW_LONG";
|
||||
const longWidgets: Widget[] = [
|
||||
{
|
||||
name: "list-table",
|
||||
icon: "list-ul",
|
||||
components: [
|
||||
{
|
||||
component: EntryList,
|
||||
props: {
|
||||
columns: "value, entity",
|
||||
columnWidths: ["13em"],
|
||||
orderByValue: true,
|
||||
header: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "gallery-view",
|
||||
icon: "image",
|
||||
components: [
|
||||
{
|
||||
component: Gallery,
|
||||
props: (entries) => {
|
||||
return {
|
||||
entities: entries.map((e) => e.entity),
|
||||
sort: false,
|
||||
};
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
updateTitle("Home");
|
||||
</script>
|
||||
|
@ -78,7 +140,7 @@
|
|||
<AttributeView
|
||||
--current-background="var(--background)"
|
||||
entries={frequent}
|
||||
type={HomeViewShortType}
|
||||
widgets={shortWidgets}
|
||||
/>
|
||||
{/if}
|
||||
</section>
|
||||
|
@ -92,7 +154,7 @@
|
|||
<AttributeView
|
||||
--current-background="var(--background)"
|
||||
entries={recent}
|
||||
type={HomeViewLongType}
|
||||
widgets={longWidgets}
|
||||
/>
|
||||
{/if}
|
||||
</section>
|
||||
|
@ -108,7 +170,7 @@
|
|||
<AttributeView
|
||||
--current-background="var(--background)"
|
||||
entries={latest}
|
||||
type={HomeViewLongType}
|
||||
widgets={longWidgets}
|
||||
/>
|
||||
{/if}
|
||||
</section>
|
||||
|
|
|
@ -10,8 +10,10 @@
|
|||
import { updateTitle } from "../util/title";
|
||||
import { query as queryFn } from "../lib/entity";
|
||||
import AttributeView from "../components/AttributeView.svelte";
|
||||
import { UpType } from "../lib/types";
|
||||
import { queryOnce } from "../lib/api";
|
||||
import EntryList from "../components/widgets/EntryList.svelte";
|
||||
import Gallery from "../components/widgets/Gallery.svelte";
|
||||
import type { Widget } from "src/lib/types";
|
||||
const navigate = useNavigate();
|
||||
|
||||
export let query: string;
|
||||
|
@ -59,8 +61,36 @@
|
|||
|
||||
$: updateTitle("Search", query);
|
||||
|
||||
const SearchViewType = new UpType();
|
||||
SearchViewType.name = "SEARCH_VIEW";
|
||||
const searchWidgets: Widget[] = [
|
||||
{
|
||||
name: "list-table",
|
||||
icon: "list-ul",
|
||||
components: [
|
||||
{
|
||||
component: EntryList,
|
||||
props: {
|
||||
columns: "entity",
|
||||
orderByValue: true,
|
||||
header: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "gallery-view",
|
||||
icon: "image",
|
||||
components: [
|
||||
{
|
||||
component: Gallery,
|
||||
props: (entries) => {
|
||||
return {
|
||||
entities: entries.map((e) => e.entity),
|
||||
};
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<div>
|
||||
|
@ -94,7 +124,7 @@
|
|||
<AttributeView
|
||||
--current-background="var(--background)"
|
||||
entries={objects}
|
||||
type={SearchViewType}
|
||||
widgets={searchWidgets}
|
||||
/>
|
||||
{/if}
|
||||
{/await}
|
||||
|
|
Loading…
Reference in a new issue