Skip to content

Commit

Permalink
feat(block-editor): add import from url feature #29874
Browse files Browse the repository at this point in the history
  • Loading branch information
nicobytes committed Oct 3, 2024
1 parent 9252e3e commit da35bc1
Show file tree
Hide file tree
Showing 11 changed files with 335 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -87,18 +87,30 @@ export class DotUploadFileService {
}

/**
* Uploads a file to dotCMS and creates a new dotAsset contentlet
* @param file the file to be uploaded
* @returns an Observable that emits the created contentlet
* Uploads a file or a string as a dotAsset contentlet.
*
* If a File is passed, it will be uploaded and the asset will be created
* with the file name as the contentlet name.
*
* If a string is passed, it will be used as the asset id.
*
* @param file The file to be uploaded or the asset id.
* @returns An observable that resolves to the created contentlet.
*/
uploadDotAsset(file: File) {
const formData = new FormData();
formData.append('file', file);
uploadDotAsset(file: File | string): Observable<DotCMSContentlet> {
if (file instanceof File) {
const formData = new FormData();
formData.append('file', file);

return this.#workflowActionsFireService.newContentlet<DotCMSContentlet>(
'dotAsset',
{ file: file.name },
formData
);
return this.#workflowActionsFireService.newContentlet<DotCMSContentlet>(
'dotAsset',
{ file: file.name },
formData
);
}

return this.#workflowActionsFireService.newContentlet<DotCMSContentlet>('dotAsset', {
asset: file
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import {
DotCopyButtonComponent
} from '@dotcms/ui';

import { DotPreviewResourceLink, PreviewFile } from '../../models';
import { DotPreviewResourceLink, UploadedFile } from '../../models';
import { getFileMetadata } from '../../utils';

