Compare commits
4 Commits
e06d2bccfe
...
7e9d4349af
Author | SHA1 | Date |
---|---|---|
Tomáš Mládek | 7e9d4349af | |
Tomáš Mládek | 426c584215 | |
Tomáš Mládek | 1118a5cfeb | |
Tomáš Mládek | e9dd4d1383 |
|
@ -1,10 +1,12 @@
|
||||||
<script context="module" lang="ts">
|
<script context="module" lang="ts">
|
||||||
import mitt from 'mitt';
|
import mitt from 'mitt';
|
||||||
|
import type { Address } from '@upnd/upend/types';
|
||||||
|
|
||||||
export type AddEvents = {
|
export type AddEvents = {
|
||||||
choose: void;
|
choose: void;
|
||||||
files: File[];
|
files: File[];
|
||||||
urls: string[];
|
urls: string[];
|
||||||
|
destination: Address;
|
||||||
};
|
};
|
||||||
export const addEmitter = mitt<AddEvents>();
|
export const addEmitter = mitt<AddEvents>();
|
||||||
</script>
|
</script>
|
||||||
|
@ -18,18 +20,22 @@
|
||||||
import { i18n } from '$lib/i18n';
|
import { i18n } from '$lib/i18n';
|
||||||
import { selected } from '$lib/components/EntitySelect.svelte';
|
import { selected } from '$lib/components/EntitySelect.svelte';
|
||||||
import Modal from '$lib/components/layout/Modal.svelte';
|
import Modal from '$lib/components/layout/Modal.svelte';
|
||||||
|
import Selector, { type SelectorValue } from '$lib/components/utils/Selector.svelte';
|
||||||
|
import { ATTR_IN } from '@upnd/upend/constants';
|
||||||
|
|
||||||
let files: File[] = [];
|
let files: File[] = [];
|
||||||
let URLs: string[] = [];
|
let URLs: string[] = [];
|
||||||
let uploading = false;
|
let uploading = false;
|
||||||
let abortController: AbortController | undefined;
|
let abortController: AbortController | undefined;
|
||||||
|
|
||||||
|
let destination: Address | undefined;
|
||||||
|
|
||||||
let progress: Record<string, number> = {};
|
let progress: Record<string, number> = {};
|
||||||
let totalProgress: number | undefined;
|
let totalProgress: number | undefined;
|
||||||
|
|
||||||
let filesElement: HTMLDivElement;
|
let filesElement: HTMLDivElement;
|
||||||
|
|
||||||
$: visible = files.length + URLs.length > 0;
|
$: visible = files.length + URLs.length > 0 || destination;
|
||||||
|
|
||||||
addEmitter.on('files', (ev) => {
|
addEmitter.on('files', (ev) => {
|
||||||
ev.forEach((file) => {
|
ev.forEach((file) => {
|
||||||
|
@ -40,6 +46,18 @@
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addEmitter.on('destination', (ev) => {
|
||||||
|
destination = ev;
|
||||||
|
});
|
||||||
|
|
||||||
|
function onDestinationSelected(ev: CustomEvent<SelectorValue | undefined>) {
|
||||||
|
if (ev.detail?.t === 'Address') {
|
||||||
|
destination = ev.detail.c;
|
||||||
|
} else {
|
||||||
|
destination = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function upload() {
|
async function upload() {
|
||||||
uploading = true;
|
uploading = true;
|
||||||
|
|
||||||
|
@ -59,6 +77,16 @@
|
||||||
},
|
},
|
||||||
timeout: -1
|
timeout: -1
|
||||||
});
|
});
|
||||||
|
if (destination) {
|
||||||
|
await api.putEntry({
|
||||||
|
entity: address,
|
||||||
|
attribute: ATTR_IN,
|
||||||
|
value: {
|
||||||
|
t: 'Address',
|
||||||
|
c: destination
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
addresses.push(address);
|
addresses.push(address);
|
||||||
|
|
||||||
if (!uploading) {
|
if (!uploading) {
|
||||||
|
@ -91,6 +119,7 @@
|
||||||
URLs = [];
|
URLs = [];
|
||||||
progress = {};
|
progress = {};
|
||||||
uploading = false;
|
uploading = false;
|
||||||
|
destination = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
function onKeydown(event: KeyboardEvent) {
|
function onKeydown(event: KeyboardEvent) {
|
||||||
|
@ -154,9 +183,20 @@
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<IconButton small disabled={uploading} name="upload" on:click={upload}>
|
<div class="controls-destination">
|
||||||
{$i18n.t('Upload')}
|
<div class="label"><Icon plain name="download" /> {$i18n.t('Destination')}</div>
|
||||||
</IconButton>
|
<Selector
|
||||||
|
initial={destination ? { t: 'Address', c: destination } : undefined}
|
||||||
|
types={['Address', 'NewAddress']}
|
||||||
|
placeholder={$i18n.t('Choose automatically') || ''}
|
||||||
|
on:input={onDestinationSelected}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="controls-submit">
|
||||||
|
<IconButton small disabled={uploading} name="upload" on:click={upload}>
|
||||||
|
{$i18n.t('Upload')}
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{#if uploading}
|
{#if uploading}
|
||||||
<div class="progress">
|
<div class="progress">
|
||||||
|
@ -260,8 +300,23 @@
|
||||||
.controls {
|
.controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
font-size: 3em;
|
font-size: 3em;
|
||||||
margin-top: 0.5rem;
|
margin-top: 0.5rem;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls-destination {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
|
||||||
|
font-size: 1rem;
|
||||||
|
flex-grow: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls-submit {
|
||||||
|
margin: 0 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress {
|
.progress {
|
||||||
|
|
|
@ -26,8 +26,8 @@
|
||||||
|
|
||||||
$: if (adding && selector) selector.focus();
|
$: if (adding && selector) selector.focus();
|
||||||
|
|
||||||
async function add(ev: CustomEvent<SelectorValue>) {
|
async function add(ev: CustomEvent<SelectorValue | undefined>) {
|
||||||
if (ev.detail.t !== 'Address') {
|
if (ev.detail?.t !== 'Address') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
dispatch('add', ev.detail.c);
|
dispatch('add', ev.detail.c);
|
||||||
|
|
|
@ -50,8 +50,8 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function add(ev: CustomEvent<SelectorValue>) {
|
async function add(ev: CustomEvent<SelectorValue | undefined>) {
|
||||||
if (!$entity || ev.detail.t !== 'Attribute') {
|
if (!$entity || ev.detail?.t !== 'Attribute') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -229,9 +229,9 @@
|
||||||
resizeObserver.observe(viewEl as any);
|
resizeObserver.observe(viewEl as any);
|
||||||
});
|
});
|
||||||
|
|
||||||
async function onSelectorInput(ev: CustomEvent<SelectorValue>) {
|
async function onSelectorInput(ev: CustomEvent<SelectorValue | undefined>) {
|
||||||
const value = ev.detail;
|
const value = ev.detail;
|
||||||
if (value.t !== 'Address') return;
|
if (value?.t !== 'Address') return;
|
||||||
const address = value.c;
|
const address = value.c;
|
||||||
|
|
||||||
const [xValue, yValue] = selectorCoords as any;
|
const [xValue, yValue] = selectorCoords as any;
|
||||||
|
@ -261,7 +261,7 @@
|
||||||
types={['Attribute', 'NewAttribute']}
|
types={['Attribute', 'NewAttribute']}
|
||||||
initial={x ? { t: 'Attribute', name: x } : undefined}
|
initial={x ? { t: 'Attribute', name: x } : undefined}
|
||||||
on:input={(ev) => {
|
on:input={(ev) => {
|
||||||
if (ev.detail.t === 'Attribute') x = ev.detail.name;
|
if (ev.detail?.t === 'Attribute') x = ev.detail.name;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div class="value">
|
<div class="value">
|
||||||
|
@ -277,7 +277,7 @@
|
||||||
types={['Attribute', 'NewAttribute']}
|
types={['Attribute', 'NewAttribute']}
|
||||||
initial={y ? { t: 'Attribute', name: y } : undefined}
|
initial={y ? { t: 'Attribute', name: y } : undefined}
|
||||||
on:input={(ev) => {
|
on:input={(ev) => {
|
||||||
if (ev.detail.t === 'Attribute') y = ev.detail.name;
|
if (ev.detail?.t === 'Attribute') y = ev.detail.name;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div class="value">
|
<div class="value">
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="label">
|
<div class="label">
|
||||||
<UpObject {address} {labels} {banner} {select} on:resolved />
|
<UpObject {address} {labels} {banner} {select} link on:resolved />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</UpLink>
|
</UpLink>
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
lastSearched = lastSearched.slice(0, 10);
|
lastSearched = lastSearched.slice(0, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onInput(event: CustomEvent<SelectorValue>) {
|
async function onInput(event: CustomEvent<SelectorValue | undefined>) {
|
||||||
const value = event.detail;
|
const value = event.detail;
|
||||||
if (!value) return;
|
if (!value) return;
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
$: if (editing && selector) selector.focus();
|
$: if (editing && selector) selector.focus();
|
||||||
$: if (!focus && !hover) editing = false;
|
$: if (!focus && !hover) editing = false;
|
||||||
|
|
||||||
function onInput(ev: CustomEvent<SelectorValue>) {
|
function onInput(ev: CustomEvent<SelectorValue | undefined>) {
|
||||||
newValue = ev.detail;
|
newValue = ev.detail;
|
||||||
selector.focus();
|
selector.focus();
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,7 +93,10 @@
|
||||||
import debug from 'debug';
|
import debug from 'debug';
|
||||||
import Spinner from './Spinner.svelte';
|
import Spinner from './Spinner.svelte';
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher<{
|
||||||
|
input: SelectorValue | undefined;
|
||||||
|
focus: boolean;
|
||||||
|
}>();
|
||||||
const dbg = debug('kestrel:Selector');
|
const dbg = debug('kestrel:Selector');
|
||||||
let selectorEl: HTMLElement;
|
let selectorEl: HTMLElement;
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
import Selector, { type SelectorValue } from '../utils/Selector.svelte';
|
import Selector, { type SelectorValue } from '../utils/Selector.svelte';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
import type { WidgetChange } from '$lib/types/base';
|
import type { WidgetChange } from '$lib/types/base';
|
||||||
|
import { addEmitter } from '$lib/components/AddModal.svelte';
|
||||||
import debug from 'debug';
|
import debug from 'debug';
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
const dbg = debug(`kestrel:EntityList`);
|
const dbg = debug(`kestrel:EntityList`);
|
||||||
|
@ -123,7 +124,7 @@
|
||||||
|
|
||||||
$: if (adding && addSelector) addSelector.focus();
|
$: if (adding && addSelector) addSelector.focus();
|
||||||
|
|
||||||
function addEntity(ev: CustomEvent<SelectorValue>) {
|
function addEntity(ev: CustomEvent<SelectorValue | undefined>) {
|
||||||
dbg('Adding entity', ev.detail);
|
dbg('Adding entity', ev.detail);
|
||||||
const addAddress = ev.detail?.t == 'Address' ? ev.detail.c : undefined;
|
const addAddress = ev.detail?.t == 'Address' ? ev.detail.c : undefined;
|
||||||
if (!addAddress) return;
|
if (!addAddress) return;
|
||||||
|
@ -202,26 +203,39 @@
|
||||||
{#if address}
|
{#if address}
|
||||||
<div class="add">
|
<div class="add">
|
||||||
{#if adding}
|
{#if adding}
|
||||||
<Selector
|
<div class="main">
|
||||||
bind:this={addSelector}
|
<Selector
|
||||||
placeholder={$i18n.t('Search database or paste an URL') || ''}
|
bind:this={addSelector}
|
||||||
types={['Address', 'NewAddress']}
|
placeholder={$i18n.t('Add or create an entry') || ''}
|
||||||
on:input={addEntity}
|
types={['Address', 'NewAddress']}
|
||||||
on:focus={(ev) => {
|
on:input={addEntity}
|
||||||
if (!ev.detail) {
|
on:focus={(ev) => {
|
||||||
adding = false;
|
if (!ev.detail) {
|
||||||
}
|
adding = false;
|
||||||
}}
|
}
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<IconButton
|
<div class="main">
|
||||||
name="plus-circle"
|
<IconButton
|
||||||
outline
|
name="plus-circle"
|
||||||
subdued
|
outline
|
||||||
on:click={() => {
|
subdued
|
||||||
adding = true;
|
on:click={() => {
|
||||||
}}
|
adding = true;
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{#if address}
|
||||||
|
<IconButton
|
||||||
|
outline
|
||||||
|
subdued
|
||||||
|
name="upload"
|
||||||
|
title={$i18n.t('Upload a file') || ''}
|
||||||
|
on:click={() => addEmitter.emit('destination', address || '')}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -318,7 +332,13 @@
|
||||||
|
|
||||||
.add {
|
.add {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
gap: 0.5em;
|
||||||
|
|
||||||
|
& .main {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.entitylist.style-grid .add {
|
.entitylist.style-grid .add {
|
||||||
|
|
|
@ -364,7 +364,8 @@
|
||||||
<div class="cell mark-attribute">
|
<div class="cell mark-attribute">
|
||||||
<Selector
|
<Selector
|
||||||
types={['Attribute', 'NewAttribute']}
|
types={['Attribute', 'NewAttribute']}
|
||||||
on:input={(ev) => (newEntryAttribute = ev.detail?.name)}
|
on:input={(ev) =>
|
||||||
|
(newEntryAttribute = ev.detail?.t === 'Attribute' ? ev.detail?.name : '')}
|
||||||
on:focus={(ev) => (addFocus = ev.detail)}
|
on:focus={(ev) => (addFocus = ev.detail)}
|
||||||
keepFocusOnSet
|
keepFocusOnSet
|
||||||
bind:this={newAttrSelector}
|
bind:this={newAttrSelector}
|
||||||
|
|
Loading…
Reference in New Issue