diff --git a/app.babel b/app.babel index 03d021805..64893d1f0 100644 --- a/app.babel +++ b/app.babel @@ -9579,6 +9579,26 @@ + + resource.delete_resource_title + + + + + + ca-ES + false + + + en-US + false + + + es-ES + false + + + resource.delete_resource_warning @@ -9619,6 +9639,26 @@ + + resource.delete_resources_title + + + + + + ca-ES + false + + + en-US + false + + + es-ES + false + + + resource.delete_resources_warning @@ -10659,6 +10699,46 @@ + + resource.reprocess_resource_description + + + + + + ca-ES + false + + + en-US + false + + + es-ES + false + + + + + resource.reprocess_resource_title + + + + + + ca-ES + false + + + en-US + false + + + es-ES + false + + + 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); + } + }
- + {#if $$slots.default} + + {/if} + {#if $$slots.default && hasMenu} +
+ {/if} + {#if hasMenu} +
+ +
+ + {#if displayMenu} + (displayMenu = false)}> +
    + {#each menuItems as item} +
  • clickOnMenu(item)}>{item.label}
  • + {/each} +
+
+ {/if} + {/if} /, 'g'); @@ -26,3 +27,7 @@ export const filterParagraphs = (query: string, paragraphs: MediaWidgetParagraph }; }); }; + +export function onClosePreview() { + viewerState.reset(); +} diff --git a/libs/search-widget/src/tiles/viewers/TextViewer.svelte b/libs/search-widget/src/tiles/viewers/TextViewer.svelte index 9f967fbfd..5a3881352 100644 --- a/libs/search-widget/src/tiles/viewers/TextViewer.svelte +++ b/libs/search-widget/src/tiles/viewers/TextViewer.svelte @@ -21,7 +21,7 @@ selectedIndex = texts.findIndex((text) => text === unmarkSelection); if (selectedIndex > -1) { setTimeout(() => - textViewerElement.querySelector(`#paragraph${selectedIndex}`)?.scrollIntoView({ behavior: 'smooth' }), + textViewerElement?.querySelector(`#paragraph${selectedIndex}`)?.scrollIntoView({ behavior: 'smooth' }), ); } }); diff --git a/libs/search-widget/src/widgets/search-widget/SearchBar.svelte b/libs/search-widget/src/widgets/search-widget/SearchBar.svelte index a4c026376..0cebe6ba9 100644 --- a/libs/search-widget/src/widgets/search-widget/SearchBar.svelte +++ b/libs/search-widget/src/widgets/search-widget/SearchBar.svelte @@ -38,11 +38,16 @@ let _features: WidgetFeatures = {}; - export const search = (query: string) => { + export function search(query: string) { searchQuery.set(query); typeAhead.set(query || ''); triggerSearch.next(); - }; + } + + export function reloadSearch() { + console.log(`reloadSearch`); + triggerSearch.next(); + } const thisComponent = get_current_component(); const dispatchCustomEvent = (name: string, detail: any) => { diff --git a/libs/search-widget/src/widgets/search-widget/SearchResults.svelte b/libs/search-widget/src/widgets/search-widget/SearchResults.svelte index d661bdf42..3d7aae7c8 100644 --- a/libs/search-widget/src/widgets/search-widget/SearchResults.svelte +++ b/libs/search-widget/src/widgets/search-widget/SearchResults.svelte @@ -23,6 +23,8 @@ import { fieldData, fieldFullId, resourceTitle } from '../../core/stores/viewer.store'; import type { Search } from '@nuclia/core'; import { distinctUntilChanged } from 'rxjs/operators'; + import { setWidgetActions } from '../../core/stores/widget.store'; + import { onClosePreview } from '../../tiles/tile.utils'; const searchAlreadyTriggered = new Subject(); const showResults = merge(triggerSearch, searchAlreadyTriggered).pipe(map(() => true)); @@ -34,10 +36,15 @@ resourceTitle.pipe(distinctUntilChanged()), ]).pipe( map(([fullId, data, title]) => - fullId && data ? { id: fullId.resourceId, field: fullId, fieldData: data, title } : null, + fullId && data ? ({ id: fullId.resourceId, field: fullId, fieldData: data, title } as Search.SmartResult) : null, ), ); + export const setTileMenu = setWidgetActions; + export function closePreview() { + onClosePreview(); + } + let svgSprite; onMount(() => { diff --git a/libs/search-widget/src/widgets/viewer-widget/ViewerWidget.svelte b/libs/search-widget/src/widgets/viewer-widget/ViewerWidget.svelte index 2d9a6c0fc..616f49393 100644 --- a/libs/search-widget/src/widgets/viewer-widget/ViewerWidget.svelte +++ b/libs/search-widget/src/widgets/viewer-widget/ViewerWidget.svelte @@ -5,16 +5,16 @@ import { onMount } from 'svelte'; import { setCDN, loadFonts, loadSvgSprite, getFieldType } from '../../core/utils'; import { setLang } from '../../core/i18n'; - import type { KBStates } from '@nuclia/core'; + import type { FieldFullId, KBStates } from '@nuclia/core'; import globalCss from '../../common/_global.scss?inline'; - import { widgetType } from '../../core/stores/widget.store'; + import { setWidgetActions, widgetType } from '../../core/stores/widget.store'; import { unsubscribeAllEffects } from '../../core/stores/effects'; import type { ResourceProperties, Search } from '@nuclia/core'; import { combineLatest, map, Observable, of, switchMap } from 'rxjs'; import { fieldData, fieldFullId, isPreviewing, resourceTitle } from '../../core/stores/viewer.store'; import { distinctUntilChanged } from 'rxjs/operators'; - import type { FieldFullId } from '../../core/models'; import Tile from '../../tiles/Tile.svelte'; + import { onClosePreview } from '../../tiles/tile.utils'; export let backend = 'https://nuclia.cloud/api'; export let zone = ''; @@ -46,6 +46,7 @@ closePreview(); } + export const setTileMenu = setWidgetActions; export function openPreview(fullId: FieldFullId, title?: string): Observable { fieldFullId.set(fullId); resourceTitle.set(title || ''); @@ -54,8 +55,7 @@ } export function closePreview() { - fieldFullId.set(null); - resourceTitle.set(''); + onClosePreview(); } const tileResult: Observable = combineLatest([ diff --git a/mrs.developer.json b/mrs.developer.json index e2e7951d4..a924d477a 100644 --- a/mrs.developer.json +++ b/mrs.developer.json @@ -4,6 +4,6 @@ "https": "https://github.com/plone/pastanaga-angular.git", "path": "/projects/pastanaga-angular/src", "package": "@guillotinaweb/pastanaga-angular", - "tag": "2.58.13" + "branch": "ng14" } }