resource.resource_page_number
diff --git a/apps/dashboard/src/app/resources/resource-list.component.ts b/apps/dashboard/src/app/resources/resource-list.component.ts
index f68df652d..a6d311cad 100644
--- a/apps/dashboard/src/app/resources/resource-list.component.ts
+++ b/apps/dashboard/src/app/resources/resource-list.component.ts
@@ -322,6 +322,7 @@ export class ResourceListComponent implements OnInit, OnDestroy {
.openConfirm({
title,
description: message,
+ confirmLabel: 'generic.delete',
isDestructive: true,
})
.onClose.pipe(
diff --git a/apps/dashboard/src/app/resources/resource-viewer.service.ts b/apps/dashboard/src/app/resources/resource-viewer.service.ts
index fc3793f00..e5fcc39f8 100644
--- a/apps/dashboard/src/app/resources/resource-viewer.service.ts
+++ b/apps/dashboard/src/app/resources/resource-viewer.service.ts
@@ -1,23 +1,28 @@
-import { filter, map, switchMap, take, tap } from 'rxjs';
+import { filter, map, Observable, switchMap, take, tap } from 'rxjs';
import { SisModalService } from '@nuclia/sistema';
import { SDKService, STFTrackingService } from '@flaps/core';
-import { Injectable } from '@angular/core';
+import { Injectable, NgZone } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { Router } from '@angular/router';
+import { FieldFullId } from '@nuclia/core';
@Injectable({
providedIn: 'root',
})
export class ResourceViewerService {
+ private widgetId?: string;
+
constructor(
private router: Router,
private sdk: SDKService,
private translation: TranslateService,
private modalService: SisModalService,
private trackingService: STFTrackingService,
+ private zone: NgZone,
) {}
init(widgetId: string) {
+ this.widgetId = widgetId;
this.sdk.currentKb.pipe(take(1)).subscribe((kb) => {
const waitForWidget = window.setInterval(() => {
const widget = document.getElementById(widgetId) as unknown as any;
@@ -32,10 +37,25 @@ export class ResourceViewerService {
if (kb.admin || kb.contrib) {
actions.push(
{
- label: this.translation.instant('generic.edit'),
+ label: this.translation.instant('resource.menu.edit'),
destructive: false,
action: this.edit.bind(this),
},
+ {
+ label: this.translation.instant('resource.menu.annotate'),
+ destructive: false,
+ action: this.annotate.bind(this),
+ },
+ {
+ label: this.translation.instant('resource.menu.classify'),
+ destructive: false,
+ action: this.classify.bind(this),
+ },
+ {
+ label: this.translation.instant('generic.reindex'),
+ destructive: false,
+ action: this.reindex.bind(this),
+ },
{
label: this.translation.instant('generic.delete'),
destructive: true,
@@ -43,7 +63,7 @@ export class ResourceViewerService {
},
);
}
- widget.setActions(actions);
+ widget.setTileMenu(actions);
widget.addEventListener('search', () => this.trackingService.logEvent('search'));
clearInterval(waitForWidget);
}
@@ -60,48 +80,77 @@ export class ResourceViewerService {
});
}
- delete(uid: string) {
+ delete(fullId: FieldFullId) {
this.modalService
.openConfirm({
- title: 'generic.alert',
+ title: 'resource.delete_resource_title',
description: 'resource.delete_resource_warning',
confirmLabel: 'generic.delete',
isDestructive: true,
})
.onClose.pipe(
filter((confirm) => !!confirm),
+ tap(() => this.closeViewer()),
switchMap(() => this.sdk.currentKb),
take(1),
- switchMap((kb) => kb.getResource(uid)),
+ switchMap((kb) => kb.getResource(fullId.resourceId)),
switchMap((res) => res.delete()),
- tap(() => this.closeViewer()),
)
.subscribe(() => {
+ this.reloadSearch();
setTimeout(() => {
this.sdk.refreshCounter(true);
}, 1000);
});
}
- edit(uid: string) {
- this.sdk.currentKb
- .pipe(
+ edit(fullId: FieldFullId) {
+ this.getResourcesBasePath().subscribe((basePath) => {
+ this.closeViewer();
+ this.navigateTo(`${basePath}/${fullId.resourceId}/edit`);
+ });
+ }
+
+ annotate(fullId: FieldFullId) {
+ this.getResourcesBasePath().subscribe((basePath) => {
+ this.closeViewer();
+ this.navigateTo(`${basePath}/${fullId.resourceId}/edit/annotation`);
+ });
+ }
+
+ classify(fullId: FieldFullId) {
+ this.getResourcesBasePath().subscribe((basePath) => {
+ this.closeViewer();
+ this.navigateTo(`${basePath}/${fullId.resourceId}/edit/classification`);
+ });
+ }
+
+ reindex(fullId: FieldFullId) {
+ this.modalService
+ .openConfirm({
+ title: 'resource.reprocess_resource_title',
+ description: 'resource.reprocess_resource_description',
+ })
+ .onClose.pipe(
+ filter((confirm) => !!confirm),
+ tap(() => this.closeViewer()),
+ switchMap(() => this.sdk.currentKb),
take(1),
- filter((kb) => !!kb.admin || !!kb.contrib),
+ switchMap((kb) => kb.getResource(fullId.resourceId)),
+ switchMap((res) => res.reprocess()),
)
- .subscribe((kb) => {
- this.closeViewer();
- this.router.navigate([`/at/${kb.account}/${kb.slug}/resources/${uid}/edit/profile`]);
- });
+ .subscribe();
}
- showUID(uid: string) {
+ showUID(fullId: FieldFullId) {
this.sdk.currentKb
.pipe(
take(1),
map(
(kb) =>
- `${this.sdk.nuclia.rest.getFullUrl(kb.path)}/resource/${uid}
`,
+ `${this.sdk.nuclia.rest.getFullUrl(kb.path)}/resource/${
+ fullId.resourceId
+ }
`,
),
switchMap(
(uidEndpoint) =>
@@ -117,6 +166,26 @@ export class ResourceViewerService {
}
closeViewer() {
- (document.getElementById('search-widget') as unknown as any)?.displayResource('');
+ if (this.widgetId) {
+ (document.getElementById(this.widgetId) as unknown as any)?.closePreview();
+ }
+ }
+
+ reloadSearch() {
+ const searchBar = document.querySelector('nuclia-search-bar') as any;
+ if (typeof searchBar?.reloadSearch === 'function') {
+ searchBar.reloadSearch();
+ }
+ }
+
+ private getResourcesBasePath(): Observable {
+ return this.sdk.currentKb.pipe(
+ take(1),
+ filter((kb) => !!kb.admin || !!kb.contrib),
+ map((kb) => `/at/${kb.account}/${kb.slug}/resources`),
+ );
+ }
+ private navigateTo(path: string) {
+ this.zone.run(() => this.router.navigate([path]));
}
}
diff --git a/apps/dashboard/src/app/search/search.component.spec.ts b/apps/dashboard/src/app/search/search.component.spec.ts
index 133b453fb..d2b344116 100644
--- a/apps/dashboard/src/app/search/search.component.spec.ts
+++ b/apps/dashboard/src/app/search/search.component.spec.ts
@@ -6,6 +6,7 @@ import { BackendConfigurationService, SDKService } from '@flaps/core';
import { TranslateService } from '@ngx-translate/core';
import { of } from 'rxjs';
import { Nuclia, WritableKnowledgeBox } from '@nuclia/core';
+import { ResourceViewerService } from '../resources/resource-viewer.service';
describe('SearchComponent', () => {
let component: SearchComponent;
@@ -31,6 +32,7 @@ describe('SearchComponent', () => {
}),
MockProvider(BackendConfigurationService),
MockProvider(TranslateService),
+ MockProvider(ResourceViewerService),
],
}).compileComponents();
diff --git a/apps/dashboard/src/app/search/search.component.ts b/apps/dashboard/src/app/search/search.component.ts
index 1dd226380..5bfa71205 100644
--- a/apps/dashboard/src/app/search/search.component.ts
+++ b/apps/dashboard/src/app/search/search.component.ts
@@ -1,10 +1,11 @@
-import { ChangeDetectionStrategy, Component, OnDestroy } from '@angular/core';
+import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core';
import { BackendConfigurationService, SDKService } from '@flaps/core';
import { distinctUntilKeyChanged, forkJoin, map, switchMap, tap } from 'rxjs';
import { DEFAULT_FEATURES_LIST } from '../widgets/widget-features';
import { DomSanitizer } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core';
import { TrainingType } from '@nuclia/core';
+import { ResourceViewerService } from '../resources/resource-viewer.service';
const searchWidgetId = 'search-bar';
const searchResultsId = 'search-results';
@@ -15,7 +16,7 @@ const searchResultsId = 'search-results';
styleUrls: ['./search.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
-export class SearchComponent implements OnDestroy {
+export class SearchComponent implements OnDestroy, OnInit {
searchWidget = this.sdk.currentKb.pipe(
distinctUntilKeyChanged('id'),
tap(() => {
@@ -59,15 +60,20 @@ export class SearchComponent implements OnDestroy {
private sanitized: DomSanitizer,
private backendConfig: BackendConfigurationService,
private translation: TranslateService,
+ private viewerService: ResourceViewerService,
) {}
+ ngOnInit() {
+ this.viewerService.init(searchResultsId);
+ }
+
ngOnDestroy() {
const searchBarElement = document.querySelector('nuclia-search-bar') as any;
const searchResultsElement = document.querySelector('nuclia-search-results') as any;
- if (typeof searchBarElement.$destroy === 'function') {
+ if (typeof searchBarElement?.$destroy === 'function') {
searchBarElement.$destroy();
}
- if (typeof searchResultsElement.$destroy === 'function') {
+ if (typeof searchResultsElement?.$destroy === 'function') {
searchResultsElement.$destroy();
}
}
diff --git a/apps/search-widget-demo/src/App.svelte b/apps/search-widget-demo/src/App.svelte
index fdc8c34cf..d5cbadaa0 100644
--- a/apps/search-widget-demo/src/App.svelte
+++ b/apps/search-widget-demo/src/App.svelte
@@ -3,25 +3,60 @@
import { Button, IconButton, Label } from '../../../libs/search-widget/src/common';
import { NucliaViewerWidget } from '../../../libs/search-widget/src/widgets/viewer-widget';
import { NucliaSearchBar, NucliaSearchResults } from '../../../libs/search-widget/src/widgets/search-widget';
+ import type { FieldFullId } from '@nuclia/core';
let selected = 'tiles';
let showConfiguration = true;
let searchBar: NucliaSearchBar;
let viewerWidget: NucliaViewerWidget;
+ let resultsWidget: NucliaSearchResults;
let resource = '20fd69d4b4dcdf0eb9e8c95dfff1ce6c';
let fieldType = 'file';
let fieldId = '20fd69d4b4dcdf0eb9e8c95dfff1ce6c';
- /**
- * Classifier_test kb (already trained, owned by Carmen): cbb4afd0-26e6-480a-a814-4e08398bdf3e
- * Kb with different kind of media (owned by Mat): 5c2bc432-a579-48cd-b408-4271e5e7a43c
- */
- let kb = 'eda3f482-d432-4fac-913a-00f0a4696fd4'; // pdfs
+ // let kb = 'eda3f482-d432-4fac-913a-00f0a4696fd4'; // pdfs
// let kb = '5c2bc432-a579-48cd-b408-4271e5e7a43c'; // medias
// let kb = 'f5d0ec7f-9ac3-46a3-b284-a38d5333d9e6'; // le petit prince
// let kb = '49e0c43e-7beb-4418-94fa-ed90226f365c'; // la classe américaine
- // let kb = '89ffdada-58ee-4199-8303-ad1450de1cbe'; // word, excel, csv, images,…
+ let kb = '89ffdada-58ee-4199-8303-ad1450de1cbe'; // word, excel, csv, images,…
+
+ onMount(() => {
+ resultsWidget?.setTileMenu([
+ {
+ label: 'Delete',
+ action: (fullId: FieldFullId) => {
+ console.log('delete', fullId);
+ },
+ },
+ {
+ label: 'Edit',
+ action: (fullId: FieldFullId) => {
+ console.log('edit', fullId);
+ },
+ },
+ ]);
+ viewerWidget?.setTileMenu([
+ {
+ label: 'Delete',
+ action: (fullId: FieldFullId) => {
+ console.log('delete', fullId);
+ },
+ },
+ {
+ label: 'Edit',
+ action: (fullId: FieldFullId) => {
+ console.log('edit', fullId);
+ },
+ },
+ {
+ label: 'Close',
+ action: () => {
+ viewerWidget.closePreview();
+ },
+ },
+ ]);
+ });
@@ -60,7 +95,7 @@
lang="en"
placeholder="Search"
features="filter,suggestions,permalink" />
-
+
{/if}
{#if selected === 'viewer'}
diff --git a/libs/common/src/assets/i18n/ca.json b/libs/common/src/assets/i18n/ca.json
index df7b8f721..a220ec9d9 100644
--- a/libs/common/src/assets/i18n/ca.json
+++ b/libs/common/src/assets/i18n/ca.json
@@ -431,9 +431,11 @@
"resource.classification.no-resource-labelset": "No hi ha cap etiqueta establerta per als recursos. Podeu afegir-ne alguns",
"resource.classification.remove-label": "Elimina l'etiqueta de la selecció",
"resource.created": "Data creació",
- "resource.delete_resource_confirm": "Vols suprimir el document?",
- "resource.delete_resource_warning": "Si elimines aquest document també s’esborrarà tota la informació processada.",
- "resource.delete_resources_confirm": "Vols suprimir documents?",
+ "resource.delete_resource_confirm": "Vols suprimir el recurs?",
+ "resource.delete_resource_title": "Esteu segur que voleu suprimir el recurs?",
+ "resource.delete_resource_warning": "Si suprimiu aquest recurs, tota la informació, camps i entitats creades durant el seu processament desapareixeran. No podeu desfer aquesta acció.",
+ "resource.delete_resources_confirm": "Vols suprimir recursos?",
+ "resource.delete_resources_title": "Esteu segur que voleu suprimir els recursos?",
"resource.delete_resources_warning": "Si elimines aquests documents també s’esborrarà tota la informació processada.",
"resource.description.title": "Descripció",
"resource.empty": "No hi ha contingut al vostre caixa de coneixement en aquest moment.",
@@ -486,6 +488,8 @@
"resource.processed_resources": "Recursos processats",
"resource.profile": "Recurs",
"resource.reindex-all-info": "S'ha iniciat el reprocessament de tots els recursos per error. Això pot trigar una mica.",
+ "resource.reprocess_resource_description": "Necessitem un temps per processar les dades. Un cop reprocessat el podeu veure a la pàgina de la llista de recursos disponible per editar, classificar o anotar.",
+ "resource.reprocess_resource_title": "Esteu segur que voleu tornar a processar el recurs?",
"resource.resource_page_number": "{{num}} per pàgina",
"resource.resources": "Recursos",
"resource.save-successful": "S'ha guardat el recurs",
diff --git a/libs/common/src/assets/i18n/en.json b/libs/common/src/assets/i18n/en.json
index 616bc2097..504733152 100644
--- a/libs/common/src/assets/i18n/en.json
+++ b/libs/common/src/assets/i18n/en.json
@@ -476,9 +476,11 @@
"resource.classification.no-resource-labelset": "There is no label set for resources. You can add some in",
"resource.classification.remove-label": "Remove label from selection",
"resource.created": "Created",
- "resource.delete_resource_confirm": "Delete document?",
- "resource.delete_resource_warning": "Deleting this document will also delete all processed information.",
- "resource.delete_resources_confirm": "Delete documents?",
+ "resource.delete_resource_confirm": "Delete resource?",
+ "resource.delete_resource_title": "Are you sure you want to delete the resource?",
+ "resource.delete_resource_warning": "If you delete this resource all information, fields, and entities created during its processing will disappear. You cannot undo this action. ",
+ "resource.delete_resources_confirm": "Delete resources?",
+ "resource.delete_resources_title": "Are you sure you want to delete the resources?",
"resource.delete_resources_warning": "Deleting these documents will also delete all processed information.",
"resource.description.title": "Description",
"resource.empty": "There is no content in your knowledge box at the moment.",
@@ -531,6 +533,8 @@
"resource.processed_resources": "Processed resources",
"resource.profile": "Resource",
"resource.reindex-all-info": "Reprocessing all resources in error started. This may take some time.",
+ "resource.reprocess_resource_description": "We need some time to process the data. Once reprocessed you can see it on the resource list page available for editing, classifying or annotating.",
+ "resource.reprocess_resource_title": "Are you sure you want to reprocess the resource?",
"resource.resource_page_number": "{{num}} per page",
"resource.resources": "Resources",
"resource.save-successful": "Resource saved",
diff --git a/libs/common/src/assets/i18n/es.json b/libs/common/src/assets/i18n/es.json
index b55b5d140..2c78caf14 100644
--- a/libs/common/src/assets/i18n/es.json
+++ b/libs/common/src/assets/i18n/es.json
@@ -431,9 +431,11 @@
"resource.classification.no-resource-labelset": "No hay ninguna etiqueta establecida para los recursos. Puedes agregar algunos en",
"resource.classification.remove-label": "Eliminar etiqueta de la selección",
"resource.created": "Data de creación",
- "resource.delete_resource_confirm": "¿Eliminar documento?",
- "resource.delete_resource_warning": "Si eliminas este documento también se borrará toda la información procesada.",
- "resource.delete_resources_confirm": "¿Eliminar documentos?",
+ "resource.delete_resource_confirm": "¿Eliminar recurso?",
+ "resource.delete_resource_title": "¿Está seguro de que desea eliminar el recurso?",
+ "resource.delete_resource_warning": "Si elimina este recurso, toda la información, los campos y las entidades creadas durante su procesamiento desaparecerán. No puede deshacer esta acción.",
+ "resource.delete_resources_confirm": "¿Eliminar recursos?",
+ "resource.delete_resources_title": "¿Está seguro de que desea eliminar los recursos?",
"resource.delete_resources_warning": "Si eliminas estos documentos también se borrará toda la información procesada.",
"resource.description.title": "Descripción",
"resource.empty": "No hay contenido en su caja de conocimiento en este momento.",
@@ -486,6 +488,8 @@
"resource.processed_resources": "Recursos procesados",
"resource.profile": "Recurso",
"resource.reindex-all-info": "Se inició el reprocesamiento de todos los recursos por error. Esto puede tomar algo de tiempo.",
+ "resource.reprocess_resource_description": "Necesitamos algo de tiempo para procesar los datos. Una vez reprocesado, puede verlo en la página de la lista de recursos disponible para editar, clasificar o anotar.",
+ "resource.reprocess_resource_title": "¿Está seguro de que desea volver a procesar el recurso?",
"resource.resource_page_number": "{{num}} por página",
"resource.resources": "Recursos",
"resource.save-successful": "Recurso guardado",
diff --git a/libs/sdk-core/CHANGELOG.md b/libs/sdk-core/CHANGELOG.md
index ae3773bfe..a885852a1 100644
--- a/libs/sdk-core/CHANGELOG.md
+++ b/libs/sdk-core/CHANGELOG.md
@@ -1,3 +1,9 @@
+# 1.1.5 (2023-03-03)
+
+### Improvements
+
+- Add `FieldFullId` interface to `resource.models`
+
# 1.1.4 (2023-03-01)
### Improvements
diff --git a/libs/sdk-core/package.json b/libs/sdk-core/package.json
index c085e5c30..5f2328251 100644
--- a/libs/sdk-core/package.json
+++ b/libs/sdk-core/package.json
@@ -1,6 +1,6 @@
{
"name": "@nuclia/core",
- "version": "1.1.4",
+ "version": "1.1.5",
"description": "SDK allowing to integrate Nuclia services in your frontend application",
"license": "MIT",
"keywords": [
diff --git a/libs/sdk-core/src/lib/db/resource/resource.models.ts b/libs/sdk-core/src/lib/db/resource/resource.models.ts
index c31df3240..d160662c2 100644
--- a/libs/sdk-core/src/lib/db/resource/resource.models.ts
+++ b/libs/sdk-core/src/lib/db/resource/resource.models.ts
@@ -112,6 +112,10 @@ export interface FieldId {
field_id: string;
}
+export interface FieldFullId extends FieldId {
+ resourceId: string;
+}
+
export interface ResourceField extends IFieldData, FieldId {}
export class FileFieldData implements IFieldData {
diff --git a/libs/search-widget/CHANGELOG.md b/libs/search-widget/CHANGELOG.md
new file mode 100644
index 000000000..b0c3a8a89
--- /dev/null
+++ b/libs/search-widget/CHANGELOG.md
@@ -0,0 +1,10 @@
+# 1.1.0 (2023-03-03)
+
+### Feature
+
+- Export a method `setTileMenu` from `SearchResults` and `ViewerWidget` components allowing to set a dropdown menu which will be available by clicking on a button in expanded tile header.
+- Export a method `closePreview` from `SearchResults` and fix the one already available in `ViewerWidget` allowing to close the tile currently expanded.
+
+# 1.0.0 (2023-03-01)
+
+- First stable release
diff --git a/libs/search-widget/package.json b/libs/search-widget/package.json
index 0c482cf0e..5fa81c3cd 100644
--- a/libs/search-widget/package.json
+++ b/libs/search-widget/package.json
@@ -1,6 +1,6 @@
{
"name": "@nuclia/widget",
- "version": "1.0.0",
+ "version": "1.1.0",
"description": "Nuclia search widgets",
"module": "nuclia.mjs",
"main": "nuclia.umd.js",
@@ -18,7 +18,7 @@
"url": "https://github.com/nuclia/frontend/issues"
},
"peerDependencies": {
- "@nuclia/core": "^1.1.4",
+ "@nuclia/core": "^1.1.5",
"@nuclia/prediction": "^1.0.0",
"date-fns": "^2.29.3",
"rxjs": "^7.5.2",
diff --git a/libs/search-widget/src/common/dropdown/Dropdown.svelte b/libs/search-widget/src/common/dropdown/Dropdown.svelte
index 32bfed499..7f85dbef3 100644
--- a/libs/search-widget/src/common/dropdown/Dropdown.svelte
+++ b/libs/search-widget/src/common/dropdown/Dropdown.svelte
@@ -3,7 +3,7 @@
import { clickOutside } from '../actions/actions';
import { freezeBackground, unblockBackground } from '../modal/modal.utils';
- export let position: { top: number; left: number } | undefined = undefined;
+ export let position: { top: number; left: number; width?: number } | undefined = undefined;
export let secondary = false;
onMount(() => {
@@ -25,6 +25,7 @@
class="sw-dropdown"
style:left={position?.left + 'px'}
style:top={position?.top + 'px'}
+ style:width={position?.width + 'px'}
use:clickOutside
on:outclick={close}>
diff --git a/libs/search-widget/src/core/api.ts b/libs/search-widget/src/core/api.ts
index 7de71cc51..a06128125 100644
--- a/libs/search-widget/src/core/api.ts
+++ b/libs/search-widget/src/core/api.ts
@@ -1,6 +1,7 @@
import type {
Classification,
Entity,
+ FieldFullId,
IResource,
KBStates,
LabelSets,
@@ -19,7 +20,7 @@ import {
} from '@nuclia/core';
import type { Observable } from 'rxjs';
import { filter, forkJoin, map, merge, of, take, tap } from 'rxjs';
-import type { EntityGroup, FieldFullId, WidgetOptions } from './models';
+import type { EntityGroup, WidgetOptions } from './models';
import { generatedEntitiesColor, getCDN } from './utils';
import { _ } from './i18n';
import type { Annotation } from './stores/annotation.store';
diff --git a/libs/search-widget/src/core/models.ts b/libs/search-widget/src/core/models.ts
index 7d491836b..414c6dbaa 100644
--- a/libs/search-widget/src/core/models.ts
+++ b/libs/search-widget/src/core/models.ts
@@ -2,7 +2,7 @@ import type {
Classification,
CloudLink,
FIELD_TYPE,
- FieldId,
+ FieldFullId,
IResource,
Paragraph,
Search,
@@ -26,7 +26,7 @@ export interface WidgetOptions {
export interface WidgetAction {
label: string;
destructive?: boolean;
- action: (uid: string) => void;
+ action: (fullId: FieldFullId) => void;
}
/**
@@ -38,10 +38,6 @@ export interface DisplayedResource {
sentence?: Search.Paragraph;
}
-export interface FieldFullId extends FieldId {
- resourceId: string;
-}
-
export enum PreviewKind {
NONE,
PDF,
diff --git a/libs/search-widget/src/core/stores/viewer.store.ts b/libs/search-widget/src/core/stores/viewer.store.ts
index 77eb7f0d2..308c5ffae 100644
--- a/libs/search-widget/src/core/stores/viewer.store.ts
+++ b/libs/search-widget/src/core/stores/viewer.store.ts
@@ -1,6 +1,6 @@
-import type { FieldFullId, MediaWidgetParagraph, PreviewKind } from '../models';
+import type { MediaWidgetParagraph, PreviewKind } from '../models';
import { SvelteState } from '../state-lib';
-import type { IFieldData, ResourceField } from '@nuclia/core';
+import type { FieldFullId, IFieldData, ResourceField } from '@nuclia/core';
import { FIELD_TYPE, FileFieldData, LinkFieldData, sliceUnicode } from '@nuclia/core';
import { getFileUrls } from '../api';
import type { Observable } from 'rxjs';
diff --git a/libs/search-widget/src/tiles/ImageTile.svelte b/libs/search-widget/src/tiles/ImageTile.svelte
index 4308c57b0..a2618aab2 100644
--- a/libs/search-widget/src/tiles/ImageTile.svelte
+++ b/libs/search-widget/src/tiles/ImageTile.svelte
@@ -23,7 +23,7 @@
showImage()}>
diff --git a/libs/search-widget/src/tiles/base-tile/BaseTile.svelte b/libs/search-widget/src/tiles/base-tile/BaseTile.svelte
index 7f9cfb5b4..748b42991 100644
--- a/libs/search-widget/src/tiles/base-tile/BaseTile.svelte
+++ b/libs/search-widget/src/tiles/base-tile/BaseTile.svelte
@@ -19,6 +19,7 @@
of,
Subject,
take,
+ takeUntil,
} from 'rxjs';
import { searchQuery } from '../../core/stores/search.store';
import { Duration } from '../../common/transition.utils';
@@ -47,6 +48,7 @@
export let noResultNavigator = false;
const dispatch = createEventDispatcher();
+ const unsubscribeAll = new Subject();
let innerWidth = window.innerWidth;
const closeButtonWidth = 48;
@@ -108,9 +110,20 @@
) {
openParagraph(undefined, -1);
}
+
+ isPreviewing
+ .pipe(
+ filter((previewOpen) => !previewOpen && expanded),
+ takeUntil(unsubscribeAll),
+ )
+ .subscribe(() => closePreview());
});
- onDestroy(() => reset());
+ onDestroy(() => {
+ unsubscribeAll.next(true);
+ unsubscribeAll.complete();
+ reset();
+ });
const onClickParagraph = (paragraph, index) => {
if (result.field) {
diff --git a/libs/search-widget/src/tiles/base-tile/header/TileHeader.scss b/libs/search-widget/src/tiles/base-tile/header/TileHeader.scss
index f118f86d3..79cd5a59b 100644
--- a/libs/search-widget/src/tiles/base-tile/header/TileHeader.scss
+++ b/libs/search-widget/src/tiles/base-tile/header/TileHeader.scss
@@ -48,6 +48,21 @@
margin: 0;
}
}
+
+ .separator {
+ background: var(--color-neutral-light);
+ height: var(--rhythm-5);
+ width: 1px;
+ }
+
+ .tile-menu {
+ li {
+ align-items: center;
+ display: flex;
+ height: var(--rhythm-4);
+ padding: 0 var(--rhythm-1_5);
+ }
+ }
}
@media (min-width: 648px) {
@@ -55,3 +70,18 @@
gap: var(--rhythm-2);
}
}
+
+@media (hover: hover) {
+ .sw-tile-header .tile-menu li {
+ background: var(--color-light-stronger);
+ cursor: pointer;
+ transition: background var(--transition-superfast);
+
+ &:hover {
+ background: var(--color-neutral-lightest);
+ }
+ &:active {
+ background: var(--color-neutral-lighter);
+ }
+ }
+}
diff --git a/libs/search-widget/src/tiles/base-tile/header/TileHeader.svelte b/libs/search-widget/src/tiles/base-tile/header/TileHeader.svelte
index 6afdaa9da..56d23bc9c 100644
--- a/libs/search-widget/src/tiles/base-tile/header/TileHeader.svelte
+++ b/libs/search-widget/src/tiles/base-tile/header/TileHeader.svelte
@@ -3,9 +3,12 @@
import { IconButton } from '../../../common';
import { _ } from '../../../core/i18n';
import { FIELD_TYPE } from '@nuclia/core';
- import { createEventDispatcher } from 'svelte';
+ import { createEventDispatcher, onMount } from 'svelte';
import { filter, take } from 'rxjs';
- import { fieldType, getFieldUrl } from '../../../core/stores/viewer.store';
+ import { fieldFullId, fieldType, getFieldUrl } from '../../../core/stores/viewer.store';
+ import { getWidgetActions } from '../../../core/stores/widget.store';
+ import { WidgetAction } from '../../../core/models';
+ import Dropdown from '../../../common/dropdown/Dropdown.svelte';
export let expanded = false;
export let headerActionsWidth = 0;
@@ -14,6 +17,18 @@
const dispatch = createEventDispatcher();
+ let menuItems: WidgetAction[] = [];
+
+ let menuButton: HTMLElement | undefined;
+ let menuPosition: { left: number; top: number } | undefined;
+ let displayMenu = false;
+
+ $: hasMenu = menuItems.length > 0;
+
+ onMount(() => {
+ menuItems = getWidgetActions();
+ });
+
function close() {
dispatch('close');
}
@@ -30,6 +45,26 @@
)
.subscribe((url) => window.open(url, 'blank', 'noreferrer'));
}
+
+ function openMenu(event) {
+ event.stopPropagation();
+ if (menuButton) {
+ displayMenu = true;
+ const menuWidth = 176;
+ menuPosition = {
+ left: menuButton.offsetLeft - menuWidth + menuButton.offsetWidth,
+ top: menuButton.clientHeight + 6,
+ width: menuWidth,
+ };
+ }
+ }
+
+ function clickOnMenu(item: WidgetAction) {
+ const fullId = fieldFullId.getValue();
+ if (fullId) {
+ item.action(fullId);
+ }
+ }