From 34b8103be57d2313c129215b444251b00daa2ff3 Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Mon, 4 Nov 2024 10:22:11 -0400 Subject: [PATCH 01/17] chore(edit-content): implement endpoint to fetch sites #30215 --- .../dot-sidebar/dot-sidebar.component.html | 11 +- .../dot-sidebar/dot-sidebar.component.ts | 74 +++++++++---- .../dot-select-existing-file.component.html | 6 +- .../dot-select-existing-file.component.scss | 3 + .../dot-select-existing-file.component.ts | 4 +- .../store/select-existing-file.store.ts | 102 +++++++++++------- 6 files changed, 138 insertions(+), 62 deletions(-) diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.html index 690560328112..1a0db4d2f638 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.html +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.html @@ -1 +1,10 @@ - + + + {{ node.label | truncatePath | slice: 0 : 19 }} + + diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.ts index 95baf9ce298e..a4fef0d67664 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.ts @@ -1,29 +1,67 @@ -import { ChangeDetectionStrategy, Component, input } from '@angular/core'; +import { SlicePipe } from '@angular/common'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + inject, + OnInit +} from '@angular/core'; -import { TreeNode } from 'primeng/api'; import { TreeModule } from 'primeng/tree'; +import { ComponentStatus } from '@dotcms/dotcms-models'; + +import { + TreeNodeItem, + TreeNodeSelectItem +} from '../../../../../../models/dot-edit-content-host-folder-field.interface'; +import { TruncatePathPipe } from '../../../../../../pipes/truncate-path.pipe'; +import { DotEditContentService } from '../../../../../../services/dot-edit-content.service'; + +export const PEER_PAGE_LIMIT = 7000; + @Component({ selector: 'dot-sidebar', standalone: true, - imports: [TreeModule], + imports: [TreeModule, TruncatePathPipe, SlicePipe], templateUrl: './dot-sidebar.component.html', styleUrls: ['./dot-sidebar.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush }) -export class DotSideBarComponent { - /** - * An observable that emits an array of TreeNode objects representing folders. - * - * @type {Observable} - * @alias folders - */ - $folders = input.required({ alias: 'folders' }); - /** - * Represents a loading state for the component. - * - * @type {boolean} - * @alias loading - */ - $loading = input.required({ alias: 'loading' }); +export class DotSideBarComponent implements OnInit { + readonly #dotEditContentService = inject(DotEditContentService); + folders: TreeNodeItem[] = []; + status: ComponentStatus = ComponentStatus.INIT; + + readonly #changeRef = inject(ChangeDetectorRef); + + ngOnInit() { + this.loadFolders(); + } + + loadFolders() { + this.status = ComponentStatus.INIT; + this.#dotEditContentService + .getSitesTreePath({ perPage: PEER_PAGE_LIMIT, filter: '*' }) + .subscribe((folders: TreeNodeItem[]) => { + this.folders = folders; + this.status = ComponentStatus.LOADED; + this.#changeRef.detectChanges(); + }); + } + + onNodeExpand(event: TreeNodeSelectItem) { + const { node } = event; + const { hostname, path } = node.data; + + node.loading = true; + + this.#dotEditContentService.getFoldersTreeNode(hostname, path).subscribe((children) => { + node.loading = false; + node.leaf = true; + node.icon = 'pi pi-folder-open'; + node.children = [...children]; + this.#changeRef.detectChanges(); + }); + } } diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.html index 68c1387397dc..ec41a2a07caa 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.html +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.html @@ -1,9 +1,9 @@
-
- +
+
-
+
diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.scss b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.scss index 674ba19f4eed..9e493b3997b2 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.scss +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.scss @@ -7,6 +7,9 @@ ::ng-deep { .p-tree { border: 0px; + padding-right: 0px; + padding-top: 0px; + padding-bottom: 0px; } } } diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.ts index b10f9dfa2f3b..4d0b81e25fb9 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, inject, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, inject, OnInit, viewChild } from '@angular/core'; import { ButtonModule } from 'primeng/button'; import { DynamicDialogRef } from 'primeng/dynamicdialog'; @@ -37,6 +37,8 @@ export class DotSelectExistingFileComponent implements OnInit { */ readonly #dialogRef = inject(DynamicDialogRef); + $sideBarRef = viewChild.required(DotSideBarComponent); + ngOnInit() { this.store.loadContent(); this.store.loadFolders(); diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/store/select-existing-file.store.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/store/select-existing-file.store.ts index ea6e91dbc56d..639f3068bdf1 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/store/select-existing-file.store.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/store/select-existing-file.store.ts @@ -1,12 +1,25 @@ import { faker } from '@faker-js/faker'; +import { tapResponse } from '@ngrx/component-store'; import { patchState, signalStore, withComputed, withMethods, withState } from '@ngrx/signals'; +import { rxMethod } from '@ngrx/signals/rxjs-interop'; +import { pipe } from 'rxjs'; -import { computed } from '@angular/core'; +import { computed, inject } from '@angular/core'; import { TreeNode } from 'primeng/api'; +import { exhaustMap, switchMap, tap } from 'rxjs/operators'; + import { ComponentStatus, DotCMSContentlet } from '@dotcms/dotcms-models'; +import { + TreeNodeItem, + TreeNodeSelectItem +} from '../../../../../models/dot-edit-content-host-folder-field.interface'; +import { DotEditContentService } from '../../../../../services/dot-edit-content.service'; + +export const PEER_PAGE_LIMIT = 7000; + export interface Content { id: string; image: string; @@ -17,13 +30,14 @@ export interface Content { export interface SelectExisingFileState { folders: { - data: TreeNode[]; + data: TreeNodeItem[]; status: ComponentStatus; }; content: { data: Content[]; status: ComponentStatus; }; + nodeExpaned: TreeNodeSelectItem['node'] | null; selectedFolder: TreeNode | null; selectedFile: DotCMSContentlet | null; searchQuery: string; @@ -39,6 +53,7 @@ const initialState: SelectExisingFileState = { data: [], status: ComponentStatus.INIT }, + nodeExpaned: null, selectedFolder: null, selectedFile: null, searchQuery: '', @@ -52,6 +67,8 @@ export const SelectExisingFileStore = signalStore( contentIsLoading: computed(() => state.content().status === ComponentStatus.LOADING) })), withMethods((store) => { + const dotEditContentService = inject(DotEditContentService); + return { loadContent: () => { const mockContent = faker.helpers.multiple( @@ -72,44 +89,51 @@ export const SelectExisingFileStore = signalStore( } }); }, - loadFolders: () => { - const mockFolders = [ - { - label: 'demo.dotcms.com', - expandedIcon: 'pi pi-folder-open', - collapsedIcon: 'pi pi-folder', - children: [ - { - label: 'demo.dotcms.com', - expandedIcon: 'pi pi-folder-open', - collapsedIcon: 'pi pi-folder', - children: [ - { - label: 'documents' - } - ] - }, - { - label: 'demo.dotcms.com', - expandedIcon: 'pi pi-folder-open', - collapsedIcon: 'pi pi-folder' - } - ] - }, - { - label: 'nico.dotcms.com', - expandedIcon: 'pi pi-folder-open', - collapsedIcon: 'pi pi-folder' - } - ]; + loadFolders: rxMethod( + pipe( + tap(() => + patchState(store, { + folders: { ...store.folders(), status: ComponentStatus.LOADING } + }) + ), + switchMap(() => { + return dotEditContentService + .getSitesTreePath({ perPage: PEER_PAGE_LIMIT, filter: '*' }) + .pipe( + tapResponse({ + next: (data) => + patchState(store, { + folders: { data, status: ComponentStatus.LOADED } + }), + error: () => + patchState(store, { + folders: { data: [], status: ComponentStatus.ERROR } + }) + }) + ); + }) + ) + ), + loadChildren: rxMethod( + pipe( + exhaustMap((event: TreeNodeSelectItem) => { + const { node } = event; + const { hostname, path } = node.data; - patchState(store, { - folders: { - data: mockFolders, - status: ComponentStatus.LOADED - } - }); - } + node.loading = true; + + return dotEditContentService.getFoldersTreeNode(hostname, path).pipe( + tap((children) => { + node.loading = false; + node.leaf = true; + node.icon = 'pi pi-folder-open'; + node.children = [...children]; + patchState(store, { nodeExpaned: node }); + }) + ); + }) + ) + ) }; }) ); From 54cd5e49dc68e03c823c7dd2365faea6545d0c24 Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Tue, 5 Nov 2024 13:56:19 -0400 Subject: [PATCH 02/17] chore(edit-content): Load sites for select existing file dialog #30215 --- .../src/lib/dot-site/dot-site.service.ts | 9 ++- .../dot-dataview/dot-dataview.component.html | 2 +- .../dot-sidebar/dot-sidebar.component.html | 32 +++++++--- .../dot-sidebar/dot-sidebar.component.scss | 4 -- .../dot-sidebar/dot-sidebar.component.ts | 64 +++++++------------ .../dot-select-existing-file.component.html | 5 +- .../dot-select-existing-file.component.ts | 19 +++++- .../store/select-existing-file.store.ts | 26 +++++--- .../store/host-folder-field.store.ts | 2 +- .../lib/services/dot-edit-content.service.ts | 12 ++-- core-web/package.json | 2 +- core-web/yarn.lock | 8 +-- 12 files changed, 105 insertions(+), 80 deletions(-) diff --git a/core-web/libs/data-access/src/lib/dot-site/dot-site.service.ts b/core-web/libs/data-access/src/lib/dot-site/dot-site.service.ts index cbac35433d8e..287299670349 100644 --- a/core-web/libs/data-access/src/lib/dot-site/dot-site.service.ts +++ b/core-web/libs/data-access/src/lib/dot-site/dot-site.service.ts @@ -17,6 +17,8 @@ export const BASE_SITE_URL = '/api/v1/site'; export const DEFAULT_PER_PAGE = 10; +export const DEFAULT_PAGE = 1; + @Injectable({ providedIn: 'root' }) @@ -41,16 +43,17 @@ export class DotSiteService { * @return {*} {Observable} * @memberof DotSiteService */ - getSites(filter = '*', perPage?: number): Observable { + getSites(filter = '*', perPage?: number, page?: number): Observable { return this.#http - .get<{ entity: Site[] }>(this.getSiteURL(filter, perPage)) + .get<{ entity: Site[] }>(this.getSiteURL(filter, perPage, page)) .pipe(pluck('entity')); } - private getSiteURL(filter: string, perPage?: number): string { + private getSiteURL(filter: string, perPage?: number, page?: number): string { const searchParams = new URLSearchParams({ filter, per_page: `${perPage || DEFAULT_PER_PAGE}`, + page: `${page || DEFAULT_PAGE}`, archived: `${this.#defaultParams.archived}`, live: `${this.#defaultParams.live}`, system: `${this.#defaultParams.system}` diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.html index 6fc9ea595e40..c577c5bdbcf8 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.html +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.html @@ -3,7 +3,7 @@ [loading]="$loading()" [paginator]="true" [rows]="7" - styleClass="p-datatable-lg flex flex-column h-full justify-content-between"> + styleClass="p-datatable-lg flex flex-column h-full justify-content-between overflow-hidden">
diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.html index 1a0db4d2f638..e3a756c7ac05 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.html +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.html @@ -1,10 +1,22 @@ - - - {{ node.label | truncatePath | slice: 0 : 19 }} - - +@let loading = $loading(); + +@if (!loading) { + + + {{ node.label | truncatePath | slice: 0 : 19 }} + + +} @else { +
+ @for (col of $fakeColumns(); track $index) { + + } +
+} diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.scss b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.scss index 9c94b8ec9f4d..e69de29bb2d1 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.scss +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.scss @@ -1,4 +0,0 @@ -:host { - height: 100%; - display: flex; -} diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.ts index a4fef0d67664..0a59f7fe8160 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.ts @@ -4,64 +4,44 @@ import { ChangeDetectorRef, Component, inject, - OnInit + input, + output, + signal, + viewChild } from '@angular/core'; -import { TreeModule } from 'primeng/tree'; +import { TreeNode } from 'primeng/api'; +import { SkeletonModule } from 'primeng/skeleton'; +import { Tree, TreeModule, TreeNodeExpandEvent } from 'primeng/tree'; -import { ComponentStatus } from '@dotcms/dotcms-models'; - -import { - TreeNodeItem, - TreeNodeSelectItem -} from '../../../../../../models/dot-edit-content-host-folder-field.interface'; import { TruncatePathPipe } from '../../../../../../pipes/truncate-path.pipe'; -import { DotEditContentService } from '../../../../../../services/dot-edit-content.service'; - -export const PEER_PAGE_LIMIT = 7000; @Component({ selector: 'dot-sidebar', standalone: true, - imports: [TreeModule, TruncatePathPipe, SlicePipe], + imports: [TreeModule, SlicePipe, TruncatePathPipe, SkeletonModule], templateUrl: './dot-sidebar.component.html', styleUrls: ['./dot-sidebar.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush }) -export class DotSideBarComponent implements OnInit { - readonly #dotEditContentService = inject(DotEditContentService); - folders: TreeNodeItem[] = []; - status: ComponentStatus = ComponentStatus.INIT; +export class DotSideBarComponent { + $folders = input.required({ alias: 'folders' }); + $loading = input.required({ alias: 'loading' }); - readonly #changeRef = inject(ChangeDetectorRef); + $fakeColumns = signal( + Array.from({ length: 50 }).map((_) => `${this.getRandomRange(75, 100)}%`) + ); - ngOnInit() { - this.loadFolders(); - } + onNodeExpand = output(); - loadFolders() { - this.status = ComponentStatus.INIT; - this.#dotEditContentService - .getSitesTreePath({ perPage: PEER_PAGE_LIMIT, filter: '*' }) - .subscribe((folders: TreeNodeItem[]) => { - this.folders = folders; - this.status = ComponentStatus.LOADED; - this.#changeRef.detectChanges(); - }); - } - - onNodeExpand(event: TreeNodeSelectItem) { - const { node } = event; - const { hostname, path } = node.data; + readonly #cd = inject(ChangeDetectorRef); + $tree = viewChild.required(Tree); - node.loading = true; + detectChanges() { + this.#cd.detectChanges(); + } - this.#dotEditContentService.getFoldersTreeNode(hostname, path).subscribe((children) => { - node.loading = false; - node.leaf = true; - node.icon = 'pi pi-folder-open'; - node.children = [...children]; - this.#changeRef.detectChanges(); - }); + getRandomRange(max: number, min: number) { + return Math.floor(Math.random() * (max - min + 1) + min); } } diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.html index ec41a2a07caa..bc7e68dda668 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.html +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.html @@ -1,7 +1,10 @@
- +
diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.ts index 4d0b81e25fb9..e41d69bce745 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.ts @@ -1,4 +1,11 @@ -import { ChangeDetectionStrategy, Component, inject, OnInit, viewChild } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + effect, + inject, + OnInit, + viewChild +} from '@angular/core'; import { ButtonModule } from 'primeng/button'; import { DynamicDialogRef } from 'primeng/dynamicdialog'; @@ -39,6 +46,16 @@ export class DotSelectExistingFileComponent implements OnInit { $sideBarRef = viewChild.required(DotSideBarComponent); + constructor() { + effect(() => { + const folders = this.store.folders(); + + if (folders.nodeExpaned) { + this.$sideBarRef().detectChanges(); + } + }); + } + ngOnInit() { this.store.loadContent(); this.store.loadFolders(); diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/store/select-existing-file.store.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/store/select-existing-file.store.ts index 639f3068bdf1..5d5a1001eea8 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/store/select-existing-file.store.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/store/select-existing-file.store.ts @@ -18,7 +18,7 @@ import { } from '../../../../../models/dot-edit-content-host-folder-field.interface'; import { DotEditContentService } from '../../../../../services/dot-edit-content.service'; -export const PEER_PAGE_LIMIT = 7000; +export const PEER_PAGE_LIMIT = 500; export interface Content { id: string; @@ -32,12 +32,12 @@ export interface SelectExisingFileState { folders: { data: TreeNodeItem[]; status: ComponentStatus; + nodeExpaned: TreeNodeSelectItem['node'] | null; }; content: { data: Content[]; status: ComponentStatus; }; - nodeExpaned: TreeNodeSelectItem['node'] | null; selectedFolder: TreeNode | null; selectedFile: DotCMSContentlet | null; searchQuery: string; @@ -47,13 +47,13 @@ export interface SelectExisingFileState { const initialState: SelectExisingFileState = { folders: { data: [], - status: ComponentStatus.INIT + status: ComponentStatus.INIT, + nodeExpaned: null }, content: { data: [], status: ComponentStatus.INIT }, - nodeExpaned: null, selectedFolder: null, selectedFile: null, searchQuery: '', @@ -98,16 +98,24 @@ export const SelectExisingFileStore = signalStore( ), switchMap(() => { return dotEditContentService - .getSitesTreePath({ perPage: PEER_PAGE_LIMIT, filter: '*' }) + .getSitesTreePath({ perPage: PEER_PAGE_LIMIT, filter: '*', page: 0 }) .pipe( tapResponse({ next: (data) => patchState(store, { - folders: { data, status: ComponentStatus.LOADED } + folders: { + data, + status: ComponentStatus.LOADED, + nodeExpaned: null + } }), error: () => patchState(store, { - folders: { data: [], status: ComponentStatus.ERROR } + folders: { + data: [], + status: ComponentStatus.ERROR, + nodeExpaned: null + } }) }) ); @@ -128,7 +136,9 @@ export const SelectExisingFileStore = signalStore( node.leaf = true; node.icon = 'pi pi-folder-open'; node.children = [...children]; - patchState(store, { nodeExpaned: node }); + + const folders = store.folders(); + patchState(store, { folders: { ...folders, nodeExpaned: node } }); }) ); }) diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-host-folder-field/store/host-folder-field.store.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-host-folder-field/store/host-folder-field.store.ts index 0685318af172..c4c4d20a58ca 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-host-folder-field/store/host-folder-field.store.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-host-folder-field/store/host-folder-field.store.ts @@ -73,7 +73,7 @@ export const HostFolderFiledStore = signalStore( tap(() => patchState(store, { status: 'LOADING' })), switchMap(({ path, isRequired }) => { return dotEditContentService - .getSitesTreePath({ perPage: PEER_PAGE_LIMIT, filter: '*' }) + .getSitesTreePath({ perPage: PEER_PAGE_LIMIT, filter: '*', page: 1 }) .pipe( map((sites) => { if (isRequired) { diff --git a/core-web/libs/edit-content/src/lib/services/dot-edit-content.service.ts b/core-web/libs/edit-content/src/lib/services/dot-edit-content.service.ts index b9eef1e20dc9..f8a64f6a1aaf 100644 --- a/core-web/libs/edit-content/src/lib/services/dot-edit-content.service.ts +++ b/core-web/libs/edit-content/src/lib/services/dot-edit-content.service.ts @@ -75,10 +75,14 @@ export class DotEditContentService { * @return {*} {Observable} * @memberof DotEditContentService */ - getSitesTreePath(data: { filter: string; perPage: number }): Observable { - const { filter, perPage } = data; - - return this.#siteService.getSites(filter, perPage).pipe( + getSitesTreePath(data: { + filter: string; + perPage: number; + page: number; + }): Observable { + const { filter, perPage, page } = data; + + return this.#siteService.getSites(filter, perPage, page).pipe( map((sites) => { return sites.map((site) => ({ key: site.hostname, diff --git a/core-web/package.json b/core-web/package.json index 0ed2eb181bc2..7872f940db08 100644 --- a/core-web/package.json +++ b/core-web/package.json @@ -115,7 +115,7 @@ "node-fetch": "^2.6.1", "primeflex": "3.3.1", "primeicons": "7.0.0", - "primeng": "17.18.9", + "primeng": "17.18.11", "prosemirror-model": "^1.16.1", "prosemirror-view": "^1.23.12", "react": "^18.2.0", diff --git a/core-web/yarn.lock b/core-web/yarn.lock index 698361a2cd83..655b451cc69b 100644 --- a/core-web/yarn.lock +++ b/core-web/yarn.lock @@ -18117,10 +18117,10 @@ primeicons@7.0.0: resolved "https://registry.yarnpkg.com/primeicons/-/primeicons-7.0.0.tgz#6b25c3fdcb29bb745a3035bdc1ed5902f4a419cf" integrity sha512-jK3Et9UzwzTsd6tzl2RmwrVY/b8raJ3QZLzoDACj+oTJ0oX7L9Hy+XnVwgo4QVKlKpnP/Ur13SXV/pVh4LzaDw== -primeng@17.18.9: - version "17.18.9" - resolved "https://registry.yarnpkg.com/primeng/-/primeng-17.18.9.tgz#dc3b65689548b53ce07ce7032a0aa3d1eb5dfbea" - integrity sha512-1FT0B8wtgvs/joduB1DDOLe2IsP1pegOiEfSPAHSbc6otgNx/6iLR0k2M/xr2c9Ur1aC7tAikkVfH3FGpWof3w== +primeng@17.18.11: + version "17.18.11" + resolved "https://registry.npmjs.org/primeng/-/primeng-17.18.11.tgz#8e5b1b921b303767611f31a356ee972e78ccc98e" + integrity sha512-LzV0fFZmb3GdnaRqi1+GP+RPtW0a+jztL5pH1zRWY7+7pyQ0n1YNyTXzmqVcdks/CmoyjNhutWEmexwi6vFVeA== dependencies: tslib "^2.3.0" From 473820adf0794a231f9e4d7f41889f1288419765 Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Tue, 5 Nov 2024 14:46:55 -0400 Subject: [PATCH 03/17] chore(edit-content): Add docs #30215 --- .../dot-dataview/dot-dataview.component.html | 2 +- .../dot-sidebar/dot-sidebar.component.scss | 4 ++ .../dot-sidebar/dot-sidebar.component.ts | 49 ++++++++++++++++--- .../dot-select-existing-file.component.ts | 6 +++ .../store/select-existing-file.store.ts | 4 +- .../lib/services/dot-edit-content.service.ts | 4 +- 6 files changed, 58 insertions(+), 11 deletions(-) diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.html index c577c5bdbcf8..6fc9ea595e40 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.html +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.html @@ -3,7 +3,7 @@ [loading]="$loading()" [paginator]="true" [rows]="7" - styleClass="p-datatable-lg flex flex-column h-full justify-content-between overflow-hidden"> + styleClass="p-datatable-lg flex flex-column h-full justify-content-between">
diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.scss b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.scss index e69de29bb2d1..9c94b8ec9f4d 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.scss +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.scss @@ -0,0 +1,4 @@ +:host { + height: 100%; + display: flex; +} diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.ts index 0a59f7fe8160..74461e79f404 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.ts @@ -6,13 +6,12 @@ import { inject, input, output, - signal, - viewChild + signal } from '@angular/core'; import { TreeNode } from 'primeng/api'; import { SkeletonModule } from 'primeng/skeleton'; -import { Tree, TreeModule, TreeNodeExpandEvent } from 'primeng/tree'; +import { TreeModule, TreeNodeExpandEvent } from 'primeng/tree'; import { TruncatePathPipe } from '../../../../../../pipes/truncate-path.pipe'; @@ -25,22 +24,60 @@ import { TruncatePathPipe } from '../../../../../../pipes/truncate-path.pipe'; changeDetection: ChangeDetectionStrategy.OnPush }) export class DotSideBarComponent { + /** + * A readonly private field that holds an instance of ChangeDetectorRef. + * This is used to detect and respond to changes in the component's data-bound properties. + */ + readonly #cd = inject(ChangeDetectorRef); + /** + * An observable that emits an array of TreeNode objects representing the folders. + * + * @type {Observable} + * @alias folders + */ $folders = input.required({ alias: 'folders' }); + /** + * A boolean observable that indicates the loading state. + * + * @type {boolean} + */ $loading = input.required({ alias: 'loading' }); + /** + * Signal that generates an array of strings representing percentages. + * Each percentage is a random value between 75% and 100%. + * The array contains 50 elements. + * + * @returns {string[]} An array of 50 percentage strings. + */ $fakeColumns = signal( Array.from({ length: 50 }).map((_) => `${this.getRandomRange(75, 100)}%`) ); + /** + * Event emitter for when a tree node is expanded. + * + * This event is triggered when a user expands a node in the tree structure. + * It emits an event of type `TreeNodeExpandEvent`. + */ onNodeExpand = output(); - readonly #cd = inject(ChangeDetectorRef); - $tree = viewChild.required(Tree); - + /** + * Triggers change detection manually. + * This method is used to ensure that the view is updated when the model changes. + * It calls the `detectChanges` method on the ChangeDetectorRef instance. + */ detectChanges() { this.#cd.detectChanges(); } + /** + * Generates a random integer within a specified range. + * + * @param max - The maximum value of the range (inclusive). + * @param min - The minimum value of the range (inclusive). + * @returns A random integer between min and max (both inclusive). + */ getRandomRange(max: number, min: number) { return Math.floor(Math.random() * (max - min + 1) + min); } diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.ts index e41d69bce745..050d6f019a89 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.ts @@ -44,6 +44,12 @@ export class DotSelectExistingFileComponent implements OnInit { */ readonly #dialogRef = inject(DynamicDialogRef); + /** + * Reference to the DotSideBarComponent instance. + * This is used to interact with the sidebar component within the template. + * + * @type {DotSideBarComponent} + */ $sideBarRef = viewChild.required(DotSideBarComponent); constructor() { diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/store/select-existing-file.store.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/store/select-existing-file.store.ts index 5d5a1001eea8..587cecfcc02f 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/store/select-existing-file.store.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/store/select-existing-file.store.ts @@ -18,7 +18,7 @@ import { } from '../../../../../models/dot-edit-content-host-folder-field.interface'; import { DotEditContentService } from '../../../../../services/dot-edit-content.service'; -export const PEER_PAGE_LIMIT = 500; +export const PEER_PAGE_LIMIT = 1000; export interface Content { id: string; @@ -98,7 +98,7 @@ export const SelectExisingFileStore = signalStore( ), switchMap(() => { return dotEditContentService - .getSitesTreePath({ perPage: PEER_PAGE_LIMIT, filter: '*', page: 0 }) + .getSitesTreePath({ perPage: PEER_PAGE_LIMIT, filter: '*' }) .pipe( tapResponse({ next: (data) => diff --git a/core-web/libs/edit-content/src/lib/services/dot-edit-content.service.ts b/core-web/libs/edit-content/src/lib/services/dot-edit-content.service.ts index f8a64f6a1aaf..d295cb74298b 100644 --- a/core-web/libs/edit-content/src/lib/services/dot-edit-content.service.ts +++ b/core-web/libs/edit-content/src/lib/services/dot-edit-content.service.ts @@ -77,8 +77,8 @@ export class DotEditContentService { */ getSitesTreePath(data: { filter: string; - perPage: number; - page: number; + perPage?: number; + page?: number; }): Observable { const { filter, perPage, page } = data; From ff9c63f78eb5df4ede01698afe03638aaf6576af Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Tue, 5 Nov 2024 14:52:41 -0400 Subject: [PATCH 04/17] chore(edit-content): Fix unit tests #30215 --- .../data-access/src/lib/dot-site/dot-site.service.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core-web/libs/data-access/src/lib/dot-site/dot-site.service.spec.ts b/core-web/libs/data-access/src/lib/dot-site/dot-site.service.spec.ts index 0f9dbfca8933..f3475dc884e7 100644 --- a/core-web/libs/data-access/src/lib/dot-site/dot-site.service.spec.ts +++ b/core-web/libs/data-access/src/lib/dot-site/dot-site.service.spec.ts @@ -27,7 +27,7 @@ describe('DotSiteService', () => { doneFn(); }); - const url = `${BASE_SITE_URL}?filter=*&per_page=10&archived=false&live=true&system=true`; + const url = `${BASE_SITE_URL}?filter=*&per_page=10&page=1&archived=false&live=true&system=true`; const req = spectator.expectOne(url, HttpMethod.GET); spectator.flushAll([req], [{ entity: mockSites }]); }); @@ -41,7 +41,7 @@ describe('DotSiteService', () => { service.searchParam = searchParams; - const url = `${BASE_SITE_URL}?filter=demo&per_page=15&archived=true&live=false&system=true`; + const url = `${BASE_SITE_URL}?filter=demo&per_page=15&page=1&archived=true&live=false&system=true`; service.getSites('demo', 15).subscribe(() => doneFn()); From 23cdfc5477288f3ff0238c89f11aa6c6735b2c17 Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Tue, 5 Nov 2024 14:56:11 -0400 Subject: [PATCH 05/17] chore(edit-content): Fix sonar issue #30215 --- .../components/dot-sidebar/dot-sidebar.component.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.ts index 74461e79f404..d645a4f620fd 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.ts @@ -1,3 +1,5 @@ +import { faker } from '@faker-js/faker'; + import { SlicePipe } from '@angular/common'; import { ChangeDetectionStrategy, @@ -79,6 +81,6 @@ export class DotSideBarComponent { * @returns A random integer between min and max (both inclusive). */ getRandomRange(max: number, min: number) { - return Math.floor(Math.random() * (max - min + 1) + min); + return faker.number.int({ max, min }); } } From 9364dd507b55d7369c46000d142798683345634f Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Tue, 5 Nov 2024 17:51:12 -0400 Subject: [PATCH 06/17] chore(edit-content): add unit tests #30215 --- .../store/select-existing-file.store.test.ts | 106 ++++++++++++++++++ .../store/select-existing-file.store.ts | 21 ++-- .../store/file-field.store.spec.ts | 80 ++++++------- .../store/file-field.store.ts | 8 ++ 4 files changed, 164 insertions(+), 51 deletions(-) create mode 100644 core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/store/select-existing-file.store.test.ts diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/store/select-existing-file.store.test.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/store/select-existing-file.store.test.ts new file mode 100644 index 000000000000..ee813215e373 --- /dev/null +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/store/select-existing-file.store.test.ts @@ -0,0 +1,106 @@ +import { createFakeEvent } from '@ngneat/spectator'; +import { SpyObject, mockProvider } from '@ngneat/spectator/jest'; +import { of, throwError } from 'rxjs'; + +import { fakeAsync, TestBed, tick } from '@angular/core/testing'; + +import { ComponentStatus } from '@dotcms/dotcms-models'; + +import { SelectExisingFileStore } from './select-existing-file.store'; + + +import { DotEditContentService } from '../../../../../services/dot-edit-content.service'; +import { TREE_SELECT_MOCK, TREE_SELECT_SITES_MOCK } from '../../../../../utils/mocks'; + +describe('SelectExisingFileStore', () => { + let store: InstanceType; + let service: SpyObject; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [SelectExisingFileStore, mockProvider(DotEditContentService)], + }); + + store = TestBed.inject(SelectExisingFileStore); + service = TestBed.inject(DotEditContentService) as SpyObject; + }); + + it('should be created', () => { + expect(store).toBeTruthy(); + }); + + + + describe('Method: loadFolders', () => { + + it('should set folders status to LOADING and then to LOADED with data', fakeAsync(() => { + service.getSitesTreePath.mockReturnValue(of(TREE_SELECT_SITES_MOCK)); + + store.loadFolders(); + + tick(50); + + expect(store.folders().status).toBe(ComponentStatus.LOADED); + expect(store.folders().data).toEqual(TREE_SELECT_SITES_MOCK); + })); + + + + it('should set folders status to ERROR on service error', fakeAsync(() => { + service.getSitesTreePath.mockReturnValue(throwError('error')); + + store.loadFolders(); + + tick(50); + + expect(store.folders().status).toBe(ComponentStatus.ERROR); + expect(store.folders().data).toEqual([]); + })); + }); + + describe('Method: loadChildren', () => { + + it('should load children for a node', fakeAsync(() => { + + const mockChildren = [...TREE_SELECT_SITES_MOCK]; + + service.getFoldersTreeNode.mockReturnValue(of(mockChildren)); + + const node = { ...TREE_SELECT_MOCK[0] }; + + const mockItem = { + originalEvent: createFakeEvent('click'), + node, + }; + store.loadChildren(mockItem); + + tick(50); + + expect(node.children).toEqual(mockChildren); + expect(node.loading).toBe(false); + expect(node.leaf).toBe(true); + expect(node.icon).toBe('pi pi-folder-open'); + expect(store.folders().nodeExpaned).toBe(node); + })); + + it('should handle error when loading children', fakeAsync(() => { + + service.getFoldersTreeNode.mockReturnValue(throwError('error')); + + const node = { ...TREE_SELECT_MOCK[0], children: [] }; + + const mockItem = { + originalEvent: createFakeEvent('click'), + node, + }; + store.loadChildren(mockItem); + + tick(50); + + expect(node.children).toEqual([]); + expect(node.loading).toBe(false); + })); + + }); + +}); \ No newline at end of file diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/store/select-existing-file.store.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/store/select-existing-file.store.ts index 587cecfcc02f..274e7d1368ea 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/store/select-existing-file.store.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/store/select-existing-file.store.ts @@ -131,14 +131,19 @@ export const SelectExisingFileStore = signalStore( node.loading = true; return dotEditContentService.getFoldersTreeNode(hostname, path).pipe( - tap((children) => { - node.loading = false; - node.leaf = true; - node.icon = 'pi pi-folder-open'; - node.children = [...children]; - - const folders = store.folders(); - patchState(store, { folders: { ...folders, nodeExpaned: node } }); + tapResponse({ + next: (children) => { + node.loading = false; + node.leaf = true; + node.icon = 'pi pi-folder-open'; + node.children = [...children]; + + const folders = store.folders(); + patchState(store, { folders: { ...folders, nodeExpaned: node } }); + }, + error: () => { + node.loading = false; + } }) ); }) diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/store/file-field.store.spec.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/store/file-field.store.spec.ts index 9745f4626ca1..676691583df1 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/store/file-field.store.spec.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/store/file-field.store.spec.ts @@ -1,8 +1,7 @@ import { SpyObject, mockProvider } from '@ngneat/spectator/jest'; -import { patchState } from '@ngrx/signals'; import { of, throwError } from 'rxjs'; -import { TestBed } from '@angular/core/testing'; +import { TestBed, fakeAsync, tick } from '@angular/core/testing'; import { FileFieldStore } from './file-field.store'; @@ -16,11 +15,11 @@ describe('FileFieldStore', () => { let service: SpyObject; beforeEach(() => { - store = TestBed.overrideProvider( - DotFileFieldUploadService, - mockProvider(DotFileFieldUploadService) - ).runInInjectionContext(() => new FileFieldStore()); + TestBed.configureTestingModule({ + providers: [FileFieldStore, mockProvider(DotFileFieldUploadService)], + }); + store = TestBed.inject(FileFieldStore); service = TestBed.inject(DotFileFieldUploadService) as SpyObject; }); @@ -98,59 +97,54 @@ describe('FileFieldStore', () => { }); describe('Method: removeFile', () => { - it('should set the state properly when removeFile is called', () => { - patchState(store, { - uploadedFile: { - source: 'contentlet', - file: NEW_FILE_MOCK.entity - }, - value: 'some value', - fileStatus: 'preview', - uiMessage: getUiMessage('SERVER_ERROR') - }); + it('should set the state properly when removeFile is called', fakeAsync(() => { + const mockContentlet = NEW_FILE_MOCK.entity; + service.uploadFile.mockReturnValue(of({ source: 'contentlet', file: mockContentlet })); + + const file = new File([''], 'filename', { type: 'text/plain' }); + Object.defineProperty(file, 'size', { value: 5000 }); + + store.handleUploadFile(file); + + tick(50); + store.removeFile(); expect(store.value()).toBe(''); expect(store.fileStatus()).toBe('init'); expect(store.uiMessage()).toBe(getUiMessage('DEFAULT')); expect(store.uploadedFile()).toBeNull(); - }); + })); }); describe('Method: setDropZoneState', () => { it('should set dropZoneActive to true', () => { - patchState(store, { - dropZoneActive: false - }); store.setDropZoneState(true); expect(store.dropZoneActive()).toBe(true); }); it('should set dropZoneActive to false', () => { - patchState(store, { - dropZoneActive: true - }); store.setDropZoneState(false); expect(store.dropZoneActive()).toBe(false); }); }); describe('Method: handleUploadFile', () => { - it('should does not call uploadService with maxFileSize exceeded', () => { - patchState(store, { - maxFileSize: 10000 - }); + it('should does not call uploadService with maxFileSize exceeded', fakeAsync(() => { + store.setMaxSizeFile(10000); + + tick(50); const file = new File([''], 'filename', { type: 'text/plain' }); Object.defineProperty(file, 'size', { value: 20000 }); store.handleUploadFile(file); expect(service.uploadFile).not.toHaveBeenCalled(); - }); + })); - it('should set state properly with maxFileSize exceeded', () => { - patchState(store, { - maxFileSize: 10000 - }); + it('should set state properly with maxFileSize exceeded', fakeAsync(() => { + store.setMaxSizeFile(10000); + + tick(50); const file = new File([''], 'filename', { type: 'text/plain' }); Object.defineProperty(file, 'size', { value: 20000 }); @@ -162,15 +156,15 @@ describe('FileFieldStore', () => { ...getUiMessage('MAX_FILE_SIZE_EXCEEDED'), args: ['10000'] }); - }); + })); - it('should call uploadService with maxFileSize not exceeded', () => { + it('should call uploadService with maxFileSize not exceeded', fakeAsync(() => { const mockContentlet = NEW_FILE_MOCK.entity; service.uploadFile.mockReturnValue(of({ source: 'contentlet', file: mockContentlet })); - patchState(store, { - maxFileSize: 10000 - }); + store.setMaxSizeFile(10000); + + tick(50); const file = new File([''], 'filename', { type: 'text/plain' }); Object.defineProperty(file, 'size', { value: 5000 }); @@ -182,15 +176,15 @@ describe('FileFieldStore', () => { maxSize: '10000', uploadType: 'dotasset' }); - }); + })); - it('should set state properly with maxFileSize not exceeded', () => { + it('should set state properly with maxFileSize not exceeded', fakeAsync(() => { const mockContentlet = NEW_FILE_MOCK.entity; service.uploadFile.mockReturnValue(of({ source: 'contentlet', file: mockContentlet })); - patchState(store, { - maxFileSize: 10000 - }); + store.setMaxSizeFile(10000); + + tick(50); const file = new File([''], 'filename', { type: 'text/plain' }); Object.defineProperty(file, 'size', { value: 5000 }); @@ -202,7 +196,7 @@ describe('FileFieldStore', () => { source: 'contentlet', file: mockContentlet }); - }); + })); it('should set state properly with an error calling uploadFile', () => { service.uploadFile.mockReturnValue(throwError('error')); diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/store/file-field.store.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/store/file-field.store.ts index 5ec332eecd08..c5995e7f5c1a 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/store/file-field.store.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/store/file-field.store.ts @@ -91,6 +91,14 @@ export const FileFieldStore = signalStore( ...actions }); }, + /** + * setAcceptedFiles is used to set accepted files + */ + setMaxSizeFile: (maxFileSize: number) => { + patchState(store, { + maxFileSize + }); + }, /** * setUIMessage is used to set uiMessage * @param uiMessage From 6b1bf9d25f9e30abe2c053974c88de51ca26f7bd Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Tue, 5 Nov 2024 17:51:34 -0400 Subject: [PATCH 07/17] chore(edit-content): add unit tests #30215 --- .../store/select-existing-file.store.test.ts | 21 +++++-------------- .../store/select-existing-file.store.ts | 6 ++++-- .../store/file-field.store.spec.ts | 4 ++-- 3 files changed, 11 insertions(+), 20 deletions(-) diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/store/select-existing-file.store.test.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/store/select-existing-file.store.test.ts index ee813215e373..9c9ba7bbdc32 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/store/select-existing-file.store.test.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/store/select-existing-file.store.test.ts @@ -8,7 +8,6 @@ import { ComponentStatus } from '@dotcms/dotcms-models'; import { SelectExisingFileStore } from './select-existing-file.store'; - import { DotEditContentService } from '../../../../../services/dot-edit-content.service'; import { TREE_SELECT_MOCK, TREE_SELECT_SITES_MOCK } from '../../../../../utils/mocks'; @@ -18,8 +17,8 @@ describe('SelectExisingFileStore', () => { beforeEach(() => { TestBed.configureTestingModule({ - providers: [SelectExisingFileStore, mockProvider(DotEditContentService)], - }); + providers: [SelectExisingFileStore, mockProvider(DotEditContentService)] + }); store = TestBed.inject(SelectExisingFileStore); service = TestBed.inject(DotEditContentService) as SpyObject; @@ -29,10 +28,7 @@ describe('SelectExisingFileStore', () => { expect(store).toBeTruthy(); }); - - describe('Method: loadFolders', () => { - it('should set folders status to LOADING and then to LOADED with data', fakeAsync(() => { service.getSitesTreePath.mockReturnValue(of(TREE_SELECT_SITES_MOCK)); @@ -44,8 +40,6 @@ describe('SelectExisingFileStore', () => { expect(store.folders().data).toEqual(TREE_SELECT_SITES_MOCK); })); - - it('should set folders status to ERROR on service error', fakeAsync(() => { service.getSitesTreePath.mockReturnValue(throwError('error')); @@ -59,9 +53,7 @@ describe('SelectExisingFileStore', () => { }); describe('Method: loadChildren', () => { - it('should load children for a node', fakeAsync(() => { - const mockChildren = [...TREE_SELECT_SITES_MOCK]; service.getFoldersTreeNode.mockReturnValue(of(mockChildren)); @@ -70,7 +62,7 @@ describe('SelectExisingFileStore', () => { const mockItem = { originalEvent: createFakeEvent('click'), - node, + node }; store.loadChildren(mockItem); @@ -84,14 +76,13 @@ describe('SelectExisingFileStore', () => { })); it('should handle error when loading children', fakeAsync(() => { - service.getFoldersTreeNode.mockReturnValue(throwError('error')); const node = { ...TREE_SELECT_MOCK[0], children: [] }; const mockItem = { originalEvent: createFakeEvent('click'), - node, + node }; store.loadChildren(mockItem); @@ -100,7 +91,5 @@ describe('SelectExisingFileStore', () => { expect(node.children).toEqual([]); expect(node.loading).toBe(false); })); - }); - -}); \ No newline at end of file +}); diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/store/select-existing-file.store.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/store/select-existing-file.store.ts index 274e7d1368ea..e2c2524e31e8 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/store/select-existing-file.store.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/store/select-existing-file.store.ts @@ -137,9 +137,11 @@ export const SelectExisingFileStore = signalStore( node.leaf = true; node.icon = 'pi pi-folder-open'; node.children = [...children]; - + const folders = store.folders(); - patchState(store, { folders: { ...folders, nodeExpaned: node } }); + patchState(store, { + folders: { ...folders, nodeExpaned: node } + }); }, error: () => { node.loading = false; diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/store/file-field.store.spec.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/store/file-field.store.spec.ts index 676691583df1..e52df720c598 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/store/file-field.store.spec.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/store/file-field.store.spec.ts @@ -16,8 +16,8 @@ describe('FileFieldStore', () => { beforeEach(() => { TestBed.configureTestingModule({ - providers: [FileFieldStore, mockProvider(DotFileFieldUploadService)], - }); + providers: [FileFieldStore, mockProvider(DotFileFieldUploadService)] + }); store = TestBed.inject(FileFieldStore); service = TestBed.inject(DotFileFieldUploadService) as SpyObject; From 0aac38182c4519efbf54d876a81d8f48a911fd54 Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Tue, 5 Nov 2024 18:05:02 -0400 Subject: [PATCH 08/17] chore(edit-content): fix error #30215 --- .../dot-sidebar/dot-sidebar.component.html | 2 +- .../dot-sidebar/dot-sidebar.component.ts | 17 +++++++---------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.html index e3a756c7ac05..cbdc0ed474fb 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.html +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.html @@ -14,7 +14,7 @@ } @else { -
+
@for (col of $fakeColumns(); track $index) { } diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.ts index d645a4f620fd..146d45e2ae50 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.ts @@ -52,9 +52,7 @@ export class DotSideBarComponent { * * @returns {string[]} An array of 50 percentage strings. */ - $fakeColumns = signal( - Array.from({ length: 50 }).map((_) => `${this.getRandomRange(75, 100)}%`) - ); + $fakeColumns = signal(Array.from({ length: 50 }).map((_) => this.getPercentage())); /** * Event emitter for when a tree node is expanded. @@ -72,15 +70,14 @@ export class DotSideBarComponent { detectChanges() { this.#cd.detectChanges(); } - /** - * Generates a random integer within a specified range. + * Generates a random percentage string between 75% and 100%. * - * @param max - The maximum value of the range (inclusive). - * @param min - The minimum value of the range (inclusive). - * @returns A random integer between min and max (both inclusive). + * @returns {string} A string representing a percentage between 75% and 100%. */ - getRandomRange(max: number, min: number) { - return faker.number.int({ max, min }); + getPercentage(): string { + const number = faker.number.int({ max: 100, min: 75 }); + + return `${number}%`; } } From dbbe0b820b584822fcf482ec34a2aa908d8fae4c Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Wed, 6 Nov 2024 10:25:25 -0400 Subject: [PATCH 09/17] chore(edit-content): apply feedback #30215 --- .../components/dot-sidebar/dot-sidebar.component.html | 2 +- .../components/dot-sidebar/dot-sidebar.component.ts | 2 +- core-web/tsconfig.base.json | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.html index cbdc0ed474fb..cd96d04248bc 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.html +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.html @@ -10,7 +10,7 @@ loadingMode="icon" (onNodeExpand)="onNodeExpand.emit($event)"> - {{ node.label | truncatePath | slice: 0 : 19 }} + {{ node.label | truncatePath | slice: 0 : 18 }} } @else { diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.ts index 146d45e2ae50..17ccc9e90a01 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.ts @@ -15,7 +15,7 @@ import { TreeNode } from 'primeng/api'; import { SkeletonModule } from 'primeng/skeleton'; import { TreeModule, TreeNodeExpandEvent } from 'primeng/tree'; -import { TruncatePathPipe } from '../../../../../../pipes/truncate-path.pipe'; +import { TruncatePathPipe } from '@dotcms/edit-content/pipes/truncate-path.pipe'; @Component({ selector: 'dot-sidebar', diff --git a/core-web/tsconfig.base.json b/core-web/tsconfig.base.json index afe7e6788348..5b5c62b2db5d 100644 --- a/core-web/tsconfig.base.json +++ b/core-web/tsconfig.base.json @@ -35,6 +35,7 @@ "@dotcms/dotcms-webcomponents": ["libs/dotcms-webcomponents/src/index.ts"], "@dotcms/dotcms-webcomponents/loader": ["dist/libs/dotcms-webcomponents/loader"], "@dotcms/edit-content": ["libs/edit-content/src/index.ts"], + "@dotcms/edit-content/*": ["libs/edit-content/src/lib/*"], "@dotcms/experiments": ["libs/sdk/experiments/src/index.ts"], "@dotcms/portlets/dot-analytics-search/portlet": [ "libs/portlets/dot-analytics-search/portlet/src/index.ts" From e29e890cf59aa2b6c84c6ae3f52224a758cee2d7 Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Wed, 6 Nov 2024 16:52:26 -0400 Subject: [PATCH 10/17] chore(edit-content): fix error #30215 --- core-web/package.json | 2 +- core-web/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core-web/package.json b/core-web/package.json index 7872f940db08..0ed2eb181bc2 100644 --- a/core-web/package.json +++ b/core-web/package.json @@ -115,7 +115,7 @@ "node-fetch": "^2.6.1", "primeflex": "3.3.1", "primeicons": "7.0.0", - "primeng": "17.18.11", + "primeng": "17.18.9", "prosemirror-model": "^1.16.1", "prosemirror-view": "^1.23.12", "react": "^18.2.0", diff --git a/core-web/yarn.lock b/core-web/yarn.lock index 655b451cc69b..698361a2cd83 100644 --- a/core-web/yarn.lock +++ b/core-web/yarn.lock @@ -18117,10 +18117,10 @@ primeicons@7.0.0: resolved "https://registry.yarnpkg.com/primeicons/-/primeicons-7.0.0.tgz#6b25c3fdcb29bb745a3035bdc1ed5902f4a419cf" integrity sha512-jK3Et9UzwzTsd6tzl2RmwrVY/b8raJ3QZLzoDACj+oTJ0oX7L9Hy+XnVwgo4QVKlKpnP/Ur13SXV/pVh4LzaDw== -primeng@17.18.11: - version "17.18.11" - resolved "https://registry.npmjs.org/primeng/-/primeng-17.18.11.tgz#8e5b1b921b303767611f31a356ee972e78ccc98e" - integrity sha512-LzV0fFZmb3GdnaRqi1+GP+RPtW0a+jztL5pH1zRWY7+7pyQ0n1YNyTXzmqVcdks/CmoyjNhutWEmexwi6vFVeA== +primeng@17.18.9: + version "17.18.9" + resolved "https://registry.yarnpkg.com/primeng/-/primeng-17.18.9.tgz#dc3b65689548b53ce07ce7032a0aa3d1eb5dfbea" + integrity sha512-1FT0B8wtgvs/joduB1DDOLe2IsP1pegOiEfSPAHSbc6otgNx/6iLR0k2M/xr2c9Ur1aC7tAikkVfH3FGpWof3w== dependencies: tslib "^2.3.0" From f87007af7ff29504484303ba9059ad9bd0bce11b Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Fri, 8 Nov 2024 12:33:52 -0400 Subject: [PATCH 11/17] chore(edit-content): create logic to services #30216 --- .../src/lib/dot-site/dot-site.service.ts | 21 ++++++ .../dot-file-field-preview.component.ts | 5 +- .../dot-file-field-ui-message.component.ts | 2 +- .../dot-form-file-editor.component.ts | 2 +- .../store/form-file-editor.store.ts | 2 +- .../dot-form-import-url.component.spec.ts | 2 +- .../store/form-import-url.store.ts | 2 +- .../dot-dataview/dot-dataview.component.html | 64 +++++++++-------- .../dot-dataview/dot-dataview.component.scss | 13 ++++ .../dot-dataview/dot-dataview.component.ts | 36 ++++++---- .../dot-sidebar/dot-sidebar.component.html | 5 +- .../dot-sidebar/dot-sidebar.component.ts | 25 ++++++- .../dot-select-existing-file.component.html | 19 ++--- .../dot-select-existing-file.component.scss | 6 +- .../dot-select-existing-file.component.ts | 12 +++- .../store/select-existing-file.store.ts | 70 ++++++++++++------- .../dot-edit-content-file-field.component.ts | 4 +- .../dot-edit-content-file-field.const.ts | 2 +- .../dot-edit-content-file-field.stories.ts | 4 +- .../upload-file/upload-file.service.ts | 4 +- .../store/file-field.store.spec.ts | 2 +- .../store/file-field.store.ts | 8 ++- .../utils/messages.spec.ts | 2 +- .../utils/messages.ts | 6 +- .../dot-edit-content-file.model.ts} | 0 ...dit-content-host-folder-field.interface.ts | 8 ++- .../lib/services/dot-edit-content.service.ts | 19 +++++ core-web/package.json | 2 +- core-web/yarn.lock | 8 +-- .../WEB-INF/messages/Language.properties | 2 + 30 files changed, 252 insertions(+), 105 deletions(-) rename core-web/libs/edit-content/src/lib/{fields/dot-edit-content-file-field/models/index.ts => models/dot-edit-content-file.model.ts} (100%) diff --git a/core-web/libs/data-access/src/lib/dot-site/dot-site.service.ts b/core-web/libs/data-access/src/lib/dot-site/dot-site.service.ts index 287299670349..48a21b479a18 100644 --- a/core-web/libs/data-access/src/lib/dot-site/dot-site.service.ts +++ b/core-web/libs/data-access/src/lib/dot-site/dot-site.service.ts @@ -6,6 +6,7 @@ import { Injectable, inject } from '@angular/core'; import { pluck } from 'rxjs/operators'; import { Site } from '@dotcms/dotcms-js'; +import { DotCMSContentlet } from '@dotcms/dotcms-models'; export interface SiteParams { archived: boolean; @@ -13,6 +14,20 @@ export interface SiteParams { system: boolean; } +export interface ContentByFolderParams { + hostFolderId: string; + showLinks?: boolean; + showDotAssets?: boolean; + showArchived?: boolean; + sortByDesc?: boolean; + showPages?: boolean; + showFiles?: boolean; + showFolders?: boolean; + showWorking?: boolean; + extensions?: string[]; + mimeTypes?: string[]; +} + export const BASE_SITE_URL = '/api/v1/site'; export const DEFAULT_PER_PAGE = 10; @@ -73,4 +88,10 @@ export class DotSiteService { .get<{ entity: Site }>(`${BASE_SITE_URL}/currentSite`) .pipe(pluck('entity')); } + + getContentByFolder(params: ContentByFolderParams) { + return this.#http + .post<{ entity: { list: DotCMSContentlet[] } }>('/api/v1/browser', params) + .pipe(pluck('entity', 'list')); + } } diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-file-field-preview/dot-file-field-preview.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-file-field-preview/dot-file-field-preview.component.ts index f194051b3e88..47e39b4744ff 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-file-field-preview/dot-file-field-preview.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-file-field-preview/dot-file-field-preview.component.ts @@ -26,7 +26,10 @@ import { DotCopyButtonComponent } from '@dotcms/ui'; -import { DotPreviewResourceLink, UploadedFile } from '../../models'; +import { + DotPreviewResourceLink, + UploadedFile +} from '../../../../models/dot-edit-content-file.model'; import { getFileMetadata } from '../../utils'; @Component({ diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-file-field-ui-message/dot-file-field-ui-message.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-file-field-ui-message/dot-file-field-ui-message.component.ts index 3ffdbf45fa3c..0e956411a96f 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-file-field-ui-message/dot-file-field-ui-message.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-file-field-ui-message/dot-file-field-ui-message.component.ts @@ -3,7 +3,7 @@ import { ChangeDetectionStrategy, Component, input } from '@angular/core'; import { DotMessagePipe } from '@dotcms/ui'; -import { UIMessage } from '../../models'; +import { UIMessage } from '../../../../models/dot-edit-content-file.model'; @Component({ selector: 'dot-file-field-ui-message', diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-form-file-editor/dot-form-file-editor.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-form-file-editor/dot-form-file-editor.component.ts index edc56360c0ed..a8018cb11a51 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-form-file-editor/dot-form-file-editor.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-form-file-editor/dot-form-file-editor.component.ts @@ -21,7 +21,7 @@ import { DotMessagePipe, DotFieldValidationMessageComponent } from '@dotcms/ui'; import { FormFileEditorStore } from './store/form-file-editor.store'; -import { UploadedFile } from '../../models'; +import { UploadedFile } from '../../../../models/dot-edit-content-file.model'; type DialogProps = { allowFileNameEdit: boolean; diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-form-file-editor/store/form-file-editor.store.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-form-file-editor/store/form-file-editor.store.ts index 22dacea3f112..81cb23ba2281 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-form-file-editor/store/form-file-editor.store.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-form-file-editor/store/form-file-editor.store.ts @@ -10,7 +10,7 @@ import { switchMap, tap } from 'rxjs/operators'; import { ComponentStatus, DotHttpErrorResponse } from '@dotcms/dotcms-models'; -import { UPLOAD_TYPE, UploadedFile } from '../../../models'; +import { UPLOAD_TYPE, UploadedFile } from '../../../../../models/dot-edit-content-file.model'; import { DotFileFieldUploadService } from '../../../services/upload-file/upload-file.service'; import { extractFileExtension, getInfoByLang } from '../../../utils/editor'; import { DEFAULT_MONACO_CONFIG } from '../dot-form-file-editor.conts'; diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-form-import-url/dot-form-import-url.component.spec.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-form-import-url/dot-form-import-url.component.spec.ts index 278f73bdad29..266f9a580dbc 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-form-import-url/dot-form-import-url.component.spec.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-form-import-url/dot-form-import-url.component.spec.ts @@ -14,7 +14,7 @@ import { DotFormImportUrlComponent } from './dot-form-import-url.component'; import { FormImportUrlStore } from './store/form-import-url.store'; import { NEW_FILE_MOCK } from '../../../../utils/mocks'; -import { UploadedFile } from '../../models'; +import { UploadedFile } from '../../../../models/dot-edit-content-file.model'; import { DotFileFieldUploadService } from '../../services/upload-file/upload-file.service'; describe('DotFormImportUrlComponent', () => { diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-form-import-url/store/form-import-url.store.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-form-import-url/store/form-import-url.store.ts index de1dad6995a7..7648d15dcbdc 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-form-import-url/store/form-import-url.store.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-form-import-url/store/form-import-url.store.ts @@ -9,7 +9,7 @@ import { switchMap, tap } from 'rxjs/operators'; import { ComponentStatus, DotHttpErrorResponse } from '@dotcms/dotcms-models'; -import { UploadedFile, UPLOAD_TYPE } from '../../../models'; +import { UploadedFile, UPLOAD_TYPE } from '../../../../../models/dot-edit-content-file.model'; import { DotFileFieldUploadService } from '../../../services/upload-file/upload-file.service'; export interface FormImportUrlState { diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.html index 6fc9ea595e40..eca0040579dd 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.html +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.html @@ -1,9 +1,24 @@ +@let loading = $loading(); +@let data = $data(); +@let rowsPerPage = $rowsPerPage(); + + [value]="data" + [loading]="loading" + [paginator]="data.length >= rowsPerPage" + [rows]="rowsPerPage" + selectionMode="single" + dataKey="identifier" + [(selection)]="$selectedProduct" + (onRowSelect)="onRowSelect.emit($event.data)" + styleClass="p-datatable-lg flex flex-column h-full"> + + + + {{ 'dot.file.field.dialog.select.existing.file.table.emptymessage' | dm }} + + +
@@ -14,6 +29,9 @@ + + {{ 'dot.file.field.dialog.select.existing.file.table.thumbnail' | dm }} + {{ 'dot.file.field.dialog.select.existing.file.table.title' | dm }} {{ 'dot.file.field.dialog.select.existing.file.table.modified.by' | dm }} @@ -24,31 +42,21 @@ - - -
- - -

{{ content.title }}

+ + +
+
- {{ content.modifiedBy }} - {{ content.lastModified | date }} - - - - - @for (col of columns; track $index) { - - - - } + +

{{ content.title }}

+ + {{ content.modUserName }} + {{ content.modDate | date }}
diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.scss b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.scss index 8acfaa4d306e..7829c11789af 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.scss +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.scss @@ -12,6 +12,19 @@ } } +.truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.dataview-thumbnail { + width: 100%; + max-height: 4rem; + overflow: hidden; + position: relative; +} + .file-selector__table_header { th { font-weight: $font-weight-bold; diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.ts index f86bbe7e649f..f6cb4d235adc 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.ts @@ -1,26 +1,29 @@ -import { DatePipe, NgOptimizedImage } from '@angular/common'; -import { ChangeDetectionStrategy, Component, input } from '@angular/core'; +import { DatePipe, SlicePipe } from '@angular/common'; +import { + ChangeDetectionStrategy, + Component, + CUSTOM_ELEMENTS_SCHEMA, + input, + model, + output, + signal +} from '@angular/core'; import { ButtonModule } from 'primeng/button'; -import { DataViewModule } from 'primeng/dataview'; import { IconFieldModule } from 'primeng/iconfield'; import { ImageModule } from 'primeng/image'; import { InputIconModule } from 'primeng/inputicon'; import { InputTextModule } from 'primeng/inputtext'; import { SkeletonModule } from 'primeng/skeleton'; import { TableModule } from 'primeng/table'; -import { TagModule } from 'primeng/tag'; +import { DotCMSContentlet } from '@dotcms/dotcms-models'; import { DotMessagePipe } from '@dotcms/ui'; -import { Content } from '../../store/select-existing-file.store'; - @Component({ selector: 'dot-dataview', standalone: true, imports: [ - DataViewModule, - TagModule, ButtonModule, TableModule, IconFieldModule, @@ -28,23 +31,24 @@ import { Content } from '../../store/select-existing-file.store'; InputTextModule, SkeletonModule, ImageModule, - NgOptimizedImage, DatePipe, - DotMessagePipe + DotMessagePipe, + SlicePipe ], templateUrl: './dot-dataview.component.html', styleUrls: ['./dot-dataview.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush + changeDetection: ChangeDetectionStrategy.OnPush, + schemas: [CUSTOM_ELEMENTS_SCHEMA] }) export class DotDataViewComponent { /** * Represents an observable stream of content data. * - * @type {Observable} + * @type {Observable} * @alias data * @required */ - $data = input.required({ alias: 'data' }); + $data = input.required({ alias: 'data' }); /** * A boolean observable that indicates the loading state. * This is typically used to show or hide a loading indicator in the UI. @@ -52,4 +56,10 @@ export class DotDataViewComponent { * @type {boolean} */ $loading = input.required({ alias: 'loading' }); + + $rowsPerPage = signal(7); + + $selectedProduct = model(null); + + onRowSelect = output(); } diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.html index cd96d04248bc..3b0fd0b3889b 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.html +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.html @@ -4,10 +4,13 @@ {{ node.label | truncatePath | slice: 0 : 18 }} diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.ts index 17ccc9e90a01..4ad72bfdc7c0 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.ts @@ -5,8 +5,10 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, + computed, inject, input, + model, output, signal } from '@angular/core'; @@ -37,7 +39,7 @@ export class DotSideBarComponent { * @type {Observable} * @alias folders */ - $folders = input.required({ alias: 'folders' }); + $folders = input([], { alias: 'folders' }); /** * A boolean observable that indicates the loading state. * @@ -54,6 +56,19 @@ export class DotSideBarComponent { */ $fakeColumns = signal(Array.from({ length: 50 }).map((_) => this.getPercentage())); + $state = computed(() => { + const folders = this.$folders(); + + console.log(folders); + + return { + folders, + defaultFile: folders.find((folder) => folder.data.identifier === 'SYSTEM_HOST') + }; + }); + + $selectedFile = model(this.$state().defaultFile); + /** * Event emitter for when a tree node is expanded. * @@ -62,6 +77,14 @@ export class DotSideBarComponent { */ onNodeExpand = output(); + /** + * Event emitter for when a node is selected in the tree. + * + * @event onNodeSelect + * @type {TreeNodeExpandEvent} + */ + onNodeSelect = output(); + /** * Triggers change detection manually. * This method is used to ensure that the view is updated when the model changes. diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.html index bc7e68dda668..ad57f05bd908 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.html +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.html @@ -1,13 +1,19 @@ +@let folders = store.folders(); +
-
+
- +
@@ -17,11 +23,8 @@ [text]="true" severity="primary" /> -
diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.scss b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.scss index 9e493b3997b2..d38daafd1247 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.scss +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.scss @@ -3,13 +3,13 @@ .file-selector { height: 43rem; .file-selector__sidebar { + min-width: 16rem; + width: 25%; border-right: $field-border-size solid $color-palette-gray-400; ::ng-deep { .p-tree { border: 0px; - padding-right: 0px; - padding-top: 0px; - padding-bottom: 0px; + padding: 0px; } } } diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.ts index 050d6f019a89..4bcd41ce59a3 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.ts @@ -10,6 +10,7 @@ import { import { ButtonModule } from 'primeng/button'; import { DynamicDialogRef } from 'primeng/dynamicdialog'; +import { DotFileFieldUploadService } from '@dotcms/edit-content/fields/dot-edit-content-file-field/services/upload-file/upload-file.service'; import { DotMessagePipe } from '@dotcms/ui'; import { DotDataViewComponent } from './components/dot-dataview/dot-dataview.component'; @@ -37,6 +38,8 @@ export class DotSelectExistingFileComponent implements OnInit { * This store is used to manage the state and actions related to selecting existing files. */ readonly store = inject(SelectExisingFileStore); + + readonly #uploadService = inject(DotFileFieldUploadService); /** * A reference to the dynamic dialog instance. * This is a read-only property that is injected using Angular's dependency injection. @@ -63,8 +66,8 @@ export class DotSelectExistingFileComponent implements OnInit { } ngOnInit() { - this.store.loadContent(); this.store.loadFolders(); + this.store.loadContent(); } /** @@ -77,4 +80,11 @@ export class DotSelectExistingFileComponent implements OnInit { closeDialog(): void { this.#dialogRef.close(); } + + addContent(): void { + const content = this.store.selectedContent(); + this.#uploadService.getContentById(content.identifier).subscribe((content) => { + this.#dialogRef.close(content); + }); + } } diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/store/select-existing-file.store.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/store/select-existing-file.store.ts index e2c2524e31e8..e6fc13147bf5 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/store/select-existing-file.store.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/store/select-existing-file.store.ts @@ -1,4 +1,3 @@ -import { faker } from '@faker-js/faker'; import { tapResponse } from '@ngrx/component-store'; import { patchState, signalStore, withComputed, withMethods, withState } from '@ngrx/signals'; import { rxMethod } from '@ngrx/signals/rxjs-interop'; @@ -6,20 +5,19 @@ import { pipe } from 'rxjs'; import { computed, inject } from '@angular/core'; -import { TreeNode } from 'primeng/api'; - import { exhaustMap, switchMap, tap } from 'rxjs/operators'; import { ComponentStatus, DotCMSContentlet } from '@dotcms/dotcms-models'; - import { TreeNodeItem, TreeNodeSelectItem -} from '../../../../../models/dot-edit-content-host-folder-field.interface'; -import { DotEditContentService } from '../../../../../services/dot-edit-content.service'; +} from '@dotcms/edit-content/models/dot-edit-content-host-folder-field.interface'; +import { DotEditContentService } from '@dotcms/edit-content/services/dot-edit-content.service'; export const PEER_PAGE_LIMIT = 1000; +export const SYSTEM_HOST_ID = 'SYSTEM_HOST'; + export interface Content { id: string; image: string; @@ -35,11 +33,11 @@ export interface SelectExisingFileState { nodeExpaned: TreeNodeSelectItem['node'] | null; }; content: { - data: Content[]; + data: DotCMSContentlet[]; status: ComponentStatus; }; - selectedFolder: TreeNode | null; - selectedFile: DotCMSContentlet | null; + currentSite: TreeNodeItem | null; + selectedContent: DotCMSContentlet | null; searchQuery: string; viewMode: 'list' | 'grid'; } @@ -54,8 +52,8 @@ const initialState: SelectExisingFileState = { data: [], status: ComponentStatus.INIT }, - selectedFolder: null, - selectedFile: null, + currentSite: null, + selectedContent: null, searchQuery: '', viewMode: 'list' }; @@ -70,25 +68,43 @@ export const SelectExisingFileStore = signalStore( const dotEditContentService = inject(DotEditContentService); return { - loadContent: () => { - const mockContent = faker.helpers.multiple( - () => ({ - id: faker.string.uuid(), - image: faker.image.url(), - title: faker.commerce.productName(), - modifiedBy: faker.internet.displayName(), - lastModified: faker.date.recent() - }), - { count: 100 } - ); - + setSelectedContent: (selectedContent: DotCMSContentlet) => { patchState(store, { - content: { - data: mockContent, - status: ComponentStatus.LOADED - } + selectedContent }); }, + loadContent: rxMethod( + pipe( + tap(() => + patchState(store, { + content: { ...store.content(), status: ComponentStatus.LOADING } + }) + ), + switchMap((event) => { + const content = store.content(); + + let identifier = SYSTEM_HOST_ID; + + if (event) { + identifier = event.node.data.identifier; + } + + return dotEditContentService.getContentByFolder(identifier).pipe( + tapResponse({ + next: (data) => { + patchState(store, { + content: { data, status: ComponentStatus.LOADED } + }); + }, + error: () => + patchState(store, { + content: { ...content, status: ComponentStatus.ERROR } + }) + }) + ); + }) + ) + ), loadFolders: rxMethod( pipe( tap(() => diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/dot-edit-content-file-field.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/dot-edit-content-file-field.component.ts index 6f7d14061d31..368a8a454641 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/dot-edit-content-file-field.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/dot-edit-content-file-field.component.ts @@ -21,6 +21,7 @@ import { filter, map } from 'rxjs/operators'; import { DotAiService, DotMessageService } from '@dotcms/data-access'; import { DotCMSContentTypeField, DotGeneratedAIImage } from '@dotcms/dotcms-models'; +import { INPUT_TYPES, UploadedFile } from '@dotcms/edit-content/models/dot-edit-content-file.model'; import { DotDropZoneComponent, DotMessagePipe, @@ -35,7 +36,6 @@ import { DotFileFieldUiMessageComponent } from './components/dot-file-field-ui-m import { DotFormFileEditorComponent } from './components/dot-form-file-editor/dot-form-file-editor.component'; import { DotFormImportUrlComponent } from './components/dot-form-import-url/dot-form-import-url.component'; import { DotSelectExistingFileComponent } from './components/dot-select-existing-file/dot-select-existing-file.component'; -import { INPUT_TYPES, UploadedFile } from './models'; import { DotFileFieldUploadService } from './services/upload-file/upload-file.service'; import { FileFieldStore } from './store/file-field.store'; import { getUiMessage } from './utils/messages'; @@ -415,7 +415,7 @@ export class DotEditContentFileFieldComponent implements ControlValueAccessor, O takeUntilDestroyed(this.#destroyRef) ) .subscribe((file) => { - this.store.setPreviewFile(file); + this.store.setPreviewFile({ source: 'contentlet', file }); }); } diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/dot-edit-content-file-field.const.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/dot-edit-content-file-field.const.ts index 2cd8dcccd076..24b69514ecc2 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/dot-edit-content-file-field.const.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/dot-edit-content-file-field.const.ts @@ -1,4 +1,4 @@ -import { INPUT_TYPES } from './models'; +import { INPUT_TYPES } from '../../models/dot-edit-content-file.model'; type Actions = { allowExistingFile: boolean; diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/dot-edit-content-file-field.stories.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/dot-edit-content-file-field.stories.ts index 30af8811f100..4fcf5647498c 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/dot-edit-content-file-field.stories.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/dot-edit-content-file-field.stories.ts @@ -13,14 +13,14 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { DotMessageService, DotUploadFileService } from '@dotcms/data-access'; import { DotCMSContentTypeField } from '@dotcms/dotcms-models'; +import { UIMessage } from '@dotcms/edit-content/models/dot-edit-content-file.model'; +import { DotEditContentService } from '@dotcms/edit-content/services/dot-edit-content.service'; import { DotEditContentFileFieldComponent } from './dot-edit-content-file-field.component'; -import { UIMessage } from './models'; import { DotFileFieldUploadService } from './services/upload-file/upload-file.service'; import { FileFieldStore } from './store/file-field.store'; import { MessageServiceMock } from './utils/mocks'; -import { DotEditContentService } from '../../services/dot-edit-content.service'; import { BINARY_FIELD_MOCK, FILE_FIELD_MOCK, diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/services/upload-file/upload-file.service.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/services/upload-file/upload-file.service.ts index dc34ea275c7d..e8cd7c65e588 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/services/upload-file/upload-file.service.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/services/upload-file/upload-file.service.ts @@ -7,9 +7,9 @@ import { map, switchMap, tap } from 'rxjs/operators'; import { DotUploadFileService, DotUploadService } from '@dotcms/data-access'; import { DotCMSContentlet, DotCMSTempFile } from '@dotcms/dotcms-models'; +import { UploadedFile, UPLOAD_TYPE } from '@dotcms/edit-content/models/dot-edit-content-file.model'; +import { DotEditContentService } from '@dotcms/edit-content/services/dot-edit-content.service'; -import { DotEditContentService } from '../../../../services/dot-edit-content.service'; -import { UploadedFile, UPLOAD_TYPE } from '../../models'; import { getFileMetadata, getFileVersion, checkMimeType } from '../../utils'; export type UploadFileProps = { diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/store/file-field.store.spec.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/store/file-field.store.spec.ts index e52df720c598..8511fa9f2f18 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/store/file-field.store.spec.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/store/file-field.store.spec.ts @@ -5,8 +5,8 @@ import { TestBed, fakeAsync, tick } from '@angular/core/testing'; import { FileFieldStore } from './file-field.store'; +import { UIMessage } from '../../../models/dot-edit-content-file.model'; import { NEW_FILE_MOCK } from '../../../utils/mocks'; -import { UIMessage } from '../models'; import { DotFileFieldUploadService } from '../services/upload-file/upload-file.service'; import { getUiMessage } from '../utils/messages'; diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/store/file-field.store.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/store/file-field.store.ts index c5995e7f5c1a..d31d091f2f19 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/store/file-field.store.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/store/file-field.store.ts @@ -7,8 +7,14 @@ import { computed, inject } from '@angular/core'; import { filter, switchMap, tap } from 'rxjs/operators'; +import { + INPUT_TYPES, + FILE_STATUS, + UIMessage, + UploadedFile +} from '@dotcms/edit-content/models/dot-edit-content-file.model'; + import { INPUT_CONFIG } from '../dot-edit-content-file-field.const'; -import { INPUT_TYPES, FILE_STATUS, UIMessage, UploadedFile } from '../models'; import { DotFileFieldUploadService } from '../services/upload-file/upload-file.service'; import { getUiMessage } from '../utils/messages'; diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/utils/messages.spec.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/utils/messages.spec.ts index f4406a10887c..9c44e93ac60f 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/utils/messages.spec.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/utils/messages.spec.ts @@ -1,6 +1,6 @@ import { getUiMessage, UiMessageMap } from './messages'; -import { MESSAGES_TYPES } from '../models'; +import { MESSAGES_TYPES } from '../../../models/dot-edit-content-file.model'; describe('getUiMessage function', () => { it('should return the correct uiMessage for a valid key', () => { diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/utils/messages.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/utils/messages.ts index b2f3d5fa491b..ce923fd5e552 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/utils/messages.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/utils/messages.ts @@ -1,4 +1,8 @@ -import { MESSAGES_TYPES, UIMessage, UIMessagesMap } from '../models'; +import { + MESSAGES_TYPES, + UIMessage, + UIMessagesMap +} from '../../../models/dot-edit-content-file.model'; export const UiMessageMap: UIMessagesMap = { DEFAULT: { diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/models/index.ts b/core-web/libs/edit-content/src/lib/models/dot-edit-content-file.model.ts similarity index 100% rename from core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/models/index.ts rename to core-web/libs/edit-content/src/lib/models/dot-edit-content-file.model.ts diff --git a/core-web/libs/edit-content/src/lib/models/dot-edit-content-host-folder-field.interface.ts b/core-web/libs/edit-content/src/lib/models/dot-edit-content-host-folder-field.interface.ts index 56ad0d7f5bf4..58369e26e8b9 100644 --- a/core-web/libs/edit-content/src/lib/models/dot-edit-content-host-folder-field.interface.ts +++ b/core-web/libs/edit-content/src/lib/models/dot-edit-content-host-folder-field.interface.ts @@ -1,6 +1,11 @@ import { TreeNode } from 'primeng/api'; -export type TreeNodeData = { type: 'site' | 'folder'; path: string; hostname: string }; +export type TreeNodeData = { + type: 'site' | 'folder'; + path: string; + hostname: string; + identifier: string; +}; export type TreeNodeItem = TreeNode; @@ -22,6 +27,7 @@ export interface TreeNodeSelectEvent { } export interface DotFolder { + identifier: string; hostName: string; path: string; addChildrenAllowed: boolean; diff --git a/core-web/libs/edit-content/src/lib/services/dot-edit-content.service.ts b/core-web/libs/edit-content/src/lib/services/dot-edit-content.service.ts index 10f245b9cb4c..06a39b11a5fd 100644 --- a/core-web/libs/edit-content/src/lib/services/dot-edit-content.service.ts +++ b/core-web/libs/edit-content/src/lib/services/dot-edit-content.service.ts @@ -88,6 +88,7 @@ export class DotEditContentService { key: site.hostname, label: `//${site.hostname}`, data: { + identifier: site.identifier, hostname: `//${site.hostname}`, path: '', type: 'site' @@ -132,6 +133,7 @@ export class DotEditContentService { key: `${folder.hostName}${folder.path}`.replace(/[/]/g, ''), label: `//${folder.hostName}${folder.path}`, data: { + identifier: folder.identifier, hostname: `//${folder.hostName}`, path: folder.path, type: 'folder' @@ -202,6 +204,7 @@ export class DotEditContentService { key: site.hostname, label: `//${site.hostname}`, data: { + identifier: site.identifier, hostname: `//${site.hostname}`, path: '', type: 'site' @@ -223,4 +226,20 @@ export class DotEditContentService { .get<{ entity: { count: number } }>(`/api/v1/content/${identifier}/references/count`) .pipe(map((response) => response.entity.count)); } + + getContentByFolder(folderId: string) { + const params = { + hostFolderId: folderId, + showLinks: false, + showDotAssets: true, + showPages: true, + showFiles: true, + showFolders: false, + showWorking: true, + showArchived: true, + sortByDesc: true + }; + + return this.#siteService.getContentByFolder(params); + } } diff --git a/core-web/package.json b/core-web/package.json index 0ed2eb181bc2..7872f940db08 100644 --- a/core-web/package.json +++ b/core-web/package.json @@ -115,7 +115,7 @@ "node-fetch": "^2.6.1", "primeflex": "3.3.1", "primeicons": "7.0.0", - "primeng": "17.18.9", + "primeng": "17.18.11", "prosemirror-model": "^1.16.1", "prosemirror-view": "^1.23.12", "react": "^18.2.0", diff --git a/core-web/yarn.lock b/core-web/yarn.lock index 698361a2cd83..5a303d59070f 100644 --- a/core-web/yarn.lock +++ b/core-web/yarn.lock @@ -18117,10 +18117,10 @@ primeicons@7.0.0: resolved "https://registry.yarnpkg.com/primeicons/-/primeicons-7.0.0.tgz#6b25c3fdcb29bb745a3035bdc1ed5902f4a419cf" integrity sha512-jK3Et9UzwzTsd6tzl2RmwrVY/b8raJ3QZLzoDACj+oTJ0oX7L9Hy+XnVwgo4QVKlKpnP/Ur13SXV/pVh4LzaDw== -primeng@17.18.9: - version "17.18.9" - resolved "https://registry.yarnpkg.com/primeng/-/primeng-17.18.9.tgz#dc3b65689548b53ce07ce7032a0aa3d1eb5dfbea" - integrity sha512-1FT0B8wtgvs/joduB1DDOLe2IsP1pegOiEfSPAHSbc6otgNx/6iLR0k2M/xr2c9Ur1aC7tAikkVfH3FGpWof3w== +primeng@17.18.11: + version "17.18.11" + resolved "https://registry.yarnpkg.com/primeng/-/primeng-17.18.11.tgz#8e5b1b921b303767611f31a356ee972e78ccc98e" + integrity sha512-LzV0fFZmb3GdnaRqi1+GP+RPtW0a+jztL5pH1zRWY7+7pyQ0n1YNyTXzmqVcdks/CmoyjNhutWEmexwi6vFVeA== dependencies: tslib "^2.3.0" diff --git a/dotCMS/src/main/webapp/WEB-INF/messages/Language.properties b/dotCMS/src/main/webapp/WEB-INF/messages/Language.properties index 53ceab023a81..409ada13e024 100644 --- a/dotCMS/src/main/webapp/WEB-INF/messages/Language.properties +++ b/dotCMS/src/main/webapp/WEB-INF/messages/Language.properties @@ -1191,6 +1191,8 @@ dot.file.field.action.import.from.url=Import from URL dot.file.field.action.import.from.url.error.message=The URL you requested is not valid. Please try again. dot.file.field.action.remove=Remove dot.file.field.dialog.select.existing.file.header=Select Existing File +dot.file.field.dialog.select.existing.file.table.emptymessage=No content found +dot.file.field.dialog.select.existing.file.table.thumbnail=Thumbnail dot.file.field.dialog.select.existing.file.table.title=Title dot.file.field.dialog.select.existing.file.table.modified.by=Modified by dot.file.field.dialog.select.existing.file.table.last.modified=Last Modified From efa2947b2682eba7a6cfdfc500383472715fcbe3 Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Fri, 8 Nov 2024 16:09:30 -0400 Subject: [PATCH 12/17] chore(edit-content): enable search #30216 --- .../dot-dataview/dot-dataview.component.html | 13 ++++++++++--- .../dot-dataview/dot-dataview.component.scss | 11 ++++++++--- .../dot-dataview/dot-dataview.component.ts | 2 +- .../dot-sidebar/dot-sidebar.component.html | 4 ++-- .../dot-sidebar/dot-sidebar.component.ts | 16 +++++++++++++++- .../dot-select-existing-file.component.scss | 2 +- 6 files changed, 37 insertions(+), 11 deletions(-) diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.html index eca0040579dd..65090be5afdb 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.html +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.html @@ -3,6 +3,7 @@ @let rowsPerPage = $rowsPerPage(); + [globalFilterFields]="['title', 'modUserName']" + styleClass="flex flex-column h-full justify-content-between"> @@ -23,7 +25,12 @@
- +
@@ -42,7 +49,7 @@ - +
({ alias: 'loading' }); - $rowsPerPage = signal(7); + $rowsPerPage = signal(9); $selectedProduct = model(null); diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.html index 3b0fd0b3889b..798f9b4f2496 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.html +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.html @@ -2,10 +2,10 @@ @if (!loading) { } * @alias folders */ - $folders = input([], { alias: 'folders' }); + $folders = input.required({ alias: 'folders' }); /** * A boolean observable that indicates the loading state. * @@ -73,6 +76,17 @@ export class DotSideBarComponent { */ onNodeSelect = output(); + $state = computed(() => { + const folders = this.$folders(); + + const selectedFile = folders.find((f) => f.data.identifier === SYSTEM_HOST_ID); + + return { + folders, + selectedFile: signal(selectedFile) + }; + }); + /** * Triggers change detection manually. * This method is used to ensure that the view is updated when the model changes. diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.scss b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.scss index d38daafd1247..6b70bcc6400b 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.scss +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.scss @@ -1,7 +1,7 @@ @use "variables" as *; .file-selector { - height: 43rem; + height: 45rem; .file-selector__sidebar { min-width: 16rem; width: 25%; From b850bfff04d43f49123cbde19ccb6e65a5760283 Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Fri, 8 Nov 2024 16:40:01 -0400 Subject: [PATCH 13/17] chore(edit-content): truncate side panel #30216 --- core-web/libs/dotcms-scss/angular/dotcms-theme/_misc.scss | 6 ++++++ .../components/dot-dataview/dot-dataview.component.html | 6 +++--- .../components/dot-dataview/dot-dataview.component.scss | 6 ------ .../components/dot-dataview/dot-dataview.component.ts | 7 ++----- .../components/dot-sidebar/dot-sidebar.component.html | 2 +- .../components/dot-sidebar/dot-sidebar.component.scss | 7 +++++++ .../components/dot-sidebar/dot-sidebar.component.ts | 3 +-- .../dot-select-existing-file.component.html | 2 +- .../dot-select-existing-file.component.scss | 1 + .../src/main/webapp/WEB-INF/messages/Language.properties | 1 + 10 files changed, 23 insertions(+), 18 deletions(-) diff --git a/core-web/libs/dotcms-scss/angular/dotcms-theme/_misc.scss b/core-web/libs/dotcms-scss/angular/dotcms-theme/_misc.scss index 9a77aee782b9..31e70d62c688 100644 --- a/core-web/libs/dotcms-scss/angular/dotcms-theme/_misc.scss +++ b/core-web/libs/dotcms-scss/angular/dotcms-theme/_misc.scss @@ -81,3 +81,9 @@ a.p-button { width: $icon-lg-box; font-size: $icon-lg; } + +.truncate-text { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.html index 65090be5afdb..91c71784680c 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.html +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.html @@ -28,8 +28,8 @@
@@ -60,7 +60,7 @@
-

{{ content.title }}

+

{{ content.title }}

{{ content.modUserName }} {{ content.modDate | date }} diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.scss b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.scss index 7b2aeee5c74e..adedadaa3b9f 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.scss +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.scss @@ -17,12 +17,6 @@ } } -.truncate { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - .dataview-thumbnail { width: 100%; max-height: 3rem; diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.ts index f818ed8a4ed2..07e5e95c8f08 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.ts @@ -1,4 +1,4 @@ -import { DatePipe, SlicePipe } from '@angular/common'; +import { DatePipe } from '@angular/common'; import { ChangeDetectionStrategy, Component, @@ -11,7 +11,6 @@ import { import { ButtonModule } from 'primeng/button'; import { IconFieldModule } from 'primeng/iconfield'; -import { ImageModule } from 'primeng/image'; import { InputIconModule } from 'primeng/inputicon'; import { InputTextModule } from 'primeng/inputtext'; import { SkeletonModule } from 'primeng/skeleton'; @@ -30,10 +29,8 @@ import { DotMessagePipe } from '@dotcms/ui'; InputIconModule, InputTextModule, SkeletonModule, - ImageModule, DatePipe, - DotMessagePipe, - SlicePipe + DotMessagePipe ], templateUrl: './dot-dataview.component.html', styleUrls: ['./dot-dataview.component.scss'], diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.html index 798f9b4f2496..0db420b65008 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.html +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.html @@ -13,7 +13,7 @@ (onNodeSelect)="onNodeSelect.emit($event)" (onNodeExpand)="onNodeExpand.emit($event)"> - {{ node.label | truncatePath | slice: 0 : 18 }} + {{ node.label | truncatePath }} } @else { diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.scss b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.scss index 9c94b8ec9f4d..2b3dc10fc538 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.scss +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.scss @@ -1,4 +1,11 @@ :host { height: 100%; display: flex; + ::ng-deep { + .p-treenode-label { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + } } diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.ts index 4f2a5da2912a..3048d4922f5d 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.ts @@ -1,6 +1,5 @@ import { faker } from '@faker-js/faker'; -import { SlicePipe } from '@angular/common'; import { ChangeDetectionStrategy, ChangeDetectorRef, @@ -24,7 +23,7 @@ import { SYSTEM_HOST_ID } from '../../store/select-existing-file.store'; @Component({ selector: 'dot-sidebar', standalone: true, - imports: [TreeModule, SlicePipe, TruncatePathPipe, SkeletonModule], + imports: [TreeModule, TruncatePathPipe, SkeletonModule], templateUrl: './dot-sidebar.component.html', styleUrls: ['./dot-sidebar.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.html index ad57f05bd908..8fc8c0b4c8ad 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.html +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.html @@ -1,7 +1,7 @@ @let folders = store.folders();
-
+
Date: Fri, 8 Nov 2024 16:47:28 -0400 Subject: [PATCH 14/17] chore(edit-content): add docs #30216 --- .../dot-file-field-preview.component.ts | 8 ++++---- .../dot-file-field-ui-message.component.ts | 3 +-- .../dot-form-file-editor.component.ts | 3 +-- .../dot-form-import-url.component.ts | 5 ++--- .../dot-dataview/dot-dataview.component.ts | 12 ++++++++++++ .../components/dot-sidebar/dot-sidebar.component.ts | 10 ++++++++++ .../dot-select-existing-file.component.ts | 4 ++++ 7 files changed, 34 insertions(+), 11 deletions(-) diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-file-field-preview/dot-file-field-preview.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-file-field-preview/dot-file-field-preview.component.ts index 47e39b4744ff..61754a7bc1df 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-file-field-preview/dot-file-field-preview.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-file-field-preview/dot-file-field-preview.component.ts @@ -19,6 +19,10 @@ import { catchError } from 'rxjs/operators'; import { DotResourceLinksService } from '@dotcms/data-access'; import { DotCMSBaseTypesContentTypes, DotCMSContentlet } from '@dotcms/dotcms-models'; +import { + DotPreviewResourceLink, + UploadedFile +} from '@dotcms/edit-content/models/dot-edit-content-file.model'; import { DotTempFileThumbnailComponent, DotFileSizeFormatPipe, @@ -26,10 +30,6 @@ import { DotCopyButtonComponent } from '@dotcms/ui'; -import { - DotPreviewResourceLink, - UploadedFile -} from '../../../../models/dot-edit-content-file.model'; import { getFileMetadata } from '../../utils'; @Component({ diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-file-field-ui-message/dot-file-field-ui-message.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-file-field-ui-message/dot-file-field-ui-message.component.ts index 0e956411a96f..4d14177c9958 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-file-field-ui-message/dot-file-field-ui-message.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-file-field-ui-message/dot-file-field-ui-message.component.ts @@ -1,10 +1,9 @@ import { NgClass } from '@angular/common'; import { ChangeDetectionStrategy, Component, input } from '@angular/core'; +import { UIMessage } from '@dotcms/edit-content/models/dot-edit-content-file.model'; import { DotMessagePipe } from '@dotcms/ui'; -import { UIMessage } from '../../../../models/dot-edit-content-file.model'; - @Component({ selector: 'dot-file-field-ui-message', standalone: true, diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-form-file-editor/dot-form-file-editor.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-form-file-editor/dot-form-file-editor.component.ts index a8018cb11a51..43982c895e9e 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-form-file-editor/dot-form-file-editor.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-form-file-editor/dot-form-file-editor.component.ts @@ -17,12 +17,11 @@ import { InputTextModule } from 'primeng/inputtext'; import { debounceTime, distinctUntilChanged, filter } from 'rxjs/operators'; +import { UploadedFile } from '@dotcms/edit-content/models/dot-edit-content-file.model'; import { DotMessagePipe, DotFieldValidationMessageComponent } from '@dotcms/ui'; import { FormFileEditorStore } from './store/form-file-editor.store'; -import { UploadedFile } from '../../../../models/dot-edit-content-file.model'; - type DialogProps = { allowFileNameEdit: boolean; userMonacoOptions: Partial; diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-form-import-url/dot-form-import-url.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-form-import-url/dot-form-import-url.component.ts index a97121b6daac..9308f75df601 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-form-import-url/dot-form-import-url.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-form-import-url/dot-form-import-url.component.ts @@ -12,12 +12,11 @@ import { ButtonModule } from 'primeng/button'; import { DynamicDialogRef, DynamicDialogConfig } from 'primeng/dynamicdialog'; import { InputTextModule } from 'primeng/inputtext'; +import { INPUT_TYPES } from '@dotcms/edit-content/models/dot-edit-content-file.model'; import { DotMessagePipe, DotFieldValidationMessageComponent, DotValidators } from '@dotcms/ui'; import { FormImportUrlStore } from './store/form-import-url.store'; -import { INPUT_TYPE } from '../../../dot-edit-content-text-field/utils'; - @Component({ selector: 'dot-form-import-url', standalone: true, @@ -38,7 +37,7 @@ export class DotFormImportUrlComponent implements OnInit { readonly #formBuilder = inject(FormBuilder); readonly #dialogRef = inject(DynamicDialogRef); readonly #dialogConfig = inject( - DynamicDialogConfig<{ inputType: INPUT_TYPE; acceptedFiles: string[] }> + DynamicDialogConfig<{ inputType: INPUT_TYPES; acceptedFiles: string[] }> ); #abortController: AbortController | null = null; diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.ts index 07e5e95c8f08..0725fbfed51d 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-dataview/dot-dataview.component.ts @@ -54,9 +54,21 @@ export class DotDataViewComponent { */ $loading = input.required({ alias: 'loading' }); + /** + * Signal representing the number of rows per page in the data view. + * + * @type {number} + */ $rowsPerPage = signal(9); + /** + * Reactive model holding the currently selected product. + * Can be a `DotCMSContentlet` or `null`. + */ $selectedProduct = model(null); + /** + * Emits the selected `DotCMSContentlet` when a row is selected. + */ onRowSelect = output(); } diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.ts index 3048d4922f5d..d6170306cf52 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/components/dot-sidebar/dot-sidebar.component.ts @@ -57,6 +57,9 @@ export class DotSideBarComponent { */ $fakeColumns = signal(Array.from({ length: 50 }).map((_) => this.getPercentage())); + /** + * Reactive model representing the currently selected file. + */ $selectedFile = model(null); /** @@ -75,6 +78,13 @@ export class DotSideBarComponent { */ onNodeSelect = output(); + /** + * Computed property representing the component's state. + * + * @returns An object containing: + * - `folders`: An array of folders obtained from `$folders()`. + * - `selectedFile`: A signal of the selected file, initialized to the file whose `data.identifier` matches `SYSTEM_HOST_ID`. + */ $state = computed(() => { const folders = this.$folders(); diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.ts index 4bcd41ce59a3..6b32a1326532 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-select-existing-file/dot-select-existing-file.component.ts @@ -81,6 +81,10 @@ export class DotSelectExistingFileComponent implements OnInit { this.#dialogRef.close(); } + /** + * Retrieves the selected content from the store, fetches it by ID using the upload service, + * and closes the dialog with the retrieved content. + */ addContent(): void { const content = this.store.selectedContent(); this.#uploadService.getContentById(content.identifier).subscribe((content) => { From b74accaf33e7b0c66e45c7d2d92891166a9ca2a0 Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Fri, 8 Nov 2024 16:49:03 -0400 Subject: [PATCH 15/17] chore(edit-content): fix problem with linter #30216 --- .../dot-form-import-url/dot-form-import-url.component.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-form-import-url/dot-form-import-url.component.spec.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-form-import-url/dot-form-import-url.component.spec.ts index 266f9a580dbc..0523ffa7092a 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-form-import-url/dot-form-import-url.component.spec.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-form-import-url/dot-form-import-url.component.spec.ts @@ -9,12 +9,12 @@ import { DialogService, DynamicDialogConfig, DynamicDialogRef } from 'primeng/dy import { DotMessageService } from '@dotcms/data-access'; import { ComponentStatus } from '@dotcms/dotcms-models'; +import { UploadedFile } from '@dotcms/edit-content/models/dot-edit-content-file.model'; import { DotFormImportUrlComponent } from './dot-form-import-url.component'; import { FormImportUrlStore } from './store/form-import-url.store'; import { NEW_FILE_MOCK } from '../../../../utils/mocks'; -import { UploadedFile } from '../../../../models/dot-edit-content-file.model'; import { DotFileFieldUploadService } from '../../services/upload-file/upload-file.service'; describe('DotFormImportUrlComponent', () => { From 24f7789c7153c521e74c02c59507e2c48239cc74 Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Fri, 8 Nov 2024 17:48:55 -0400 Subject: [PATCH 16/17] chore(edit-content): fix unit tests #30216 --- .../libs/data-access/src/lib/dot-site/dot-site.service.ts | 6 ++++++ core-web/libs/edit-content/src/lib/utils/mocks.ts | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/core-web/libs/data-access/src/lib/dot-site/dot-site.service.ts b/core-web/libs/data-access/src/lib/dot-site/dot-site.service.ts index 48a21b479a18..4588b71d36db 100644 --- a/core-web/libs/data-access/src/lib/dot-site/dot-site.service.ts +++ b/core-web/libs/data-access/src/lib/dot-site/dot-site.service.ts @@ -89,6 +89,12 @@ export class DotSiteService { .pipe(pluck('entity')); } + /** + * Retrieves contentlets from a specified folder. + * + * @param params - Parameters defining the folder and retrieval options. + * @returns An observable emitting an array of `DotCMSContentlet` items. + */ getContentByFolder(params: ContentByFolderParams) { return this.#http .post<{ entity: { list: DotCMSContentlet[] } }>('/api/v1/browser', params) diff --git a/core-web/libs/edit-content/src/lib/utils/mocks.ts b/core-web/libs/edit-content/src/lib/utils/mocks.ts index 221a569c8c9b..8d2cde606293 100644 --- a/core-web/libs/edit-content/src/lib/utils/mocks.ts +++ b/core-web/libs/edit-content/src/lib/utils/mocks.ts @@ -1303,6 +1303,7 @@ export const TREE_SELECT_SITES_MOCK: TreeNodeItem[] = [ key: 'demo.dotcms.com', label: 'demo.dotcms.com', data: { + identifier: 'demo.dotcms.com', hostname: 'demo.dotcms.com', path: '', type: 'site' @@ -1314,6 +1315,7 @@ export const TREE_SELECT_SITES_MOCK: TreeNodeItem[] = [ key: 'nico.dotcms.com', label: 'nico.dotcms.com', data: { + identifier: 'nico.dotcms.com', hostname: 'nico.dotcms.com', path: '', type: 'site' @@ -1325,6 +1327,7 @@ export const TREE_SELECT_SITES_MOCK: TreeNodeItem[] = [ key: 'System Host', label: 'System Host', data: { + identifier: 'System Host', hostname: 'System Host', path: '', type: 'site' @@ -1339,6 +1342,7 @@ export const TREE_SELECT_MOCK: TreeNodeItem[] = [ key: 'demo.dotcms.com', label: 'demo.dotcms.com', data: { + identifier: 'demo.dotcms.com', hostname: 'demo.dotcms.com', path: '', type: 'site' @@ -1350,6 +1354,7 @@ export const TREE_SELECT_MOCK: TreeNodeItem[] = [ key: 'demo.dotcms.comlevel1', label: 'demo.dotcms.com/level1/', data: { + identifier: 'demo.dotcms.comlevel1', hostname: 'demo.dotcms.com', path: '/level1/', type: 'folder' @@ -1361,6 +1366,7 @@ export const TREE_SELECT_MOCK: TreeNodeItem[] = [ key: 'demo.dotcms.comlevel1child1', label: 'demo.dotcms.com/level1/child1/', data: { + identifier: 'demo.dotcms.comlevel1child1', hostname: 'demo.dotcms.com', path: '/level1/child1/', type: 'folder' @@ -1374,6 +1380,7 @@ export const TREE_SELECT_MOCK: TreeNodeItem[] = [ key: 'demo.dotcms.comlevel2', label: 'demo.dotcms.com/level2/', data: { + identifier: 'demo.dotcms.comlevel2', hostname: 'demo.dotcms.com', path: '/level2/', type: 'folder' @@ -1387,6 +1394,7 @@ export const TREE_SELECT_MOCK: TreeNodeItem[] = [ key: 'nico.dotcms.com', label: 'nico.dotcms.com', data: { + identifier: 'nico.dotcms.com', hostname: 'nico.dotcms.com', path: '', type: 'site' From 6ddf93e99364f94eda9c2eb3274c87d0872fd0ec Mon Sep 17 00:00:00 2001 From: Nicolas Molina Date: Fri, 8 Nov 2024 21:14:35 -0400 Subject: [PATCH 17/17] chore(edit-content): fix unit tests #30216 --- ...ontent-host-folder-field.component.spec.ts | 118 +++++++----------- .../src/lib/utils/fake-rx-method.ts | 18 --- 2 files changed, 48 insertions(+), 88 deletions(-) delete mode 100644 core-web/libs/edit-content/src/lib/utils/fake-rx-method.ts diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-host-folder-field/dot-edit-content-host-folder-field.component.spec.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-host-folder-field/dot-edit-content-host-folder-field.component.spec.ts index 00858e0dd86c..8748b9022d5f 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-host-folder-field/dot-edit-content-host-folder-field.component.spec.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-host-folder-field/dot-edit-content-host-folder-field.component.spec.ts @@ -1,18 +1,16 @@ -import { Spectator, SpyObject, createComponentFactory } from '@ngneat/spectator/jest'; +import { createFakeEvent } from '@ngneat/spectator'; +import { Spectator, createComponentFactory, mockProvider } from '@ngneat/spectator/jest'; +import { of } from 'rxjs'; -import { signal } from '@angular/core'; +import { fakeAsync, tick } from '@angular/core/testing'; import { ControlContainer, FormGroupDirective } from '@angular/forms'; +import { DotEditContentService } from '@dotcms/edit-content/services/dot-edit-content.service'; import { mockMatchMedia } from '@dotcms/utils-testing'; import { DotEditContentHostFolderFieldComponent } from './dot-edit-content-host-folder-field.component'; import { HostFolderFiledStore } from './store/host-folder-field.store'; -import { - TreeNodeItem, - TreeNodeSelectItem -} from '../../models/dot-edit-content-host-folder-field.interface'; -import { newFakeRxMethod, getRxMethodFake } from '../../utils/fake-rx-method'; import { HOST_FOLDER_TEXT_MOCK, TREE_SELECT_SITES_MOCK, @@ -20,47 +18,30 @@ import { createFormGroupDirectiveMock } from '../../utils/mocks'; -class MockHostFolderFiledStore { - nodeSelected = signal(null); - nodeExpaned = signal(null); - tree = signal([]); - status = signal<'idle' | 'pending' | 'fulfilled' | { error: string }>('idle'); - - iconClasses = signal(['']); - - loadSites = newFakeRxMethod(); - loadChildren = newFakeRxMethod(); - chooseNode = newFakeRxMethod(); -} - -type TypeMock = SpyObject; - describe('DotEditContentHostFolderFieldComponent', () => { let spectator: Spectator; - let component: DotEditContentHostFolderFieldComponent; - let store: TypeMock; + let store: InstanceType; const createComponent = createComponentFactory({ component: DotEditContentHostFolderFieldComponent, componentViewProviders: [ { provide: ControlContainer, useValue: createFormGroupDirectiveMock() } ], - componentProviders: [ - { - provide: HostFolderFiledStore, - useClass: MockHostFolderFiledStore - } + componentProviders: [HostFolderFiledStore], + providers: [ + FormGroupDirective, + mockProvider(DotEditContentService, { + getSitesTreePath: jest.fn(() => of(TREE_SELECT_SITES_MOCK)) + }) ], - providers: [FormGroupDirective], detectChanges: false }); beforeEach(() => { spectator = createComponent(); spectator.setInput('field', { ...HOST_FOLDER_TEXT_MOCK }); - store = spectator.inject(HostFolderFiledStore, true) as unknown as TypeMock; - component = spectator.component; - component.formControl.setValue(null); + store = spectator.inject(HostFolderFiledStore, true); + spectator.component.formControl.setValue(null); mockMatchMedia(); }); @@ -71,15 +52,13 @@ describe('DotEditContentHostFolderFieldComponent', () => { }); it('should show options', () => { - store.tree.set(TREE_SELECT_SITES_MOCK); - const spyloadSites = getRxMethodFake(store.loadSites); - spectator.detectChanges(); + const loadSitesSpy = jest.spyOn(store, 'loadSites'); - const options = component.store.tree(); + spectator.component.ngOnInit(); - expect(options).toBe(TREE_SELECT_SITES_MOCK); - expect(component.$treeSelect().options).toBe(TREE_SELECT_SITES_MOCK); - expect(spyloadSites).toHaveBeenCalled(); + expect(store.tree()).toBe(TREE_SELECT_SITES_MOCK); + expect(spectator.component.$treeSelect().options).toBe(TREE_SELECT_SITES_MOCK); + expect(loadSitesSpy).toHaveBeenCalled(); }); it('should tree selection height and virtual scroll height be the same', async () => { @@ -98,43 +77,42 @@ describe('DotEditContentHostFolderFieldComponent', () => { }); describe('The init value with the root path', () => { - it('should show a root path', () => { - store.tree.set(TREE_SELECT_SITES_MOCK); + it('should show a root path', fakeAsync(() => { const nodeSelected = TREE_SELECT_SITES_MOCK[0]; - store.nodeSelected.set(nodeSelected); - component.formControl.setValue(nodeSelected.key); + spectator.component.formControl.setValue(nodeSelected.key); spectator.detectChanges(); - expect(component.formControl.value).toBe('demo.dotcms.com'); - expect(component.pathControl.value.key).toBe(nodeSelected.key); - expect(component.$treeSelect().value.label).toBe(nodeSelected.label); - }); - }); - describe('The init value with the one levels', () => { - it('should show a path selected', () => { - store.tree.set(TREE_SELECT_MOCK); - const nodeSelected = TREE_SELECT_MOCK[0].children[0]; - store.nodeSelected.set(nodeSelected); - component.formControl.setValue(nodeSelected.label); - spectator.detectChanges(); + tick(50); - expect(component.formControl.value).toBe('demo.dotcms.com/level1/'); - expect(component.pathControl.value.key).toBe(nodeSelected.key); - expect(component.$treeSelect().value.label).toBe(nodeSelected.label); - }); - }); + store.chooseNode({ + originalEvent: createFakeEvent('click'), + node: nodeSelected + }); + + tick(50); + + expect(spectator.component.formControl.value).toBe('demo.dotcms.com:/'); + expect(spectator.component.pathControl.value.key).toBe(nodeSelected.key); + expect(spectator.component.$treeSelect().value.label).toBe(nodeSelected.label); + })); - describe('The init value with the two levels', () => { - it('should show a path selected', () => { - store.tree.set(TREE_SELECT_MOCK); + it('should show a path selected with the two levels', fakeAsync(() => { const nodeSelected = TREE_SELECT_MOCK[0].children[0].children[0]; - store.nodeSelected.set(nodeSelected); - component.formControl.setValue(nodeSelected.label); + spectator.component.formControl.setValue(nodeSelected.label); spectator.detectChanges(); - expect(component.formControl.value).toBe('demo.dotcms.com/level1/child1/'); - expect(component.pathControl.value.key).toBe(nodeSelected.key); - expect(component.$treeSelect().value.label).toBe(nodeSelected.label); - }); + tick(50); + + store.chooseNode({ + originalEvent: createFakeEvent('click'), + node: nodeSelected + }); + + tick(50); + + expect(spectator.component.formControl.value).toBe('demo.dotcms.com:/level1/child1/'); + expect(spectator.component.pathControl.value.key).toBe(nodeSelected.key); + expect(spectator.component.$treeSelect().value.label).toBe(nodeSelected.label); + })); }); }); diff --git a/core-web/libs/edit-content/src/lib/utils/fake-rx-method.ts b/core-web/libs/edit-content/src/lib/utils/fake-rx-method.ts deleted file mode 100644 index c245af5da62e..000000000000 --- a/core-web/libs/edit-content/src/lib/utils/fake-rx-method.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { jest } from '@jest/globals'; -import { rxMethod } from '@ngrx/signals/rxjs-interop'; - -import { tap } from 'rxjs/operators'; - -export const FAKE_RX_METHOD = Symbol('FAKE_RX_METHOD'); - -export function newFakeRxMethod() { - const f = jest.fn(); - const r = rxMethod(tap((x) => f(x))); - r[FAKE_RX_METHOD] = f; - - return r; -} - -export function getRxMethodFake(rxMethod) { - return rxMethod[FAKE_RX_METHOD]; -}