[ui] rudimentary attribute editing

also refactor to use actual correct types throughout
feat/vaults
Tomáš Mládek 2022-01-09 21:24:49 +01:00
parent 8ceb310d9e
commit a099cbd85f
No known key found for this signature in database
GPG Key ID: ED21612889E75EC5
9 changed files with 149 additions and 44 deletions

View File

@ -1,4 +1,4 @@
import type { IEntry, ListingResult, VALUE_TYPE } from "./types";
import type { IEntry, IValue, ListingResult, VALUE_TYPE } from "./types";
// export function listingAsOrdered(listing: ListingResult): OrderedListing {
// const entries = Object.entries(listing) as [Address, IEntry][];
@ -103,7 +103,7 @@ export class UpObject {
export class UpEntry extends UpObject implements IEntry {
entity: string;
attribute: string;
value: { t: VALUE_TYPE; c: string | number };
value: IValue;
constructor(address: string, entry: IEntry, listing: UpListing) {
super(address, listing);

View File

@ -4,7 +4,12 @@ export type VALUE_TYPE = "Value" | "Address" | "Invalid";
export interface IEntry {
entity: Address;
attribute: string;
value: { t: VALUE_TYPE; c: string | number };
value: IValue;
}
export interface IValue {
t: VALUE_TYPE;
c: string | number;
}
export interface ListingResult {
@ -28,8 +33,7 @@ export interface IJob {
state: "InProgress" | "Done" | "Failed";
}
export interface VaultInfo {
name: string | null;
location: string;
}
}

View File

@ -55,16 +55,26 @@
body: JSON.stringify({
entity: address,
attribute: change.attribute,
value: {
t: "Value",
c: change.value,
},
value: change.value,
}),
});
break;
case "delete":
await fetch(`/api/obj/${change.addr}`, { method: "DELETE" });
break;
case "update":
// TODO
await fetch(`/api/obj/${change.addr}`, { method: "DELETE" });
await fetch(`/api/obj`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
entity: address,
attribute: change.attribute,
value: change.value,
}),
});
break;
default:
console.error("Unimplemented AttributeChange", change);
return;

View File

