diff --git a/core-web/libs/edit-content/src/lib/components/dot-edit-content-field/dot-edit-content-field.component.html b/core-web/libs/edit-content/src/lib/components/dot-edit-content-field/dot-edit-content-field.component.html index 84e0eeb93eb8..115d82c9bd5a 100644 --- a/core-web/libs/edit-content/src/lib/components/dot-edit-content-field/dot-edit-content-field.component.html +++ b/core-web/libs/edit-content/src/lib/components/dot-edit-content-field/dot-edit-content-field.component.html @@ -142,6 +142,14 @@ [field]="field" /> } } + @case (fieldTypes.RELATIONSHIP) { + @defer (on immediate) { + + } + } } @if (field.hint) { {{ field.hint }} diff --git a/core-web/libs/edit-content/src/lib/components/dot-edit-content-field/dot-edit-content-field.component.ts b/core-web/libs/edit-content/src/lib/components/dot-edit-content-field/dot-edit-content-field.component.ts index 511ad1382b37..86ffe887a20a 100644 --- a/core-web/libs/edit-content/src/lib/components/dot-edit-content-field/dot-edit-content-field.component.ts +++ b/core-web/libs/edit-content/src/lib/components/dot-edit-content-field/dot-edit-content-field.component.ts @@ -3,24 +3,25 @@ import { ControlContainer, ReactiveFormsModule } from '@angular/forms'; import { BlockEditorModule } from '@dotcms/block-editor'; import { DotCMSContentlet, DotCMSContentTypeField } from '@dotcms/dotcms-models'; +import { DotEditContentBinaryFieldComponent } from '@dotcms/edit-content/fields/dot-edit-content-binary-field/dot-edit-content-binary-field.component'; +import { DotEditContentCalendarFieldComponent } from '@dotcms/edit-content/fields/dot-edit-content-calendar-field/dot-edit-content-calendar-field.component'; +import { DotEditContentCategoryFieldComponent } from '@dotcms/edit-content/fields/dot-edit-content-category-field/dot-edit-content-category-field.component'; +import { DotEditContentCheckboxFieldComponent } from '@dotcms/edit-content/fields/dot-edit-content-checkbox-field/dot-edit-content-checkbox-field.component'; +import { DotEditContentCustomFieldComponent } from '@dotcms/edit-content/fields/dot-edit-content-custom-field/dot-edit-content-custom-field.component'; +import { DotEditContentFileFieldComponent } from '@dotcms/edit-content/fields/dot-edit-content-file-field/dot-edit-content-file-field.component'; +import { DotEditContentHostFolderFieldComponent } from '@dotcms/edit-content/fields/dot-edit-content-host-folder-field/dot-edit-content-host-folder-field.component'; +import { DotEditContentJsonFieldComponent } from '@dotcms/edit-content/fields/dot-edit-content-json-field/dot-edit-content-json-field.component'; +import { DotEditContentKeyValueComponent } from '@dotcms/edit-content/fields/dot-edit-content-key-value/dot-edit-content-key-value.component'; +import { DotEditContentMultiSelectFieldComponent } from '@dotcms/edit-content/fields/dot-edit-content-multi-select-field/dot-edit-content-multi-select-field.component'; +import { DotEditContentRadioFieldComponent } from '@dotcms/edit-content/fields/dot-edit-content-radio-field/dot-edit-content-radio-field.component'; +import { DotEditContentRelationshipFieldComponent } from '@dotcms/edit-content/fields/dot-edit-content-relationship-field/dot-edit-content-relationship-field.component'; +import { DotEditContentSelectFieldComponent } from '@dotcms/edit-content/fields/dot-edit-content-select-field/dot-edit-content-select-field.component'; +import { DotEditContentTagFieldComponent } from '@dotcms/edit-content/fields/dot-edit-content-tag-field/dot-edit-content-tag-field.component'; +import { DotEditContentTextAreaComponent } from '@dotcms/edit-content/fields/dot-edit-content-text-area/dot-edit-content-text-area.component'; +import { DotEditContentTextFieldComponent } from '@dotcms/edit-content/fields/dot-edit-content-text-field/dot-edit-content-text-field.component'; +import { DotEditContentWYSIWYGFieldComponent } from '@dotcms/edit-content/fields/dot-edit-content-wysiwyg-field/dot-edit-content-wysiwyg-field.component'; import { DotFieldRequiredDirective } from '@dotcms/ui'; -import { DotEditContentBinaryFieldComponent } from '../../fields/dot-edit-content-binary-field/dot-edit-content-binary-field.component'; -import { DotEditContentCalendarFieldComponent } from '../../fields/dot-edit-content-calendar-field/dot-edit-content-calendar-field.component'; -import { DotEditContentCategoryFieldComponent } from '../../fields/dot-edit-content-category-field/dot-edit-content-category-field.component'; -import { DotEditContentCheckboxFieldComponent } from '../../fields/dot-edit-content-checkbox-field/dot-edit-content-checkbox-field.component'; -import { DotEditContentCustomFieldComponent } from '../../fields/dot-edit-content-custom-field/dot-edit-content-custom-field.component'; -import { DotEditContentFileFieldComponent } from '../../fields/dot-edit-content-file-field/dot-edit-content-file-field.component'; -import { DotEditContentHostFolderFieldComponent } from '../../fields/dot-edit-content-host-folder-field/dot-edit-content-host-folder-field.component'; -import { DotEditContentJsonFieldComponent } from '../../fields/dot-edit-content-json-field/dot-edit-content-json-field.component'; -import { DotEditContentKeyValueComponent } from '../../fields/dot-edit-content-key-value/dot-edit-content-key-value.component'; -import { DotEditContentMultiSelectFieldComponent } from '../../fields/dot-edit-content-multi-select-field/dot-edit-content-multi-select-field.component'; -import { DotEditContentRadioFieldComponent } from '../../fields/dot-edit-content-radio-field/dot-edit-content-radio-field.component'; -import { DotEditContentSelectFieldComponent } from '../../fields/dot-edit-content-select-field/dot-edit-content-select-field.component'; -import { DotEditContentTagFieldComponent } from '../../fields/dot-edit-content-tag-field/dot-edit-content-tag-field.component'; -import { DotEditContentTextAreaComponent } from '../../fields/dot-edit-content-text-area/dot-edit-content-text-area.component'; -import { DotEditContentTextFieldComponent } from '../../fields/dot-edit-content-text-field/dot-edit-content-text-field.component'; -import { DotEditContentWYSIWYGFieldComponent } from '../../fields/dot-edit-content-wysiwyg-field/dot-edit-content-wysiwyg-field.component'; import { CALENDAR_FIELD_TYPES } from '../../models/dot-edit-content-field.constant'; import { FIELD_TYPES } from '../../models/dot-edit-content-field.enum'; @@ -54,10 +55,10 @@ import { FIELD_TYPES } from '../../models/dot-edit-content-field.enum'; DotEditContentCategoryFieldComponent, DotFieldRequiredDirective, BlockEditorModule, - DotEditContentBinaryFieldComponent, DotEditContentKeyValueComponent, DotEditContentWYSIWYGFieldComponent, - DotEditContentFileFieldComponent + DotEditContentFileFieldComponent, + DotEditContentRelationshipFieldComponent ] }) export class DotEditContentFieldComponent { diff --git a/core-web/libs/edit-content/src/lib/components/dot-edit-content-form/utils.ts b/core-web/libs/edit-content/src/lib/components/dot-edit-content-form/utils.ts index 8a9d760c60af..d879cde01f78 100644 --- a/core-web/libs/edit-content/src/lib/components/dot-edit-content-form/utils.ts +++ b/core-web/libs/edit-content/src/lib/components/dot-edit-content-form/utils.ts @@ -62,5 +62,6 @@ export const resolutionValue: Record = { } return field.defaultValue ?? []; - } + }, + [FIELD_TYPES.RELATIONSHIP]: defaultResolutionFn }; diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-relationship-field/components/dot-select-existing-content/dot-select-existing-content.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-relationship-field/components/dot-select-existing-content/dot-select-existing-content.component.html new file mode 100644 index 000000000000..a2ec2f8d40bb --- /dev/null +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-relationship-field/components/dot-select-existing-content/dot-select-existing-content.component.html @@ -0,0 +1,89 @@ +@let rowsPerPage = 25; +@let data = store.data(); + + + + +
+ + + + +
+
+ + + + + Title + + + + Step + + + + Description + + + + Last Update + + + Menu + + + + + +
+

