wip: LabelBorder component, modeless AudioViewer, ImageViewer

feat/axum
Tomáš Mládek 2023-09-05 20:57:01 +02:00
parent 0a5398b0a7
commit a29d66d829
8 changed files with 278 additions and 226 deletions

View File

@ -21,6 +21,7 @@
import IconButton from "./utils/IconButton.svelte";
import { createEventDispatcher, type ComponentType } from "svelte";
import UpObject from "./display/UpObject.svelte";
import LabelBorder from "./utils/LabelBorder.svelte";
const dispatch = createEventDispatcher();
export let entries: UpEntry[];
@ -72,9 +73,9 @@
}
</script>
<section class="entry-view labelborder" class:highlighted>
<header>
<h3>
<LabelBorder>
<svelte:fragment slot="header-full">
<h3 class:highlighted>
{#if group}
{#if icon}
<div class="icon">
@ -107,35 +108,30 @@
{/each}
</div>
{/if}
</header>
<div class="content">
{#each components as component}
<svelte:component
this={component.component}
{...component.props || {}}
on:change
/>
{/each}
</div>
</section>
</svelte:fragment>
{#each components as component}
<svelte:component
this={component.component}
{...component.props || {}}
on:change
/>
{/each}
</LabelBorder>
<style scoped lang="scss">
@use "./util";
.icon {
display: inline-block;
font-size: 1.25em;
margin-top: -0.3em;
position: relative;
bottom: -2px;
}
section.entry-view {
.icon {
display: inline-block;
font-size: 1.25em;
margin-top: -0.3em;
position: relative;
bottom: -2px;
}
h3 {
margin: 0;
transition: text-shadow 0.2s;
h3 {
transition: text-shadow 0.2s;
}
&.highlighted h3 {
&.highlighted {
text-shadow: #cb4b16 0 0 0.5em;
}
}

View File

@ -20,6 +20,7 @@
import { ATTR_IN, ATTR_LABEL, ATTR_KEY, ATTR_OF } from "upend/constants";
import InspectGroups from "./InspectGroups.svelte";
import InspectTypeEditor from "./InspectTypeEditor.svelte";
import LabelBorder from "./utils/LabelBorder.svelte";
const dispatch = createEventDispatcher();
const params = useParams();
@ -412,19 +413,17 @@
</div>
</div>
<section class="labelborder">
<header>
<h3>{$i18n.t("Used")} ({attributesUsed.length})</h3>
</header>
<div class="content">
<EntryList
columns="entity,value"
columnWidths={["auto", "33%"]}
entries={attributesUsed}
orderByValue
/>
</div>
</section>
<LabelBorder>
<span slot="header"
>{$i18n.t("Used")} ({attributesUsed.length})</span
>
<EntryList
columns="entity,value"
columnWidths={["auto", "33%"]}
entries={attributesUsed}
orderByValue
/>
</LabelBorder>
{/if}
</div>
{:else}
@ -472,8 +471,6 @@
</div>
<style scoped lang="scss">
@use "./util";
header h2 {
margin-bottom: 0;
}

View File

@ -9,6 +9,7 @@
import { i18n } from "../i18n";
import type { UpObject } from "upend";
import type { Readable } from "svelte/store";
import LabelBorder from "./utils/LabelBorder.svelte";
const dispatch = createEventDispatcher();
export let entity: Readable<UpObject>;
@ -49,90 +50,84 @@
}
</script>
<section class="groups labelborder">
<header><h3>{$i18n.t("Groups")}</h3></header>
<div class="content">
{#if adding}
<div class="selector">
<Selector
bind:this={groupSelector}
type="value"
valueTypes={["Address"]}
bind:value={groupToAdd}
on:input={addGroup}
on:focus={(ev) => {
if (!ev.detail) adding = false;
}}
placeholder={$i18n.t("Choose an entity...")}
<LabelBorder hide={groups.length === 0}>
<span slot="header">{$i18n.t("Groups")}</span>
{#if adding}
<div class="selector">
<Selector
bind:this={groupSelector}
type="value"
valueTypes={["Address"]}
bind:value={groupToAdd}
on:input={addGroup}
on:focus={(ev) => {
if (!ev.detail) adding = false;
}}
placeholder={$i18n.t("Choose an entity...")}
/>
</div>
{/if}
<div class="body">
<div class="group-list">
{#each groups as [groupAddress, groupEntryAddress]}
<div
class="group"
on:mouseenter={() => dispatch("highlighted", groupAddress)}
on:mouseleave={() => dispatch("highlighted", undefined)}
>
<UpObjectDisplay address={groupAddress} link />
<IconButton
subdued
name="x-circle"
on:click={() => removeGroup(groupEntryAddress)}
/>
</div>
{:else}
<div class="no-groups">
{$i18n.t("Object is not in any groups.")}
</div>
{/each}
</div>
{#if !adding}
<div class="add-button">
<IconButton
outline
small
name="folder-plus"
on:click={() => (adding = true)}
/>
</div>
{/if}
<div class="body">
<div class="group-list">
{#each groups as [groupAddress, groupEntryAddress]}
<div
class="group"
on:mouseenter={() => dispatch("highlighted", groupAddress)}
on:mouseleave={() => dispatch("highlighted", undefined)}
>
<UpObjectDisplay address={groupAddress} link />
<IconButton
subdued
name="x-circle"
on:click={() => removeGroup(groupEntryAddress)}
/>
</div>
{:else}
<div class="no-groups">
{$i18n.t("Object is not in any groups.")}
</div>
{/each}
</div>
{#if !adding}
<div class="add-button">
<IconButton
outline
small
name="folder-plus"
on:click={() => (adding = true)}
/>
</div>
{/if}
</div>
</div>
</section>
</LabelBorder>
<style lang="scss">
@use "./util";
.group-list {
display: flex;
flex-wrap: wrap;
gap: 0.25rem 0.2rem;
align-items: center;
}
.group {
display: inline-flex;
align-items: center;
}
.body {
display: flex;
align-items: start;
.groups {
margin: 0.25rem 0;
.group-list {
display: flex;
flex-wrap: wrap;
gap: 0.25rem 0.2rem;
align-items: center;
flex-grow: 1;
}
}
.group {
display: inline-flex;
align-items: center;
}
.body {
display: flex;
align-items: start;
.group-list {
flex-grow: 1;
}
}
.selector {
width: 100%;
margin-bottom: 0.5rem;
}
.selector {
width: 100%;
margin-bottom: 0.5rem;
}
.no-groups {

View File

@ -8,6 +8,7 @@
import type { Readable } from "svelte/store";
import { ATTR_OF } from "upend/constants";
import { createEventDispatcher } from "svelte";
import LabelBorder from "./utils/LabelBorder.svelte";
const dispatch = createEventDispatcher();
export let entity: Readable<UpObject>;
@ -50,59 +51,52 @@
</script>
{#if typeEntries.length || $entity?.attr["~IN"]?.length}
<section class="labelborder">
<header><h3>{$i18n.t("Type Attributes")}</h3></header>
<div class="content">
{#if adding}
<div class="selector">
<Selector
bind:this={typeSelector}
type="attribute"
bind:attribute={attributeToAdd}
on:input={add}
placeholder={$i18n.t("Assign an attribute to this type...")}
on:focus={(ev) => {
if (!ev.detail) adding = false;
}}
/>
</div>
{/if}
<div class="body">
<ul class="attributes">
{#each typeEntries as typeEntry}
<li class="attribute">
<div class="label">
<UpObjectDisplay address={typeEntry.entity} link />
</div>
<div class="controls">
<IconButton
name="x-circle"
on:click={() => remove(typeEntry)}
/>
</div>
</li>
{:else}
<li class="no-attributes">
{$i18n.t("No attributes assigned to this type.")}
</li>
{/each}
</ul>
<div class="add-button">
<IconButton
outline
small
name="plus-circle"
on:click={() => (adding = true)}
/>
</div>
<LabelBorder hide={typeEntries.length === 0}>
<span slot="header">{$i18n.t("Type Attributes")}</span>
{#if adding}
<div class="selector">
<Selector
bind:this={typeSelector}
type="attribute"
bind:attribute={attributeToAdd}
on:input={add}
placeholder={$i18n.t("Assign an attribute to this type...")}
on:focus={(ev) => {
if (!ev.detail) adding = false;
}}
/>
</div>
{/if}
<div class="body">
<ul class="attributes">
{#each typeEntries as typeEntry}
<li class="attribute">
<div class="label">
<UpObjectDisplay address={typeEntry.entity} link />
</div>
<div class="controls">
<IconButton name="x-circle" on:click={() => remove(typeEntry)} />
</div>
</li>
{:else}
<li class="no-attributes">
{$i18n.t("No attributes assigned to this type.")}
</li>
{/each}
</ul>
<div class="add-button">
<IconButton
outline
small
name="plus-circle"
on:click={() => (adding = true)}
/>
</div>
</div>
</section>
</LabelBorder>
{/if}
<style lang="scss">
@use "./util";
.attributes {
display: flex;
align-items: baseline;

View File

@ -10,6 +10,8 @@
import Selector from "../../utils/Selector.svelte";
import UpObject from "../../display/UpObject.svelte";
import Spinner from "../../utils/Spinner.svelte";
import IconButton from "../../../components/utils/IconButton.svelte";
import LabelBorder from "../../../components/utils/LabelBorder.svelte";
import { i18n } from "../../../i18n";
import { ATTR_LABEL } from "upend/constants";
import debug from "debug";
@ -18,7 +20,7 @@
export let address: string;
export let detail: boolean;
let editable = false; // TODO
let editable = false;
let containerEl: HTMLDivElement;
let timelineEl: HTMLDivElement;
@ -27,7 +29,7 @@
let wavesurfer: WaveSurfer;
// Zoom handling
$: zoom = detail ? 1 : undefined;
let zoom = 1;
const setZoom = throttle((level: number) => {
wavesurfer.zoom(level);
}, 250);
@ -276,13 +278,19 @@
{/if}
{#if loaded}
<header>
{#if zoom}
<div class="zoom">
<Icon name="zoom-out" />
<input type="range" min="1" max="50" bind:value={zoom} />
<Icon name="zoom-in" />
</div>
{/if}
<IconButton
name="edit"
title={$i18n.t("Toggle Edit Mode")}
on:click={() => (editable = !editable)}
active={editable}
>
{$i18n.t("Annotate")}
</IconButton>
<div class="zoom">
<Icon name="zoom-out" />
<input type="range" min="1" max="50" bind:value={zoom} />
<Icon name="zoom-in" />
</div>
</header>
{/if}
<div
@ -292,10 +300,8 @@
/>
<div class="wavesurfer" bind:this={containerEl} />
{#if currentAnnotation}
<section class="annotationEditor labelborder">
<header>
<h3>{$i18n.t("Annotation")}</h3>
</header>
<LabelBorder>
<span slot="header">{$i18n.t("Annotation")}</span>
{#if currentAnnotation.attributes["upend-address"]}
<UpObject
link
@ -367,13 +373,12 @@
/>
{/key}
</div>
</section>
</LabelBorder>
{/if}
</div>
<style lang="scss">
@use "../../../styles/colors";
@use "../../util";
.audio {
width: 100%;
@ -381,7 +386,7 @@
header {
display: flex;
justify-content: flex-end;
justify-content: space-between;
& > * {
flex-basis: 50%;
}
@ -395,31 +400,30 @@
}
}
.annotationEditor {
& > * {
margin: 0.5em 0;
}
.baseControls,
.content {
margin: 0.5em 0;
}
.baseControls,
.regionControls,
.existControls {
display: flex;
gap: 0.5em;
}
.baseControls,
.regionControls,
.existControls {
display: flex;
gap: 0.5em;
}
.baseControls {
justify-content: space-between;
}
.baseControls {
justify-content: space-between;
}
.regionControls div {
display: flex;
align-items: center;
gap: 0.25em;
}
.regionControls div {
display: flex;
align-items: center;
gap: 0.25em;
}
input[type="number"] {
width: 6em;
}
input[type="number"] {
width: 6em;
}
.hidden {

View File

@ -7,11 +7,12 @@
import Spinner from "../../utils/Spinner.svelte";
import UpObject from "../UpObject.svelte";
import { ATTR_LABEL } from "upend/constants";
import { i18n } from "../../../i18n";
export let address: string;
export let detail: boolean;
let editable = false; // TODO
let editable = false;
const { entity } = useEntity(address);
@ -219,8 +220,21 @@
{/if}
{#if imageLoaded}
<div class="toolbar">
<IconButton name="brightness-half" on:click={cycleBrightness} />
<IconButton name="tone" on:click={cycleContrast} />
<IconButton
name="edit"
on:click={() => (editable = !editable)}
active={editable}
>
{$i18n.t("Annotate")}
</IconButton>
<div class="image-controls">
<IconButton name="brightness-half" on:click={cycleBrightness}>
{$i18n.t("Brightness")}
</IconButton>
<IconButton name="tone" on:click={cycleContrast}>
{$i18n.t("Contrast")}
</IconButton>
</div>
</div>
{/if}
<div
@ -274,8 +288,12 @@
.toolbar {
display: flex;
justify-content: center;
justify-content: space-between;
margin-bottom: 0.5em;
.image-controls {
display: flex;
}
}
.zoomable {

View File

@ -0,0 +1,60 @@
<script lang="ts">
export let hide = false;
let hidden = true;
</script>
<section class="labelborder" class:hide class:hidden>
<header
on:click={() => {
if (hide) {
hidden = !hidden;
}
}}
>
<slot name="header-full">
<h3><slot name="header" /></h3>
</slot>
</header>
<div class="content">
<slot />
</div>
</section>
<style lang="scss">
section.labelborder {
margin-top: 0.66rem;
header {
display: flex;
align-items: end;
justify-content: space-between;
border-bottom: 1px solid var(--foreground);
padding-bottom: 0.33rem;
margin-bottom: 0.33rem;
h3 {
margin: 0;
}
}
&.hide {
header {
cursor: pointer;
}
transition: opacity 0.2s ease-in-out;
&.hidden {
opacity: 0.66;
header {
border-bottom-width: 0.5px;
}
.content {
display: none;
}
}
}
}
</style>

View File

@ -4,12 +4,11 @@
import { useEntity } from "../../lib/entity";
import type { AttributeCreate, AttributeUpdate } from "../../types/base";
import type { UpEntry } from "upend";
import LabelBorder from "./LabelBorder.svelte";
const dispatch = createEventDispatcher();
export let address: string;
let editable = false;
$: ({ entity } = useEntity(address));
let noteEntry: UpEntry | undefined;
@ -45,26 +44,15 @@
}, 500);
</script>
{#if notes || editable}
<section class="notes labelborder">
<header>
<h3>Notes</h3>
</header>
<div
class="content"
contenteditable={editable ? "true" : "false"}
on:input={update}
bind:this={contentEl}
>
{notes ? notes : ""}
</div>
</section>
{/if}
<LabelBorder hide={!notes?.length}>
<span slot="header">Notes</span>
<div class="notes" contenteditable on:input={update} bind:this={contentEl}>
{notes ? notes : ""}
</div>
</LabelBorder>
<style lang="scss">
@use "../util";
.content {
.notes {
background: var(--background);
border-radius: 4px;
padding: 0.5em !important;