@ -0,0 +1,37 @@
<script lang="ts">
import Selector from "./Selector.svelte";
import { createEventDispatcher } from "svelte";
import type { IValue } from "upend/types";
import IconButton from "./IconButton.svelte";
const dispatch = createEventDispatcher();
export let editable: boolean;
export let value: IValue;
let newValue: IValue = value;
</script>
<div class="editable">
{#if editable}
<div class="inner">
<div class="selector">
<Selector type="value" bind:value={newValue} />
</div>
<IconButton
name="check-circle"
on:click={() => dispatch("edit", newValue)}
/>
</div>
{:else}
<slot />
{/if}
</div>
<style>
.inner {
display: flex;
}
.selector {
flex-grow: 1;
}
</style>

View File

@ -8,6 +8,10 @@
let focused = false;
$: dispatch("focusChange", focused);
function onInput() {
dispatch("input", value);
}
</script>
<div class="input" class:focused>
@ -15,7 +19,7 @@
<input
{placeholder}
bind:value
on:input
on:input={onInput}
on:focus={() => (focused = true)}
on:blur={() => (focused = false)}
/>

View File

@ -1,27 +1,68 @@
<script lang="ts">
import { tick } from "svelte";
import { debounce } from "lodash";
import type { IValue } from "upend/types";
import Input from "./Input.svelte";
export let value = "";
export let attribute: string | undefined = undefined;
export let value: IValue | undefined = undefined;
export let type: "entity" | "attribute" | "value";
let options = [];
async function updateOptions(query: string) {
interface SelectorOption {
attribute?: string;
value?: IValue;
}
let options: SelectorOption[] = [];
const updateOptions = debounce(async (query: string) => {
switch (type) {
case "entity":
throw new Error("unimplemented");
case "attribute":
const req = await fetch("/api/all/attributes");
const allAttributes: string[] = await req.json();
options = allAttributes.filter((attr) =>
attr.toLowerCase().includes(query.toLowerCase())
);
options = allAttributes
.filter((attr) => attr.toLowerCase().includes(query.toLowerCase()))
.map((attribute) => {
return {
attribute,
};
});
break;
case "value":
throw new Error("unimplemented");
break;
}
}, 200);
$: updateOptions(inputValue);
function set(option: SelectorOption) {
if (type == "attribute") {
attribute = option.attribute;
inputValue = option.attribute;
} else {
value = option.value;
inputValue = String(option.value.c); // todo;
}
visible = false;
}
let inputValue = "";
if (type == "attribute") {
inputValue = attribute || "";
} else {
inputValue = String(value?.c || "");
}
function onInput(ev: CustomEvent<string>) {
if (type == "attribute") {
attribute = ev.detail;
} else {
value = {
t: "Value",
c: ev.detail,
};
}
}
$: updateOptions(value);
let inputFocused = false;
let hover = false;
@ -29,7 +70,11 @@
</script>
<div class="selector">
<Input bind:value on:focusChange={(ev) => (inputFocused = ev.detail)} />
<Input
value={inputValue}
on:input={onInput}
on:focusChange={(ev) => (inputFocused = ev.detail)}
/>
<ul
class="options"
class:visible
@ -37,13 +82,12 @@
on:mouseleave={() => (hover = false)}
>
{#each options as option}
<li
on:click={() => {
value = option;
visible = false;
}}
>
{option}
<li on:click={() => set(option)}>
{#if option.attribute}
{option.attribute}
{:else if option.value}
RESOLVING LOGIC
{/if}
</li>
{/each}
</ul>

View File

@ -11,6 +11,8 @@
import IconButton from "../utils/IconButton.svelte";
import Input from "../utils/Input.svelte";
import Selector from "../utils/Selector.svelte";
import type { IValue } from "upend/types";
import Editable from "../utils/Editable.svelte";
const dispatch = createEventDispatcher();
const params = useParams();
@ -28,7 +30,7 @@
// Editing
let newEntryAttribute = "";
let newEntryValue = "";
let newEntryValue: IValue | undefined;
async function addEntry() {
dispatch("change", {
@ -37,14 +39,14 @@
value: newEntryValue,
} as AttributeChange);
newEntryAttribute = "";
newEntryValue = "";
newEntryValue = undefined;
}
async function removeEntry(addr: string) {
if (confirm("Are you sure you want to remove the attribute?")) {
dispatch("change", { type: "delete", addr } as AttributeChange);
}
}
async function updateEntry(addr: string, attribute: string, value: string) {
async function updateEntry(addr: string, attribute: string, value: IValue) {
dispatch("change", {
type: "update",
addr,
@ -180,11 +182,11 @@
const handler = VALUE_FORMATTERS[attribute];
if (handler) {
try {
return handler(value);
return handler(value);
} catch (error) {
console.warn(`Error while formatting "${value}": ${error}`);
}
}
}
return String(value);
}
@ -264,10 +266,11 @@
{#if showValue}
<td class="value">
<text-input
<Editable
{editable}
value={entry.value.c}
on:edit={(val) => updateEntry(entry.address, entry.attribute, val)}
value={entry.value}
on:edit={(ev) =>
updateEntry(entry.address, entry.attribute, ev.detail)}
>
{#if entry.value.t === "Address"}
<UpObject
@ -293,25 +296,25 @@
/>
</div>
{/if}
</text-input>
</Editable>
</td>
{/if}
</tr>
{/each}
{#if editable}
<tr>
<tr class="add-row">
<td class="attr-action">
<IconButton name="plus-circle" on:click={addEntry} />
</td>
{#if showAttribute}
<td>
<Selector type="attribute" bind:value={newEntryAttribute} />
<Selector type="attribute" bind:attribute={newEntryAttribute} />
</td>
{/if}
{#if showValue}
<td>
<Input bind:value={newEntryValue} />
<Selector type="value" bind:value={newEntryValue} />
</td>
{/if}
</tr>

View File

@ -1,3 +1,5 @@
import type { IValue } from "upend/types";
export type AttributeChange =
| AttributeCreate
| AttributeUpdate
@ -6,16 +8,17 @@ export type AttributeChange =
export interface AttributeCreate {
type: "create";
attribute: string;
value: any;
value: IValue;
}
export interface AttributeUpdate {
type: "update";
addr: string;
value: any;
attribute: string; // TODO: remove
value: IValue;
}
export interface AttributeDelete {
type: "delete";
addr: string;
}
}

View File

@ -3474,8 +3474,8 @@ __metadata:
"upend@file:../tools/upend_js::locator=svelte-app%40workspace%3A.":
version: 0.0.1
resolution: "upend@file:../tools/upend_js#../tools/upend_js::hash=a954aa&locator=svelte-app%40workspace%3A."
checksum: 1b0605428365b2a672cb119e795292102f80171f03efec8290a5811b1514107455557a21157e7376aa7dcb7285484fd1db791d6d876893ec92f1794a0f61120f
resolution: "upend@file:../tools/upend_js#../tools/upend_js::hash=2fee87&locator=svelte-app%40workspace%3A."
checksum: 51fd21439f8a42d901e03c06e4797f89fd7c505c21ce53d9cb7340ae7495f05d774dd7f0ff758b5470ebb1fb6725e33e06b09d98ec91f17e9766ae1c05d2bd58
languageName: node
linkType: hard