feat: modeless entrylist editing
ci/woodpecker/push/woodpecker Pipeline failed Details

feat/axum
Tomáš Mládek 2023-09-07 18:57:45 +02:00
parent 959a613ea3
commit 3a34fc346c
5 changed files with 198 additions and 23 deletions

View File

@ -10,6 +10,7 @@
components: (input: {
entries: UpEntry[];
group?: string;
address?: string;
}) => Array<WidgetComponent>;
}
</script>
@ -29,6 +30,7 @@
export let initialWidget: string | undefined = undefined;
export let title: string | undefined = undefined;
export let group: string | undefined = undefined;
export let address: string | undefined = undefined;
export let icon: string | undefined = undefined;
export let highlighted = false;
@ -69,7 +71,7 @@
$: {
components = availableWidgets
.find((w) => w.name === currentWidget)
.components({ entries, group });
.components({ entries, group, address });
}
</script>

View File

@ -7,7 +7,7 @@
import type { UpEntry } from "upend";
import Spinner from "./utils/Spinner.svelte";
import NotesEditor from "./utils/NotesEditor.svelte";
import type { AttributeChange } from "../types/base";
import type { WidgetChange } from "../types/base";
import type { EntityInfo } from "upend/types";
import IconButton from "./utils/IconButton.svelte";
import type { BrowseContext } from "../util/browse";
@ -188,7 +188,7 @@
}
}
async function onChange(ev: CustomEvent<AttributeChange>) {
async function onChange(ev: CustomEvent<WidgetChange>) {
const change = ev.detail;
switch (change.type) {
case "create":
@ -204,6 +204,27 @@
case "update":
await api.putEntityAttribute(address, change.attribute, change.value);
break;
case "entry-add":
await api.putEntry({
entity: address,
attribute: ATTR_IN,
value: { t: "Address", c: change.address },
});
break;
case "entry-delete": {
const inEntry = $entity?.attr[`~${ATTR_IN}`].find(
(e) => e.entity === change.address,
);
if (inEntry) {
await api.deleteEntry(inEntry.address);
} else {
console.warn(
"Couldn't find IN entry for entity %s?!",
change.address,
);
}
break;
}
default:
console.error("Unimplemented AttributeChange", change);
return;
@ -259,10 +280,11 @@
{
name: "EntityList",
icon: "image",
components: ({ entries }) => [
components: ({ entries, address }) => [
{
component: EntityList,
props: {
address,
entities: entries
.filter((e) => e.value.t == "Address")
.map((e) => e.value.c),
@ -277,10 +299,11 @@
{
name: "List",
icon: "list-check",
components: ({ entries }) => [
components: ({ entries, address }) => [
{
component: EntityList,
props: {
address,
entities: entries.map((e) => e.entity),
thumbnails: false,
},
@ -290,10 +313,11 @@
{
name: "EntityList",
icon: "image",
components: ({ entries }) => [
components: ({ entries, address }) => [
{
component: EntityList,
props: {
address,
entities: entries.map((e) => e.entity),
thumbnails: true,
},
@ -366,6 +390,7 @@
highlighted={highlightedType == typeAddr}
title={labels.join(" | ")}
group={typeAddr}
{address}
/>
{/each}
@ -375,6 +400,7 @@
widgets={attributeWidgets}
entries={currentUntypedProperties}
on:change={onChange}
{address}
/>
{/if}
@ -384,6 +410,7 @@
widgets={linkWidgets}
entries={currentUntypedLinks}
on:change={onChange}
{address}
/>
{/if}
@ -393,6 +420,7 @@
widgets={taggedWidgets}
entries={tagged}
on:change={onChange}
{address}
/>
{/if}
@ -401,6 +429,7 @@
title={`${$i18n.t("Referred to")} (${currentBacklinks.length})`}
entries={currentBacklinks}
on:change={onChange}
{address}
/>
{/if}

View File

@ -1,19 +1,27 @@
<script lang="ts">
import { readable, type Readable } from "svelte/store";
import type { UpListing } from "upend";
import type { Address } from "upend/types";
import type { Address, IValue } from "upend/types";
import { query } from "../../lib/entity";
import UpObject from "../display/UpObject.svelte";
import UpObjectCard from "../display/UpObjectCard.svelte";
import { ATTR_LABEL } from "upend/constants";
import { ATTR_IN, ATTR_LABEL } from "upend/constants";
import { i18n } from "../../i18n";
import Icon from "../utils/Icon.svelte";
import IconButton from "../utils/IconButton.svelte";
import Selector from "../utils/Selector.svelte";
import { createEventDispatcher } from "svelte";
import type { WidgetChange } from "src/types/base";
import debug from "debug";
const dispatch = createEventDispatcher();
const dbg = debug(`kestrel:EntityList`);
export let entities: Address[];
export let thumbnails = true;
export let sort = true;
export let address: Address | undefined = undefined;
const deduplicatedEntities = Array.from(new Set(entities));
$: deduplicatedEntities = Array.from(new Set(entities));
let style: "list" | "grid" | "flex" = "grid";
@ -21,7 +29,7 @@
$: style = !thumbnails || clientWidth < 600 ? "list" : "grid";
// Sorting
let sortedEntities = [];
let sortedEntities: Address[] = [];
let sortKeys: { [key: string]: string[] } = {};
function addSortKeys(key: string, vals: string[], resort: boolean) {
@ -116,6 +124,37 @@
},
};
}
// Adding
let addSelector: Selector | undefined;
let adding = false;
$: if (adding && addSelector) addSelector.focus();
function addEntity(ev: CustomEvent<IValue>) {
dbg("Adding entity", ev.detail);
const addAddress = ev.detail?.t == "Address" ? ev.detail.c : undefined;
if (!addAddress) return;
dispatch("change", {
type: "entry-add",
address: addAddress,
} as WidgetChange);
}
function removeEntity(address: string) {
if (
confirm(
$i18n.t("Are you sure you want to remove this entry from members?"),
)
) {
dbg("Removing entity", address);
dispatch("change", {
type: "entry-delete",
address,
} as WidgetChange);
}
}
</script>
<div
@ -138,21 +177,63 @@
addSortKeys(entity, event.detail, true);
}}
/>
<div class="icon">
<IconButton
name="trash"
color="#dc322f"
on:click={() => removeEntity(entity)}
/>
</div>
{:else}
<UpObject
link
address={entity}
labels={sortKeys[entity]}
on:resolved={(event) => {
addSortKeys(entity, event.detail, true);
}}
/>
<div class="object">
<UpObject
link
address={entity}
labels={sortKeys[entity]}
on:resolved={(event) => {
addSortKeys(entity, event.detail, true);
}}
/>
</div>
<div class="icon">
<IconButton
name="trash"
color="#dc322f"
on:click={() => removeEntity(entity)}
/>
</div>
{/if}
{:else}
<div class="skeleton" style="text-align: center">...</div>
{/if}
</div>
{/each}
{#if address}
<div class="add item">
{#if adding}
<Selector
bind:this={addSelector}
type="value"
valueTypes={["Address"]}
on:input={addEntity}
on:focus={(ev) => {
if (!ev.detail) {
adding = false;
}
}}
/>
{:else}
<IconButton
name="plus-circle"
outline
subdued
on:click={() => {
adding = true;
}}
/>
{/if}
</div>
{/if}
</div>
{:else}
<div class="message">
@ -174,6 +255,7 @@
:global(.entitylist.style-grid .items) {
display: grid;
grid-template-columns: repeat(4, 1fr);
align-items: end;
}
:global(.entitylist.style-flex .items) {
@ -190,10 +272,60 @@
.item {
min-width: 0;
overflow: hidden;
}
.message {
text-align: center;
margin: 0.5em;
}
.entitylist:not(.has-thumbnails) {
.item {
display: flex;
.object {
width: 100%;
}
.icon {
width: 0;
transition: width 0.3s ease;
text-align: center;
}
&:hover {
.icon {
width: 1.5em;
}
}
}
}
.entitylist.has-thumbnails {
.item {
position: relative;
.icon {
position: absolute;
top: 0.5em;
right: 0.5em;
opacity: 0;
transition: opacity 0.3s ease;
}
&:hover .icon {
opacity: 1;
}
}
}
.add {
display: flex;
flex-direction: column;
}
.entitylist.style-grid .add {
grid-column: 1 / -1;
}
</style>

View File

@ -4,7 +4,7 @@
import Ellipsis from "../utils/Ellipsis.svelte";
import UpObject from "../display/UpObject.svelte";
import { createEventDispatcher } from "svelte";
import type { AttributeChange, AttributeUpdate } from "../../types/base";
import type { WidgetChange, AttributeUpdate } from "../../types/base";
import type { UpEntry, UpListing } from "upend";
import IconButton from "../utils/IconButton.svelte";
import Selector from "../utils/Selector.svelte";
@ -57,13 +57,13 @@
type: "create",
attribute,
value,
} as AttributeChange);
} as WidgetChange);
newEntryAttribute = "";
newEntryValue = undefined;
}
async function removeEntry(address: string) {
if (confirm($i18n.t("Are you sure you want to remove the property?"))) {
dispatch("change", { type: "delete", address } as AttributeChange);
dispatch("change", { type: "delete", address } as WidgetChange);
}
}
async function updateEntry(

View File

@ -1,9 +1,11 @@
import type { IValue } from "upend/types";
export type AttributeChange =
export type WidgetChange =
| AttributeCreate
| AttributeUpdate
| AttributeDelete;
| AttributeDelete
| EntryInAdd
| EntryInDelete;
export interface AttributeCreate {
type: "create";
@ -21,3 +23,13 @@ export interface AttributeDelete {
type: "delete";
address: string;
}
export interface EntryInAdd {
type: "entry-add";
address: string;
}
export interface EntryInDelete {
type: "entry-delete";
address: string;
}