chore: refactor widgets + gallery

This commit is contained in:
Tomáš Mládek 2022-09-04 23:29:41 +02:00
parent 49b21cfd7a
commit 7bfb4f86ca
6 changed files with 226 additions and 176 deletions

View file

@ -1,8 +1,7 @@
<script lang="ts"> <script lang="ts">
import UpLink from "./display/UpLink.svelte"; import UpLink from "./display/UpLink.svelte";
import type { Component, UpType, Widget } from "../lib/types"; import type { Component, UpType, Widget } from "../lib/types";
import Table from "./widgets/Table.svelte"; import EntryList from "./widgets/EntryList.svelte";
import TableComponent from "./widgets/Table.svelte"; // silence false svelte(reactive-component) warnings
import type { UpEntry } from "upend"; import type { UpEntry } from "upend";
import Icon from "./utils/Icon.svelte"; import Icon from "./utils/Icon.svelte";
import IconButton from "./utils/IconButton.svelte"; import IconButton from "./utils/IconButton.svelte";
@ -11,6 +10,7 @@
export let entries: UpEntry[]; export let entries: UpEntry[];
export let type: UpType | undefined = undefined; export let type: UpType | undefined = undefined;
export let widgets: Widget[] | undefined;
export let title: string | undefined = undefined; export let title: string | undefined = undefined;
export let editable = false; export let editable = false;
export let reverse = false; export let reverse = false;
@ -27,11 +27,11 @@
$: { $: {
availableWidgets = [ availableWidgets = [
{ {
name: "table", name: "entrylist",
icon: "table", icon: "table",
components: [ components: [
{ {
component: TableComponent, component: EntryList,
}, },
], ],
}, },
@ -40,6 +40,11 @@
if (type?.widgetInfo.length > 0) { if (type?.widgetInfo.length > 0) {
availableWidgets = [...type.widgetInfo, ...availableWidgets]; availableWidgets = [...type.widgetInfo, ...availableWidgets];
} }
if (widgets?.length) {
availableWidgets = [...widgets, ...availableWidgets];
}
if (availableWidgets.map((w) => w.name).includes(initialWidget)) { if (availableWidgets.map((w) => w.name).includes(initialWidget)) {
currentWidget = initialWidget; currentWidget = initialWidget;
} else { } else {
@ -94,14 +99,16 @@
{#each components as component} {#each components as component}
<svelte:component <svelte:component
this={component.component} this={component.component}
{...component.props || {}} {...(typeof component.props === "function"
? component.props(entries)
: component.props) || {}}
{entries} {entries}
{editable} {editable}
on:change on:change
/> />
{/each} {/each}
{:else} {:else}
<Table columns="entity, attribute" {entries} /> <EntryList columns="entity, attribute" {entries} />
{/if} {/if}
</div> </div>
</section> </section>

View file

@ -1,19 +1,23 @@
<script lang="ts"> <script lang="ts">
import { readable, type Readable } from "svelte/store"; import { readable, type Readable } from "svelte/store";
import type { UpListing } from "upend";
import type { UpEntry, UpListing } from "upend"; import type { Address } from "upend/types";
import { query } from "../../lib/entity"; import { query } from "../../lib/entity";
import { defaultEntitySort, entityValueSort } from "../../util/sort";
import BlobPreview from "../display/BlobPreview.svelte"; import BlobPreview from "../display/BlobPreview.svelte";
import UpLink from "../display/UpLink.svelte"; import UpLink from "../display/UpLink.svelte";
import UpObject from "../display/UpObject.svelte"; import UpObject from "../display/UpObject.svelte";
export let entries: UpEntry[]; export let entities: Address[];
export const editable = false; export let thumbnails = true;
export let showEntities = false; export let sort = true;
let style: "list" | "grid" | "flex" = "grid";
let clientWidth: number;
$: style = clientWidth < 600 ? "list" : "grid";
// Sorting // Sorting
let sortedAttributes = entries; let sortedEntities = entities;
let sortKeys: { [key: string]: string[] } = {}; let sortKeys: { [key: string]: string[] } = {};
function addSortKeys(key: string, vals: string[], resort = true) { function addSortKeys(key: string, vals: string[], resort = true) {
@ -35,15 +39,11 @@
let labelListing: Readable<UpListing> = readable(undefined); let labelListing: Readable<UpListing> = readable(undefined);
$: { $: {
const addresses = []; const addresses = [];
entries entities.forEach((addr) => {
.flatMap((e) => if (!addresses.includes(addr)) {
e.value.t === "Address" ? [e.entity, e.value.c] : [e.entity] addresses.push(addr);
) }
.forEach((addr) => { });
if (!addresses.includes(addr)) {
addresses.push(addr);
}
});
const addressesString = addresses.map((addr) => `@${addr}`).join(" "); const addressesString = addresses.map((addr) => `@${addr}`).join(" ");
@ -51,82 +51,105 @@
} }
function sortAttributes() { function sortAttributes() {
sortedAttributes = showEntities if (!sort) return;
? entityValueSort(entries, sortKeys)
: defaultEntitySort(entries, sortKeys); 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) { if ($labelListing) {
entries.forEach((entry) => { entities.forEach((address) => {
addSortKeys( addSortKeys(address, $labelListing.getObject(address).identify());
entry.entity,
$labelListing.getObject(entry.entity).identify()
);
if (entry.value.t === "Address") {
addSortKeys(
entry.value.c,
$labelListing.getObject(String(entry.value.c)).identify(),
false
);
}
}); });
sortAttributes(); 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(); sortAttributes();
</script> </script>
<div class="gallery"> <div
{#each sortedAttributes as entry (entry.address)} class="gallery style-{style}"
<div class="thumbnail"> class:has-thumbnails={thumbnails}
<UpLink bind:clientWidth
to={{ entity: String(showEntities ? entry.entity : entry.value.c) }} >
> <div class="header" />
<BlobPreview <div class="items">
address={String(showEntities ? entry.entity : entry.value.c)} {#each sortedEntities as entity (entity)}
/> {#if thumbnails}
<div class="label"> <div class="thumbnail">
<UpObject <UpLink to={{ entity }}>
address={String(showEntities ? entry.entity : entry.value.c)} <BlobPreview address={entity} />
on:resolved={(event) => { <div class="label">
addSortKeys(String(entry.value.c), event.detail); <UpObject
}} address={entity}
/> on:resolved={(event) => {
addSortKeys(entity, event.detail);
}}
/>
</div>
</UpLink>
</div> </div>
</UpLink> {:else}
</div> <UpObject link address={entity} />
{/each} {/if}
{/each}
</div>
</div> </div>
<style lang="scss"> <style lang="scss">
.thumbnail { .thumbnail {
border: 1px solid var(--foreground); border: 1px solid var(--foreground-lighter);
border-radius: 4px; border-radius: 4px;
padding: 0.25em; padding: 0.25em;
} }
.gallery { .items {
display: flex; gap: 4px;
}
.gallery.has-thumbnails .items {
gap: 1rem; 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; flex-wrap: wrap;
align-items: flex-end; align-items: flex-end;
} }
:global(.gallery.style-list .items) {
display: flex;
flex-direction: column;
align-items: stretch;
}
.thumbnail { .thumbnail {
display: flex;
flex-direction: column;
justify-content: flex-end;
flex-grow: 1; flex-grow: 1;
min-width: 0; min-width: 0;
flex-basis: 20%; flex-basis: 20%;

View file

@ -1,4 +1,4 @@
import Table from "../components/widgets/Table.svelte"; import type { UpEntry } from "upend";
import Gallery from "../components/widgets/Gallery.svelte"; import Gallery from "../components/widgets/Gallery.svelte";
export class UpType { export class UpType {
@ -24,7 +24,9 @@ export const UNTYPED = new UpType();
export interface Component { export interface Component {
component: any; // TODO component: any; // TODO
props?: { [key: string]: unknown }; props?:
| { [key: string]: unknown }
| ((entries: UpEntry[]) => { [key: string]: unknown });
} }
export interface Widget { export interface Widget {
@ -45,20 +47,32 @@ const TYPE_WIDGETS: { [key: string]: Widget[] } = {
icon: "folder", icon: "folder",
components: [ components: [
{ {
component: Table, component: Gallery,
props: { props: (entries) => {
columns: "value", return {
header: false, thumbnails: false,
entities: entries
.filter((e) => e.attribute == "HAS")
.map((e) => String(e.value.c)),
};
}, },
}, },
], ],
}, },
{ {
name: "gallery-view", name: "hierarchical-listing-gallery",
icon: "image", icon: "image",
components: [ components: [
{ {
component: Gallery, 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,
},
},
],
},
],
}; };

View file

@ -1,4 +1,7 @@
<script lang="ts"> <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 { Link } from "svelte-navigator";
import { UpListing } from "upend"; import { UpListing } from "upend";
import AttributeView from "../components/AttributeView.svelte"; import AttributeView from "../components/AttributeView.svelte";
@ -6,7 +9,6 @@
import Spinner from "../components/utils/Spinner.svelte"; import Spinner from "../components/utils/Spinner.svelte";
import { fetchRoots } from "../lib/api"; import { fetchRoots } from "../lib/api";
import { query } from "../lib/entity"; import { query } from "../lib/entity";
import { UpType } from "../lib/types";
import { vaultInfo } from "../util/info"; import { vaultInfo } from "../util/info";
import { updateTitle } from "../util/title"; import { updateTitle } from "../util/title";
@ -37,11 +39,71 @@
.sort((a, b) => (b.value.c as number) - (a.value.c as number)) .sort((a, b) => (b.value.c as number) - (a.value.c as number))
.slice(0, 25); .slice(0, 25);
const HomeViewShortType = new UpType(); const shortWidgets: Widget[] = [
HomeViewShortType.name = "HOME_VIEW_SHORT"; {
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(); const longWidgets: Widget[] = [
HomeViewLongType.name = "HOME_VIEW_LONG"; {
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"); updateTitle("Home");
</script> </script>
@ -78,7 +140,7 @@
<AttributeView <AttributeView
--current-background="var(--background)" --current-background="var(--background)"
entries={frequent} entries={frequent}
type={HomeViewShortType} widgets={shortWidgets}
/> />
{/if} {/if}
</section> </section>
@ -92,7 +154,7 @@
<AttributeView <AttributeView
--current-background="var(--background)" --current-background="var(--background)"
entries={recent} entries={recent}
type={HomeViewLongType} widgets={longWidgets}
/> />
{/if} {/if}
</section> </section>
@ -108,7 +170,7 @@
<AttributeView <AttributeView
--current-background="var(--background)" --current-background="var(--background)"
entries={latest} entries={latest}
type={HomeViewLongType} widgets={longWidgets}
/> />
{/if} {/if}
</section> </section>

View file

@ -10,8 +10,10 @@
import { updateTitle } from "../util/title"; import { updateTitle } from "../util/title";
import { query as queryFn } from "../lib/entity"; import { query as queryFn } from "../lib/entity";
import AttributeView from "../components/AttributeView.svelte"; import AttributeView from "../components/AttributeView.svelte";
import { UpType } from "../lib/types";
import { queryOnce } from "../lib/api"; 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(); const navigate = useNavigate();
export let query: string; export let query: string;
@ -59,8 +61,36 @@
$: updateTitle("Search", query); $: updateTitle("Search", query);
const SearchViewType = new UpType(); const searchWidgets: Widget[] = [
SearchViewType.name = "SEARCH_VIEW"; {
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> </script>
<div> <div>
@ -94,7 +124,7 @@
<AttributeView <AttributeView
--current-background="var(--background)" --current-background="var(--background)"
entries={objects} entries={objects}
type={SearchViewType} widgets={searchWidgets}
/> />
{/if} {/if}
{/await} {/await}