@Component({
Expand All @@ -53,7 +53,7 @@ export class DotFileFieldPreviewComponent implements OnInit {
*
* @memberof DotFileFieldPreviewComponent
*/
$previewFile = input.required<PreviewFile>({ alias: 'previewFile' });
$previewFile = input.required<UploadedFile>({ alias: 'previewFile' });
/**
* Remove file
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<form (ngSubmit)="onSubmit()" [formGroup]="form" class="url-mode__form" data-testId="form">
<div class="url-mode__input-container">
<label for="url-input">{{ 'dot.file.field.action.import.from.url' | dm }}</label>
<input
id="url-input"
type="text"
autocomplete="off"
formControlName="url"
pInputText
placeholder="https://www.dotcms.com/image.png"
aria-label="URL input field"
data-testId="url-input" />
<div class="error-messsage__container">
@let error = store.error();
@if (error) {
<dot-field-validation-message
[message]="'dot.file.field.action.import.from.url.error.message' | dm"
[field]="form.get('url')"
data-testId="error-message" />
} @else {
<small class="p-invalid" data-testId="error-msg">
{{ error | dm }}
</small>
}
</div>
</div>
<div class="url-mode__actions">
<p-button
(click)="cancelUpload()"
[label]="'dot.common.cancel' | dm"
styleClass="p-button-outlined"
type="button"
aria-label="Cancel button"
data-testId="cancel-button" />
<div [style]="{ width: '6.85rem' }">
@if (store.isLoading()) {
<p-button
[icon]="'pi pi-spin pi-spinner'"
type="button"
aria-label="Loading button"
data-testId="loading-button" />
} @else {
<p-button
[label]="'dot.common.import' | dm"
[icon]="'pi pi-download'"
type="submit"
aria-label="Import button"
data-testId="import-button" />
}
</div>
</div>
</form>
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
@use "variables" as *;

:host ::ng-deep {
display: block;
width: 32rem;

.p-button {
width: 100%;
}

.error-messsage__container {
min-height: $spacing-4;
}
}

.url-mode__form {
display: flex;
flex-direction: column;
gap: $spacing-3;
justify-content: center;
align-items: flex-start;
}

.url-mode__input-container {
width: 100%;
display: flex;
gap: $spacing-1;
flex-direction: column;
}

.url-mode__actions {
width: 100%;
display: flex;
gap: $spacing-1;
align-items: center;
justify-content: flex-end;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { ChangeDetectionStrategy, Component, effect, inject, OnInit } from '@angular/core';
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';

import { ButtonModule } from 'primeng/button';
import { DynamicDialogRef, DialogService } from 'primeng/dynamicdialog';
import { InputTextModule } from 'primeng/inputtext';

import { DotMessagePipe, DotFieldValidationMessageComponent } from '@dotcms/ui';

import { FormImportUrlStore } from './store/form-import-url.store';

@Component({
selector: 'dot-form-import-url',
standalone: true,
imports: [
DotMessagePipe,
ReactiveFormsModule,
DotFieldValidationMessageComponent,
ButtonModule,
InputTextModule
],
templateUrl: './dot-form-import-url.component.html',
styleUrls: ['./dot-form-import-url.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [FormImportUrlStore]
})
export class DotFormImportUrlComponent implements OnInit {
readonly #formBuilder = inject(FormBuilder);
readonly store = inject(FormImportUrlStore);
readonly #dialogRef = inject(DynamicDialogRef);
readonly #dialogService = inject(DialogService);
readonly #instance = this.#dialogService.getInstance(this.#dialogRef);

readonly form = this.#formBuilder.group({
url: ['', [Validators.required, Validators.pattern(/^(ftp|http|https):\/\/[^ "]+$/)]]
});

constructor() {
effect(
() => {
const file = this.store.file();
const isDone = this.store.isDone();

if (file && isDone) {
this.#dialogRef.close(file);
}
},
{
allowSignalWrites: true
}
);
}

ngOnInit(): void {
const uploadType = this.#instance?.data?.inputType === 'Binary' ? 'temp' : 'dotasset';
this.store.setUploadType(uploadType);
}

onSubmit(): void {
if (this.form.invalid) {
return;
}

const { url } = this.form.getRawValue();
this.store.uploadFileByUrl(url);
}

cancelUpload(): void {
this.#dialogRef.close();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { tapResponse } from '@ngrx/operators';
import { patchState, signalStore, withComputed, withMethods, withState } from '@ngrx/signals';
import { rxMethod } from '@ngrx/signals/rxjs-interop';
import { pipe } from 'rxjs';

import { computed, inject } from '@angular/core';

import { switchMap, tap } from 'rxjs/operators';

import { UploadedFile, UPLOAD_TYPE } from '../../../models';
import { DotFileFieldUploadService } from '../../../services/upload-file/upload-file.service';

export interface FormImportUrlState {
file: UploadedFile | null;
status: 'init' | 'uploading' | 'done' | 'error';
error: string | null;
uploadType: UPLOAD_TYPE;
}

const initialState: FormImportUrlState = {
file: null,
status: 'init',
error: null,
uploadType: 'temp'
};

export const FormImportUrlStore = signalStore(
withState(initialState),
withComputed((state) => ({
isLoading: computed(() => state.status() === 'uploading'),
isDone: computed(() => state.status() === 'done')
})),
withMethods((store, uploadService = inject(DotFileFieldUploadService)) => ({
uploadFileByUrl: rxMethod<string>(
pipe(
tap(() => patchState(store, { status: 'uploading' })),
switchMap((fileUrl) => {
return uploadService
.uploadFile({
file: fileUrl,
uploadType: store.uploadType()
})
.pipe(
tapResponse({
next: (file) => patchState(store, { file, status: 'done' }),
error: console.error
})
);
})
)
),
setUploadType: (uploadType: FormImportUrlState['uploadType']) => {
patchState(store, { uploadType });
}
}))
);
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
<div class="file-field__actions">
@if (store.allowURLImport()) {
<p-button
(click)="showImportUrlDialog()"
[label]="'dot.file.field.action.import.from.url' | dm"
data-testId="action-import-from-url"
icon="pi pi-link"
Expand Down Expand Up @@ -87,10 +88,10 @@
<dot-spinner data-testId="loading" />
}
@case ('preview') {
@if (store.previewFile()) {
@if (store.uploadedFile()) {
<dot-file-field-preview
(removeFile)="store.removeFile()"
[previewFile]="store.previewFile()" />
[previewFile]="store.uploadedFile()" />
}
}
}
Expand Down
Loading

0 comments on commit da35bc1

Please sign in to comment.