chore: refactor widgets + gallery

feat/type-attributes
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">
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>

View File

@ -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,15 +39,11 @@
let labelListing: Readable<UpListing> = readable(undefined);
$: {
const addresses = [];
entries
.flatMap((e) =>
e.value.t === "Address" ? [e.entity, e.value.c] : [e.entity]
)
.forEach((addr) => {
if (!addresses.includes(addr)) {
addresses.push(addr);
}
});
entities.forEach((addr) => {
if (!addresses.includes(addr)) {
addresses.push(addr);
}
});
const addressesString = addresses.map((addr) => `@${addr}`).join(" ");
@ -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) }}
>
<BlobPreview
address={String(showEntities ? entry.entity : entry.value.c)}
/>
<div class="label">
<UpObject
address={String(showEntities ? entry.entity : entry.value.c)}
on:resolved={(event) => {
addSortKeys(String(entry.value.c), event.detail);
}}
/>
<div
class="gallery style-{style}"
class:has-thumbnails={thumbnails}
bind:clientWidth
>
<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={entity}
on:resolved={(event) => {
addSortKeys(entity, event.detail);
}}
/>
</div>
</UpLink>
</div>
</UpLink>
</div>
{/each}
{: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%;

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";
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,
},
},
],
},
],
};

View File

@ -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>

View File

@ -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}