2024-01-22 13:12:21 +01:00
|
|
|
<script lang="ts">
|
|
|
|
import { i18n } from '../i18n';
|
|
|
|
import EntitySetEditor from './EntitySetEditor.svelte';
|
|
|
|
import EntryView from './EntryView.svelte';
|
|
|
|
import Icon from './utils/Icon.svelte';
|
|
|
|
import EntityList from './widgets/EntityList.svelte';
|
|
|
|
import api from '$lib/api';
|
|
|
|
import { Query } from '@upnd/upend';
|
|
|
|
import { ATTR_IN } from '@upnd/upend/constants';
|
|
|
|
import { createEventDispatcher } from 'svelte';
|
|
|
|
import { Any } from '@upnd/upend/query';
|
2024-01-22 20:33:12 +01:00
|
|
|
import type { Widget } from '$lib/components/EntryView.svelte';
|
2024-01-22 13:12:21 +01:00
|
|
|
const dispatch = createEventDispatcher();
|
|
|
|
|
|
|
|
export let spec: string;
|
|
|
|
|
|
|
|
const individualSpecs = spec.split(/(?=[+=-])/);
|
|
|
|
let includedGroups = individualSpecs.filter((s) => s.startsWith('+')).map((s) => s.slice(1));
|
|
|
|
let requiredGroups = individualSpecs.filter((s) => s.startsWith('=')).map((s) => s.slice(1));
|
|
|
|
let excludedGroups = individualSpecs.filter((s) => s.startsWith('-')).map((s) => s.slice(1));
|
|
|
|
|
|
|
|
$: if (
|
|
|
|
includedGroups.length === 0 &&
|
|
|
|
requiredGroups.length === 0 &&
|
|
|
|
excludedGroups.length === 0
|
|
|
|
) {
|
|
|
|
dispatch('close');
|
|
|
|
}
|
|
|
|
|
2024-01-22 20:33:12 +01:00
|
|
|
const combinedWidgets: Widget[] = [
|
2024-01-22 13:12:21 +01:00
|
|
|
{
|
|
|
|
name: 'List',
|
|
|
|
icon: 'list-check',
|
|
|
|
components: ({ entities }) => [
|
|
|
|
{
|
|
|
|
component: EntityList,
|
|
|
|
props: {
|
|
|
|
entities,
|
|
|
|
thumbnails: false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
]
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: 'EntityList',
|
|
|
|
icon: 'image',
|
|
|
|
components: ({ entities }) => [
|
|
|
|
{
|
|
|
|
component: EntityList,
|
|
|
|
props: {
|
|
|
|
entities,
|
|
|
|
thumbnails: true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
];
|
|
|
|
|
2024-01-22 20:33:12 +01:00
|
|
|
let resultEntities: string[] = [];
|
2024-01-22 13:12:21 +01:00
|
|
|
async function updateResultEntities(
|
|
|
|
includedGroups: string[],
|
|
|
|
requiredGroups: string[],
|
|
|
|
excludedGroups: string[]
|
|
|
|
) {
|
|
|
|
const included = includedGroups.length
|
|
|
|
? (
|
|
|
|
await api.query(
|
|
|
|
Query.matches(
|
|
|
|
Any,
|
|
|
|
ATTR_IN,
|
|
|
|
includedGroups.map((g) => `@${g}`)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
).objects
|
|
|
|
: [];
|
|
|
|
const required = requiredGroups.length
|
|
|
|
? (
|
|
|
|
await api.query(
|
|
|
|
Query.matches(
|
|
|
|
Any,
|
|
|
|
ATTR_IN,
|
|
|
|
requiredGroups.map((g) => `@${g}`)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
).objects
|
|
|
|
: [];
|
|
|
|
const excluded = excludedGroups.length
|
|
|
|
? (
|
|
|
|
await api.query(
|
|
|
|
Query.matches(
|
|
|
|
Any,
|
|
|
|
ATTR_IN,
|
|
|
|
excludedGroups.map((g) => `@${g}`)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
).objects
|
|
|
|
: [];
|
|
|
|
resultEntities = (Object.keys(included).length ? Object.keys(included) : Object.keys(required))
|
|
|
|
.filter((e) => !requiredGroups.length || Object.keys(required).includes(e))
|
|
|
|
.filter((e) => !Object.keys(excluded).includes(e));
|
|
|
|
}
|
|
|
|
$: updateResultEntities(includedGroups, requiredGroups, excludedGroups);
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<div class="view" data-address-multi={resultEntities}>
|
|
|
|
<h2>
|
|
|
|
<Icon plain name="intersect" />
|
|
|
|
{$i18n.t('Combine')}
|
|
|
|
</h2>
|
|
|
|
<div class="controls">
|
|
|
|
<EntitySetEditor
|
|
|
|
entities={includedGroups}
|
2024-01-22 20:33:12 +01:00
|
|
|
header={$i18n.t('Include') || ''}
|
2024-01-22 13:12:21 +01:00
|
|
|
confirmRemoveMessage={null}
|
|
|
|
on:add={(ev) => (includedGroups = [...includedGroups, ev.detail])}
|
|
|
|
on:remove={(ev) => (includedGroups = includedGroups.filter((e) => e !== ev.detail))}
|
|
|
|
/>
|
|
|
|
<EntitySetEditor
|
|
|
|
entities={requiredGroups}
|
2024-01-22 20:33:12 +01:00
|
|
|
header={$i18n.t('Require') || ''}
|
2024-01-22 13:12:21 +01:00
|
|
|
confirmRemoveMessage={null}
|
|
|
|
on:add={(ev) => (requiredGroups = [...requiredGroups, ev.detail])}
|
|
|
|
on:remove={(ev) => (requiredGroups = requiredGroups.filter((e) => e !== ev.detail))}
|
|
|
|
/>
|
|
|
|
<EntitySetEditor
|
|
|
|
entities={excludedGroups}
|
2024-01-22 20:33:12 +01:00
|
|
|
header={$i18n.t('Exclude') || ''}
|
2024-01-22 13:12:21 +01:00
|
|
|
confirmRemoveMessage={null}
|
|
|
|
on:add={(ev) => (excludedGroups = [...excludedGroups, ev.detail])}
|
|
|
|
on:remove={(ev) => (excludedGroups = excludedGroups.filter((e) => e !== ev.detail))}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<div class="entities">
|
|
|
|
<EntryView
|
2024-01-22 20:33:12 +01:00
|
|
|
title={$i18n.t('Matching entities') || ''}
|
2024-01-22 13:12:21 +01:00
|
|
|
entities={resultEntities}
|
|
|
|
widgets={combinedWidgets}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<style lang="scss">
|
|
|
|
.view {
|
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
height: 100%;
|
|
|
|
}
|
|
|
|
|
|
|
|
h2 {
|
|
|
|
text-align: center;
|
|
|
|
margin: 0;
|
|
|
|
margin-top: -0.66em;
|
|
|
|
}
|
|
|
|
|
|
|
|
.controls {
|
|
|
|
margin-bottom: 1rem;
|
|
|
|
}
|
|
|
|
|
|
|
|
.entities {
|
|
|
|
flex-grow: 1;
|
|
|
|
overflow-y: auto;
|
|
|
|
height: 0;
|
|
|
|
}
|
|
|
|
</style>
|