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 95983b495ea..620adb602ab 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 @@ -95,12 +95,16 @@ } @case (fieldTypes.FILE) { } @case (fieldTypes.IMAGE) { 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.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-file-field-preview/dot-file-field-preview.component.html index f0dc6ce69bc..28f8254bf09 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.html +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-file-field-preview/dot-file-field-preview.component.html @@ -1 +1,85 @@ -

Preview

+@let previewFile = $previewFile(); +@let metadata = $metadata(); + +
+
+ @if (previewFile.source === 'temp') { + + } @else { + + } +
+ + + +
+ + +
+ {{ 'Size' | dm }}: + +
+ + + + +
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.scss b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-file-field-preview/dot-file-field-preview.component.scss new file mode 100644 index 00000000000..8910d142fb2 --- /dev/null +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/components/dot-file-field-preview/dot-file-field-preview.component.scss @@ -0,0 +1,140 @@ +@use "variables" as *; + +:host { + display: block; + width: 100%; + height: 100%; +} + +.preview-container { + display: flex; + gap: $spacing-1; + align-items: flex-start; + justify-content: center; + height: 100%; + width: 100%; + position: relative; + container-type: inline-size; + container-name: preview; + + &:only-child { + gap: 0; + } +} + +.preview-image__container { + height: 100%; + width: 100%; + display: flex; + justify-content: center; + align-items: center; + background: $color-palette-gray-200; +} + +.preview-metadata__info { + display: flex; + justify-content: flex-start; + align-items: center; + gap: $spacing-0; +} + +.preview-metadata__container { + flex-grow: 1; + padding: $spacing-1; + padding-right: $spacing-6; + flex-direction: column; + overflow: hidden; + gap: $spacing-2; + min-width: 150px; + + span { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .preview-metadata_header { + font-size: $font-size-md; + font-weight: $font-weight-semi-bold; + margin: 0; + color: $black; + } +} + +.preview-metadata__actions { + position: absolute; + bottom: $spacing-1; + right: 0; + justify-content: flex-end; + align-items: center; + gap: $spacing-1; + z-index: 100; +} + +.file-info__item { + display: flex; + padding: $spacing-0 0; + flex-direction: column; + justify-content: center; + align-items: flex-start; + gap: $spacing-0; + + &:not(:last-child)::after { + content: ""; + display: block; + width: 100%; + height: 1px; + background: $color-palette-gray-200; + margin: $spacing-1 0; + } +} + +.file-info__link { + display: flex; + align-items: center; + gap: $spacing-1; + min-height: 32px; + font-size: $font-size-sm; + width: 100%; + + a { + color: $black; + text-decoration: none; + flex: 1 0 0; + } +} + +.file-info__title { + font-size: $font-size-sm; + font-style: normal; + font-weight: 600; +} + +.file-info__size { + display: flex; + align-items: center; + gap: $spacing-0; +} + +@container preview (min-width: 376px) { + .preview-metadata__container { + display: flex; + } + + .preview-metadata__action--responsive { + display: none; + } + + .preview-image__container { + height: 100%; + max-width: 17.5rem; + } + + .preview-resource-links__actions { + display: flex; + } + + .preview-overlay__container { + display: none; + } +} 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 00e7c8b5ec2..eeb15c4501d 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 @@ -1,12 +1,53 @@ -import { CUSTOM_ELEMENTS_SCHEMA, ChangeDetectionStrategy, Component } from '@angular/core'; +import { + CUSTOM_ELEMENTS_SCHEMA, + ChangeDetectionStrategy, + Component, + computed, + input, + output, + signal +} from '@angular/core'; + +import { ButtonModule } from 'primeng/button'; +import { DialogModule } from 'primeng/dialog'; + +import { DotTempFileThumbnailComponent, DotFileSizeFormatPipe, DotMessagePipe } from '@dotcms/ui'; + +import { PreviewFile } from '../../models'; +import { getFileMetadata } from '../../utils'; @Component({ selector: 'dot-file-field-preview', standalone: true, - imports: [], + imports: [ + DotTempFileThumbnailComponent, + DotFileSizeFormatPipe, + DotMessagePipe, + ButtonModule, + DialogModule + ], providers: [], templateUrl: './dot-file-field-preview.component.html', + styleUrls: ['./dot-file-field-preview.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, schemas: [CUSTOM_ELEMENTS_SCHEMA] }) -export class DotFileFieldPreviewComponent {} +export class DotFileFieldPreviewComponent { + $previewFile = input.required({ alias: 'previewFile' }); + $fieldVariable = input.required({ alias: 'fieldVariable' }); + removeFile = output(); + $showDialog = signal(false); + + $metadata = computed(() => { + const previewFile = this.$previewFile(); + if (previewFile.source === 'temp') { + return previewFile.file.metadata; + } + + return getFileMetadata(previewFile.file, this.$fieldVariable()); + }); + + toggleShowDialog() { + this.$showDialog.set(!this.$showDialog()); + } +} diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/dot-edit-content-file-field.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/dot-edit-content-file-field.component.html index c8b15727cbb..7e2ace36b1c 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/dot-edit-content-file-field.component.html +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/dot-edit-content-file-field.component.html @@ -7,10 +7,23 @@
- + + @if (store.uiMessage()) {
@@ -76,7 +88,12 @@ } @case ('preview') { - + @if (store.previewFile()) { + + } } }
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 1b652b4f6ca..4d3c13b9c60 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 @@ -1,10 +1,10 @@ import { ChangeDetectionStrategy, Component, + effect, forwardRef, inject, input, - signal, OnInit } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; @@ -12,12 +12,13 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { ButtonModule } from 'primeng/button'; import { DotMessageService } from '@dotcms/data-access'; -import { DotCMSContentTypeField } from '@dotcms/dotcms-models'; +import { DotCMSContentlet, DotCMSContentTypeField } from '@dotcms/dotcms-models'; import { DotDropZoneComponent, DotMessagePipe, DotAIImagePromptComponent, - DotSpinnerModule + DotSpinnerModule, + DropZoneFileEvent } from '@dotcms/ui'; import { DotFileFieldPreviewComponent } from './components/dot-file-field-preview/dot-file-field-preview.component'; @@ -54,11 +55,20 @@ export class DotEditContentFileFieldComponent implements ControlValueAccessor, O readonly #messageService = inject(DotMessageService); $field = input.required({ alias: 'field' }); + $contentlet = input.required({ alias: 'contentlet' }); + $fieldVariable = input.required({ alias: 'fieldVariable' }); private onChange: (value: string) => void; private onTouched: () => void; - $value = signal(''); + constructor() { + effect(() => { + const value = this.store.value(); + console.log('current value', value); + this.onChange(value); + this.onTouched(); + }); + } ngOnInit() { this.store.initLoad({ @@ -72,7 +82,7 @@ export class DotEditContentFileFieldComponent implements ControlValueAccessor, O } writeValue(value: string): void { - this.$value.set(value); + this.store.setValue(value); } registerOnChange(fn: (value: string) => void) { this.onChange = fn; @@ -81,4 +91,28 @@ export class DotEditContentFileFieldComponent implements ControlValueAccessor, O registerOnTouched(fn: () => void) { this.onTouched = fn; } + + /** + * Handle file drop + * + * @param {DropZoneFileEvent} { validity, file } + * @return {*} + * @memberof DotEditContentBinaryFieldComponent + */ + handleFileDrop({ validity, file }: DropZoneFileEvent): void { + if (!file) { + return; + } + + if (!validity.valid) { + //this.handleFileDropError(validity); + + return; + } + + const fileList = new FileList(); + fileList[0] = file; + + this.store.handleUploadFile(fileList); + } } 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 1b223074042..c06de23ab14 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 @@ -5,27 +5,35 @@ type Actions = { allowURLImport: boolean; allowCreateFile: boolean; allowGenerateImg: boolean; + acceptedFiles: string[]; + maxFileSize: number; }; type ConfigActions = Record; -export const INPUT_CONFIG_ACTIONS: ConfigActions = { +export const INPUT_CONFIG: ConfigActions = { File: { allowExistingFile: true, allowURLImport: true, allowCreateFile: true, - allowGenerateImg: false + allowGenerateImg: false, + acceptedFiles: [], + maxFileSize: 0 }, Image: { allowExistingFile: true, allowURLImport: true, allowCreateFile: false, - allowGenerateImg: true + allowGenerateImg: true, + acceptedFiles: ['image/*'], + maxFileSize: 0 }, Binary: { allowExistingFile: false, allowURLImport: true, allowCreateFile: true, - allowGenerateImg: true + allowGenerateImg: true, + acceptedFiles: [], + maxFileSize: 0 } }; 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/fields/dot-edit-content-file-field/models/index.ts index adbe5448d19..56fdc597afe 100644 --- 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/fields/dot-edit-content-file-field/models/index.ts @@ -1,3 +1,5 @@ +import { DotCMSContentlet, DotCMSTempFile } from '@dotcms/dotcms-models'; + export type INPUT_TYPES = 'File' | 'Image' | 'Binary'; export type FILE_STATUS = 'init' | 'uploading' | 'preview'; @@ -7,3 +9,13 @@ export interface UIMessage { severity: 'info' | 'error' | 'warning' | 'success'; icon: string; } + +export type PreviewFile = + | { + source: 'temp'; + file: DotCMSTempFile; + } + | { + source: 'contentlet'; + file: DotCMSContentlet; + }; 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 bc82b878d76..cc9ed834b81 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 @@ -1,11 +1,12 @@ import { patchState, signalStore, withComputed, withMethods, withState } from '@ngrx/signals'; -import { computed } from '@angular/core'; +import { computed, inject } from '@angular/core'; +import { DotUploadService } from '@dotcms/data-access'; import { DotCMSContentlet, DotCMSTempFile } from '@dotcms/dotcms-models'; -import { INPUT_CONFIG_ACTIONS } from '../dot-edit-content-file-field.const'; -import { INPUT_TYPES, FILE_STATUS, UIMessage } from '../models'; +import { INPUT_CONFIG } from '../dot-edit-content-file-field.const'; +import { INPUT_TYPES, FILE_STATUS, UIMessage, PreviewFile } from '../models'; export interface FileFieldState { contentlet: DotCMSContentlet | null; @@ -21,6 +22,10 @@ export interface FileFieldState { allowExistingFile: boolean; allowCreateFile: boolean; uiMessage: UIMessage | null; + acceptedFiles: string[]; + maxFileSize: number; + fieldVariable: string; + previewFile: PreviewFile | null; } const initialState: FileFieldState = { @@ -36,7 +41,11 @@ const initialState: FileFieldState = { allowGenerateImg: false, allowExistingFile: false, allowCreateFile: false, - uiMessage: null + uiMessage: null, + acceptedFiles: [], + maxFileSize: 0, + fieldVariable: '', + previewFile: null }; export const FileFieldStore = signalStore( @@ -53,20 +62,64 @@ export const FileFieldStore = signalStore( return currentStatus === 'uploading'; }) })), - withMethods((store) => ({ + withMethods((store, uploadService = inject(DotUploadService)) => ({ initLoad: (initState: { inputType: FileFieldState['inputType']; uiMessage: FileFieldState['uiMessage']; }) => { const { inputType, uiMessage } = initState; - const actions = INPUT_CONFIG_ACTIONS[inputType] || {}; + const actions = INPUT_CONFIG[inputType] || {}; patchState(store, { inputType, uiMessage, ...actions }); + }, + setValue: (value: string) => { + patchState(store, { value }); + }, + removeFile: () => { + patchState(store, { + contentlet: null, + tempFile: null, + value: '', + fileStatus: 'init' + }); + }, + setDropZoneState: (state: boolean) => { + patchState(store, { + dropZoneActive: state + }); + }, + setUploading: () => { + patchState(store, { + dropZoneActive: false, + fileStatus: 'uploading' + }); + }, + handleUploadFile: async (files: FileList) => { + const file = files[0]; + + if (file) { + patchState(store, { + dropZoneActive: false, + fileStatus: 'uploading' + }); + + const tempFile = await uploadService.uploadFile({ + file, + maxSize: `${store.maxFileSize()}` + }); + patchState(store, { + tempFile, + contentlet: null, + fileStatus: 'preview', + value: tempFile?.id, + previewFile: { source: 'temp', file: tempFile } + }); + } } })) ); diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/utils/index.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/utils/index.ts new file mode 100644 index 00000000000..49a68220754 --- /dev/null +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/utils/index.ts @@ -0,0 +1,12 @@ +import { DotCMSContentlet, DotFileMetadata } from '@dotcms/dotcms-models'; + +export const getFileMetadata = ( + contentlet: DotCMSContentlet, + fieldVariable: string +): DotFileMetadata => { + const { metaData } = contentlet; + + const metadata = metaData || contentlet[`${fieldVariable}MetaData`]; + + return metadata || {}; +};