Relate content by clicking on the Plus Button

+
+ + +
+ + + + + + +

{{ item.title }}

+ + {{ item.step }} + +

{{ item.description }}

+ + {{ item.lastUpdate }} + + + + +
+
+ + + + +
diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-relationship-field/components/dot-select-existing-content/dot-select-existing-content.component.scss b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-relationship-field/components/dot-select-existing-content/dot-select-existing-content.component.scss new file mode 100644 index 000000000000..1c8300c5b264 --- /dev/null +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-relationship-field/components/dot-select-existing-content/dot-select-existing-content.component.scss @@ -0,0 +1,20 @@ +@use "variables" as *; + +::ng-deep { + p-table { + .p-datatable { + border: 1px solid $color-palette-gray-300; + border-radius: $border-radius-md; + .p-datatable-header { + background-color: $color-palette-gray-100; + } + } + } +} + +.existing-content__table_header { + th { + font-weight: $font-weight-bold; + background-color: $color-palette-gray-100; + } +} diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-relationship-field/components/dot-select-existing-content/dot-select-existing-content.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-relationship-field/components/dot-select-existing-content/dot-select-existing-content.component.ts new file mode 100644 index 000000000000..1905da478095 --- /dev/null +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-relationship-field/components/dot-select-existing-content/dot-select-existing-content.component.ts @@ -0,0 +1,47 @@ +import { ChangeDetectionStrategy, Component, inject, model } from '@angular/core'; + +import { ButtonModule } from 'primeng/button'; +import { DialogModule } from 'primeng/dialog'; +import { IconFieldModule } from 'primeng/iconfield'; +import { InputIconModule } from 'primeng/inputicon'; +import { InputTextModule } from 'primeng/inputtext'; +import { MenuModule } from 'primeng/menu'; +import { TableModule } from 'primeng/table'; + +import { DotMessagePipe } from '@dotcms/ui'; + +import { Content, ExistingContentStore } from './store/existing-content.store'; + +@Component({ + selector: 'dot-select-existing-content', + standalone: true, + imports: [ + TableModule, + ButtonModule, + MenuModule, + DotMessagePipe, + DialogModule, + IconFieldModule, + InputIconModule, + InputTextModule + ], + templateUrl: './dot-select-existing-content.component.html', + styleUrls: ['./dot-select-existing-content.component.scss'], + providers: [ExistingContentStore], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class DotSelectExistingContentComponent { + /** + * A readonly instance of the ExistingContentStore injected into the component. + * This store is used to manage the state and actions related to the existing content. + */ + readonly store = inject(ExistingContentStore); + + $visible = model(false, { alias: 'visible' }); + + $selectedItems = model([]); + + closeDialog() { + this.$visible.set(false); + } +} diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-relationship-field/components/dot-select-existing-content/store/existing-content.store.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-relationship-field/components/dot-select-existing-content/store/existing-content.store.ts new file mode 100644 index 000000000000..b02e03714c93 --- /dev/null +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-relationship-field/components/dot-select-existing-content/store/existing-content.store.ts @@ -0,0 +1,57 @@ +import { faker } from '@faker-js/faker'; +import { + patchState, + signalStore, + withComputed, + withHooks, + withMethods, + withState +} from '@ngrx/signals'; + +import { computed } from '@angular/core'; + +import { ComponentStatus } from '@dotcms/dotcms-models'; + +export interface Content { + id: string; + title: string; + step: string; + description: string; + lastUpdate: string; +} + +export interface ExistingContentState { + data: Content[]; + status: ComponentStatus; +} + +const initialState: ExistingContentState = { + data: [], + status: ComponentStatus.INIT +}; + +export const ExistingContentStore = signalStore( + withState(initialState), + withComputed((state) => ({ + isLoading: computed(() => state.status() === ComponentStatus.LOADING) + })), + withMethods((store) => ({ + loadContent() { + const mockData = Array.from({ length: 100 }, () => ({ + id: faker.string.uuid(), + title: faker.lorem.sentence(), + step: faker.helpers.arrayElement(['Draft', 'Published', 'Archived']), + description: faker.lorem.paragraph(), + lastUpdate: faker.date.recent().toISOString() + })); + patchState(store, { + data: mockData + }); + } + })), + withHooks({ + onInit: (store) => { + store.loadContent(); + } + }) +); diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-relationship-field/dot-edit-content-relationship-field.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-relationship-field/dot-edit-content-relationship-field.component.html new file mode 100644 index 000000000000..911a7bb9dcad --- /dev/null +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-relationship-field/dot-edit-content-relationship-field.component.html @@ -0,0 +1,35 @@ + + + + + + Title + Language + State + + + + + +
+ +

Relate content by clicking on the Plus Button

+
+ + +
+ + + {{ item.title }} + {{ item.language }} + {{ item.state }} + + +
+ + diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-relationship-field/dot-edit-content-relationship-field.component.scss b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-relationship-field/dot-edit-content-relationship-field.component.scss new file mode 100644 index 000000000000..5b8cb1ee787f --- /dev/null +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-relationship-field/dot-edit-content-relationship-field.component.scss @@ -0,0 +1,24 @@ +@use "variables" as *; + +::ng-deep { + p-table { + .p-datatable { + border: 1px solid $color-palette-gray-300; + border-radius: $border-radius-md; + .p-datatable-header { + background-color: $color-palette-gray-100; + } + } + } +} +.add-item-btn { + position: absolute; + top: 0.4rem; + right: 0.4rem; +} +.relationship-field__table_header { + th { + font-weight: $font-weight-bold; + background-color: $color-palette-gray-100; + } +} diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-relationship-field/dot-edit-content-relationship-field.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-relationship-field/dot-edit-content-relationship-field.component.ts new file mode 100644 index 000000000000..6c60c2925d8f --- /dev/null +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-relationship-field/dot-edit-content-relationship-field.component.ts @@ -0,0 +1,129 @@ +import { + ChangeDetectionStrategy, + Component, + DestroyRef, + forwardRef, + inject, + input, + signal +} from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; + +import { MenuItem } from 'primeng/api'; +import { ButtonModule } from 'primeng/button'; +import { DialogService, DynamicDialogRef } from 'primeng/dynamicdialog'; +import { MenuModule } from 'primeng/menu'; +import { TableModule } from 'primeng/table'; + +import { DotMessageService } from '@dotcms/data-access'; +import { DotCMSContentTypeField } from '@dotcms/dotcms-models'; +import { DotSelectExistingContentComponent } from '@dotcms/edit-content/fields/dot-edit-content-relationship-field/components/dot-select-existing-content/dot-select-existing-content.component'; + +import { RelationshipFieldStore } from './store/relationship-field.store'; + +@Component({ + selector: 'dot-edit-content-relationship-field', + standalone: true, + imports: [TableModule, ButtonModule, MenuModule, DotSelectExistingContentComponent], + providers: [ + RelationshipFieldStore, + DialogService, + { + multi: true, + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => DotEditContentRelationshipFieldComponent) + } + ], + templateUrl: './dot-edit-content-relationship-field.component.html', + styleUrls: ['./dot-edit-content-relationship-field.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class DotEditContentRelationshipFieldComponent implements ControlValueAccessor { + /** + * A readonly private field that injects the DotMessageService. + * This service is used for handling message-related functionalities within the component. + */ + readonly #dotMessageService = inject(DotMessageService); + /** + * A readonly private field that holds a reference to the `DestroyRef` service. + * This service is injected into the component to manage the destruction lifecycle. + */ + readonly #destroyRef = inject(DestroyRef); + /** + * A readonly private field that holds an instance of the DialogService. + * This service is injected using Angular's dependency injection mechanism. + * It is used to manage dialog interactions within the component. + */ + readonly #dialogService = inject(DialogService); + /** + * Reference to the dynamic dialog. It can be null if no dialog is currently open. + * + * @type {DynamicDialogRef | null} + */ + #dialogRef: DynamicDialogRef | null = null; + + $showExistingContentDialog = signal(false); + + private onChange: ((value: string) => void) | null = null; + private onTouched: (() => void) | null = null; + + $menuItems = signal([ + { + label: 'Existing Content', + command: () => { + this.$showExistingContentDialog.update((value) => !value); + } + }, + { + label: 'New Content', + command: () => { + // TODO: Implement new content + } + } + ]); + + /** + * A readonly instance of the RelationshipFieldStore injected into the component. + * This store is used to manage the state and actions related to the relationship field. + */ + readonly store = inject(RelationshipFieldStore); + + /** + * DotCMS Content Type Field + * + * @memberof DotEditContentFileFieldComponent + */ + $field = input.required({ alias: 'field' }); + + /** + * Set the value of the field. + * If the value is empty, nothing happens. + * If the value is not empty, the store is called to get the asset data. + * + * @param value the value to set + */ + writeValue(value: string): void { + if (!value) { + return; + } + } + /** + * Registers a callback function that is called when the control's value changes in the UI. + * This function is passed to the {@link NG_VALUE_ACCESSOR} token. + * + * @param fn The callback function to register. + */ + registerOnChange(fn: (value: string) => void) { + this.onChange = fn; + } + + /** + * Registers a callback function that is called when the control is marked as touched in the UI. + * This function is passed to the {@link NG_VALUE_ACCESSOR} token. + * + * @param fn The callback function to register. + */ + registerOnTouched(fn: () => void) { + this.onTouched = fn; + } +} diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-relationship-field/store/relationship-field.store.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-relationship-field/store/relationship-field.store.ts new file mode 100644 index 000000000000..4b44959c72a0 --- /dev/null +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-relationship-field/store/relationship-field.store.ts @@ -0,0 +1,32 @@ +import { patchState, signalStore, withComputed, withMethods, withState } from '@ngrx/signals'; + +import { computed } from '@angular/core'; + +export interface RelationshipFieldItem { + id: string; + title: string; + language: string; + state: string; +} + +export interface RelationshipFieldState { + data: RelationshipFieldItem[]; +} + +const initialState: RelationshipFieldState = { + data: [] +}; + +export const RelationshipFieldStore = signalStore( + withState(initialState), + withComputed(({ data }) => ({ + data2: computed(() => data()) + })), + withMethods((store) => ({ + setData(data: RelationshipFieldItem[]) { + patchState(store, { + data + }); + } + })) +); diff --git a/core-web/libs/edit-content/src/lib/models/dot-edit-content-field.enum.ts b/core-web/libs/edit-content/src/lib/models/dot-edit-content-field.enum.ts index 2f071ef11027..c23acf2ec028 100644 --- a/core-web/libs/edit-content/src/lib/models/dot-edit-content-field.enum.ts +++ b/core-web/libs/edit-content/src/lib/models/dot-edit-content-field.enum.ts @@ -30,5 +30,6 @@ export enum FIELD_TYPES { TEXT = 'Text', TEXTAREA = 'Textarea', TIME = 'Time', - WYSIWYG = 'WYSIWYG' + WYSIWYG = 'WYSIWYG', + RELATIONSHIP = 'Relationship' }