feat: modeless group operations
parent
a5e33a5061
commit
0a5398b0a7
|
@ -12,10 +12,14 @@
|
|||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let entity: Readable<UpObject>;
|
||||
export let editable = false;
|
||||
|
||||
let adding = false;
|
||||
let groupSelector: Selector;
|
||||
|
||||
$: if (adding && groupSelector) groupSelector.focus();
|
||||
|
||||
$: groups = ($entity?.attr[ATTR_IN] || []).map(
|
||||
(e) => [e.value.c as string, e.address] as [string, string]
|
||||
(e) => [e.value.c as string, e.address] as [string, string],
|
||||
);
|
||||
|
||||
let groupToAdd: IValue | undefined;
|
||||
|
@ -38,64 +42,100 @@
|
|||
}
|
||||
|
||||
async function removeGroup(groupAddress: string) {
|
||||
await api.deleteEntry(groupAddress);
|
||||
dispatch("change");
|
||||
if (confirm($i18n.t("Are you sure you want to remove this group?"))) {
|
||||
await api.deleteEntry(groupAddress);
|
||||
dispatch("change");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if groups.length || editable}
|
||||
<section class="groups labelborder">
|
||||
<header><h3>{$i18n.t("Groups")}</h3></header>
|
||||
<div class="content">
|
||||
{#each groups as [groupAddress, groupEntryAddress]}
|
||||
<div
|
||||
class="tag"
|
||||
on:mouseenter={() => dispatch("highlighted", groupAddress)}
|
||||
on:mouseleave={() => dispatch("highlighted", undefined)}
|
||||
>
|
||||
<UpObjectDisplay address={groupAddress} link />
|
||||
{#if editable}
|
||||
<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...")}
|
||||
/>
|
||||
</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)}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
{#if editable}
|
||||
<div class="selector">
|
||||
<Selector
|
||||
type="value"
|
||||
valueTypes={["Address"]}
|
||||
bind:value={groupToAdd}
|
||||
on:input={addGroup}
|
||||
placeholder="Choose an entity..."
|
||||
</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>
|
||||
</section>
|
||||
{/if}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style lang="scss">
|
||||
@use "./util";
|
||||
|
||||
.groups {
|
||||
margin: 0.25rem 0;
|
||||
.content {
|
||||
.group-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem 0.5rem;
|
||||
gap: 0.25rem 0.2rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tag {
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
.no-groups {
|
||||
opacity: 0.66;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -11,7 +11,11 @@
|
|||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let entity: Readable<UpObject>;
|
||||
export let editable = false;
|
||||
|
||||
let adding = false;
|
||||
let typeSelector: Selector;
|
||||
|
||||
$: if (adding && typeSelector) typeSelector.focus();
|
||||
|
||||
$: typeEntries = $entity?.attr[`~${ATTR_OF}`] || [];
|
||||
|
||||
|
@ -45,35 +49,53 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
{#if typeEntries.length || (editable && $entity?.attr["~IN"]?.length)}
|
||||
{#if typeEntries.length || $entity?.attr["~IN"]?.length}
|
||||
<section class="labelborder">
|
||||
<header><h3>{$i18n.t("Type Attributes")}</h3></header>
|
||||
<div class="content">
|
||||
<ul class="attributes">
|
||||
{#each typeEntries as typeEntry}
|
||||
<li class="attribute">
|
||||
<div class="label">
|
||||
<UpObjectDisplay address={typeEntry.entity} link />
|
||||
</div>
|
||||
<div class="controls">
|
||||
{#if editable}
|
||||
{#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)}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{#if editable}
|
||||
<Selector
|
||||
type="attribute"
|
||||
bind:attribute={attributeToAdd}
|
||||
on:input={add}
|
||||
placeholder={$i18n.t("Assign an attribute to this type...")}
|
||||
/>
|
||||
{/if}
|
||||
</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>
|
||||
</div>
|
||||
</section>
|
||||
{/if}
|
||||
|
@ -92,6 +114,24 @@
|
|||
display: flex;
|
||||
}
|
||||
|
||||
.body {
|
||||
display: flex;
|
||||
align-items: start;
|
||||
|
||||
.attributes {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.selector {
|
||||
width: 100%;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.no-attributes {
|
||||
opacity: 0.66;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
export let title: string | undefined = undefined;
|
||||
export let outline = false;
|
||||
export let subdued = false;
|
||||
export let small = false;
|
||||
export let color: string | undefined = "var(--active-color, var(--primary))";
|
||||
</script>
|
||||
|
||||
|
@ -15,6 +16,7 @@
|
|||
class:active
|
||||
class:outline
|
||||
class:subdued
|
||||
class:small
|
||||
{disabled}
|
||||
{title}
|
||||
style="--color: {color}"
|
||||
|
@ -55,6 +57,9 @@
|
|||
border-radius: 4px;
|
||||
|
||||
padding: 0.25em 1em;
|
||||
&.small {
|
||||
padding: 0.1em 0.8em;
|
||||
}
|
||||
}
|
||||
|
||||
.active,
|
||||
|
|
Loading…
Reference in New Issue