Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Edit Contentlet: Allow user to upload files from external sources #26313

Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
abe7ab8
dev: add url mode
rjvelazco Sep 27, 2023
a8e1ae4
dev: create DotBinaryFieldUrlMode component
rjvelazco Sep 27, 2023
12faf12
test: cover dotBinaryFieldUrlMode Component tests
rjvelazco Sep 28, 2023
07ebeba
dev: add i18n - internalization
rjvelazco Sep 28, 2023
effa9e0
Merge branch 'master' into issue-26047-edit-contentlet-allow-user-to-…
rjvelazco Sep 28, 2023
ce802ab
dev: improve style & build binary field
rjvelazco Sep 28, 2023
34b6fc2
dev: avoid closing the dialog while uploading
rjvelazco Sep 29, 2023
df24057
dev: add store for URL Mode
rjvelazco Oct 2, 2023
341d1d9
test: DotBinaryFieldUrlMode Component
rjvelazco Oct 2, 2023
d4fadb1
Merge branch 'master' into issue-26047-edit-contentlet-allow-user-to-…
rjvelazco Oct 2, 2023
4720997
dev: generate contenttype-fields-styles.css file
rjvelazco Oct 3, 2023
3d2aaf4
clean up
rjvelazco Oct 3, 2023
0508569
Merge branch 'master' into issue-26047-edit-contentlet-allow-user-to-…
rjvelazco Oct 3, 2023
a95011f
Merge branch 'master' into issue-26047-edit-contentlet-allow-user-to-…
rjvelazco Oct 3, 2023
7b2fcd5
clean up v2
rjvelazco Oct 3, 2023
f6cac1a
Merge branch 'master' into issue-26047-edit-contentlet-allow-user-to-…
zJaaal Oct 3, 2023
66a5986
feedback
rjvelazco Oct 3, 2023
f0af00f
Merge branch 'issue-26047-edit-contentlet-allow-user-to-upload-files-…
rjvelazco Oct 3, 2023
c9b81ff
clean up
rjvelazco Oct 3, 2023
0d89925
feedback v1
rjvelazco Oct 4, 2023
2f50886
move fonts.scss to dotcms-scss/angular
rjvelazco Oct 4, 2023
cfb5e3b
feedback v3
rjvelazco Oct 4, 2023
7aa6731
clean up
rjvelazco Oct 4, 2023
4f22c6d
feeback: input & button styles
rjvelazco Oct 5, 2023
d9062ee
feeback: binart fiel css file
rjvelazco Oct 5, 2023
3cdd3ce
styles: fix error message height
rjvelazco Oct 5, 2023
e5b396f
Merge branch 'master' into issue-26047-edit-contentlet-allow-user-to-…
rjvelazco Oct 6, 2023
76890a5
clean up
rjvelazco Oct 6, 2023
ff72d17
feedback: button styles
rjvelazco Oct 6, 2023
332852f
dev: build binary field web-component
rjvelazco Oct 6, 2023
b3cb1c7
clean up
rjvelazco Oct 6, 2023
5585633
Merge branch 'master' into issue-26047-edit-contentlet-allow-user-to-…
rjvelazco Oct 6, 2023
757a308
fix: frontend tests
rjvelazco Oct 6, 2023
2353aa3
clean up v2
rjvelazco Oct 6, 2023
19500d3
build web component
rjvelazco Oct 6, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 11 additions & 1 deletion core-web/apps/contenttype-fields-builder/project.json
Expand Up @@ -19,7 +19,17 @@
"apps/contenttype-fields-builder/src/favicon.ico",
"apps/contenttype-fields-builder/src/assets"
],
"styles": ["apps/contenttype-fields-builder/src/styles.scss"],
"styles": [
"node_modules/primeicons/primeicons.css",
"node_modules/primeng/resources/primeng.min.css",
"libs/dotcms-scss/angular/dotcms-theme/_misc.scss",
"libs/dotcms-scss/angular/dotcms-theme/components/buttons/common.scss",
"libs/dotcms-scss/angular/dotcms-theme/components/buttons/_button.scss",
"libs/dotcms-scss/angular/dotcms-theme/components/_dialog.scss",
"libs/dotcms-scss/angular/dotcms-theme/components/form/_inputtext.scss",
"libs/dotcms-scss/angular/dotcms-theme/utils/_validation.scss",
"libs/dotcms-scss/angular/_prime-icons.scss"
],
"stylePreprocessorOptions": {
"includePaths": ["libs/dotcms-scss/angular"]
},
Expand Down
@@ -1,6 +1,6 @@
@use "variables" as *;

@import "libs/block-editor/src/fonts";
@import "libs/dotcms-scss/angular/_prime-icons.scss";

:host ::ng-deep {
@import "libs/dotcms-scss/angular/styles";
Expand Down
Expand Up @@ -12,7 +12,7 @@
class="binary-field__drop-zone"
[ngClass]="{ 'binary-field__drop-zone--active': vm.dropZoneActive }">
<dot-drop-zone
[accept]="acceptedTypes"
[accept]="accept"
[maxFileSize]="maxFileSize"
(fileDragOver)="setDropZoneActiveState(true)"
(fileDragLeave)="setDropZoneActiveState(false)"
Expand All @@ -33,10 +33,10 @@
<input
class="binary-field__input"
#inputFile
[accept]="acceptedTypes.join(',')"
[accept]="accept.join(',')"
(change)="handleFileSelection($event)"
data-testId="binary-field__file-input"
type="file" />
type="file"
data-testId="binary-field__file-input" />
</div>

<div class="binary-field__actions">
Expand Down Expand Up @@ -73,17 +73,20 @@
data-testId="action-remove-btn"
icon="pi pi-trash"></p-button>
</div>

<p-dialog
[visible]="vm.dialogOpen"
[(visible)]="dialogOpen"
[modal]="true"
[header]="dialogHeaderMap[vm.mode] | dm"
[draggable]="false"
[resizable]="false"
(visibleChange)="visibleChange($event)">
(onHide)="afterDialogClose()">
<div [ngSwitch]="vm.mode">
<div *ngSwitchCase="BINARY_FIELD_MODE.URL" data-testId="url">
TODO: Implement Import from URL
<dot-dot-binary-field-url-mode
[accept]="accept"
[maxFileSize]="maxFileSize"
(tempFileUploaded)="setTempFile($event)"
(cancel)="closeDialog()"></dot-dot-binary-field-url-mode>
</div>
<div *ngSwitchCase="BINARY_FIELD_MODE.EDITOR" data-testId="editor">
TODO: Implement Write Code
Expand Down
@@ -1,14 +1,5 @@
@use "variables" as *;
rjvelazco marked this conversation as resolved.
Show resolved Hide resolved

@import "libs/block-editor/src/fonts"; // Import PrimeNG Icon

:host ::ng-deep {
@import "libs/dotcms-scss/angular/dotcms-theme/components/buttons/button";
@import "libs/dotcms-scss/angular/dotcms-theme/components/dialog";
@import "node_modules/primeicons/primeicons";
@import "libs/dotcms-scss/angular/dotcms-theme/_misc.scss";
}

.binary-field__container {
display: flex;
justify-content: center;
Expand Down Expand Up @@ -119,3 +110,11 @@
display: block;
height: 25rem;
}

p-dialog ::ng-deep {
.p-dialog-mask.p-component-overlay {
background-color: transparent;
-webkit-backdrop-filter: blur($blur-md);
backdrop-filter: blur($blur-md);
}
}
Expand Up @@ -87,7 +87,7 @@ describe('DotBinaryFieldComponent', () => {
spectator = createComponent({
detectChanges: false,
props: {
accept: 'image/*',
accept: ['image/*'],
maxFileSize: 1000,
helperText: 'helper text'
}
Expand Down Expand Up @@ -255,31 +255,35 @@ describe('DotBinaryFieldComponent', () => {
});

it('should open dialog with code component when click on edit button', async () => {
const spyOpenDialog = jest.spyOn(store, 'openDialog');
const spySetMode = jest.spyOn(store, 'setMode');
const editorBtn = spectator.query(byTestId('action-editor-btn')) as HTMLButtonElement;
editorBtn.click();

spectator.detectChanges();
await spectator.fixture.whenStable();

const editorElement = spectator.query(byTestId('editor'));
const isDialogOpen = spectator.fixture.componentInstance.openDialog;

expect(editorElement).toBeTruthy();
expect(spyOpenDialog).toHaveBeenCalledWith(BINARY_FIELD_MODE.EDITOR);
expect(isDialogOpen).toBeTruthy();
expect(spySetMode).toHaveBeenCalledWith(BINARY_FIELD_MODE.EDITOR);
});

it('should open dialog with url componet component when click on url button', async () => {
const spyOpenDialog = jest.spyOn(store, 'openDialog');
const spySetMode = jest.spyOn(store, 'setMode');
const urlBtn = spectator.query(byTestId('action-url-btn')) as HTMLButtonElement;
urlBtn.click();

spectator.detectChanges();
await spectator.fixture.whenStable();

const urlElement = spectator.query(byTestId('url'));
const isDialogOpen = spectator.fixture.componentInstance.openDialog;

expect(urlElement).toBeTruthy();
expect(spyOpenDialog).toHaveBeenCalledWith(BINARY_FIELD_MODE.URL);
expect(isDialogOpen).toBeTruthy();
expect(spySetMode).toHaveBeenCalledWith(BINARY_FIELD_MODE.URL);
});
});

Expand Down
Expand Up @@ -7,12 +7,19 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

import { ButtonModule } from 'primeng/button';
import { DialogModule } from 'primeng/dialog';
import { InputTextModule } from 'primeng/inputtext';

import { DotMessageService, DotUploadService } from '@dotcms/data-access';
import { DotDropZoneComponent, DotMessagePipe, DotSpinnerModule } from '@dotcms/ui';
import {
DotDropZoneComponent,
DotFieldValidationMessageComponent,
DotMessagePipe,
DotSpinnerModule
} from '@dotcms/ui';

import { DotBinaryFieldComponent } from './binary-field.component';
import { DotBinaryFieldUiMessageComponent } from './components/dot-binary-field-ui-message/dot-binary-field-ui-message.component';
import { DotBinaryFieldUrlModeComponent } from './components/dot-binary-field-url-mode/dot-binary-field-url-mode.component';
import { DotBinaryFieldStore } from './store/binary-field.store';

import { CONTENTTYPE_FIELDS_MESSAGE_MOCK } from '../../utils/mock';
Expand All @@ -32,7 +39,10 @@ export default {
DotDropZoneComponent,
DotBinaryFieldUiMessageComponent,
DotMessagePipe,
DotSpinnerModule
DotSpinnerModule,
InputTextModule,
DotBinaryFieldUrlModeComponent,
DotFieldValidationMessageComponent
],
providers: [
DotBinaryFieldStore,
Expand Down Expand Up @@ -65,14 +75,14 @@ export default {
})
],
args: {
accept: 'image/*',
accept: ['image/*'],
maxFileSize: 1000000,
helperText: 'This field accepts only images with a maximum size of 1MB.'
},
argTypes: {
accept: {
defaultValue: 'image/*',
control: 'string',
defaultValue: ['image/*'],
control: 'object',
description: 'Accepted file types'
},
maxFileSize: {
Expand Down
Expand Up @@ -15,6 +15,7 @@ import {

import { ButtonModule } from 'primeng/button';
import { DialogModule } from 'primeng/dialog';
import { InputTextModule } from 'primeng/inputtext';

import { skip } from 'rxjs/operators';

Expand All @@ -29,6 +30,7 @@ import {
} from '@dotcms/ui';

import { DotBinaryFieldUiMessageComponent } from './components/dot-binary-field-ui-message/dot-binary-field-ui-message.component';
import { DotBinaryFieldUrlModeComponent } from './components/dot-binary-field-url-mode/dot-binary-field-url-mode.component';
import {
BINARY_FIELD_MODE,
BINARY_FIELD_STATUS,
Expand All @@ -43,7 +45,6 @@ const initialState: BinaryFieldState = {
tempFile: null,
mode: BINARY_FIELD_MODE.DROPZONE,
status: BINARY_FIELD_STATUS.INIT,
dialogOpen: false,
dropZoneActive: false,
UiMessage: getUiMessage(UI_MESSAGE_KEYS.DEFAULT)
};
Expand All @@ -60,7 +61,9 @@ const initialState: BinaryFieldState = {
DotMessagePipe,
DotBinaryFieldUiMessageComponent,
DotSpinnerModule,
HttpClientModule
HttpClientModule,
InputTextModule,
DotBinaryFieldUrlModeComponent
],
providers: [DotBinaryFieldStore],
templateUrl: './binary-field.component.html',
Expand All @@ -69,11 +72,7 @@ const initialState: BinaryFieldState = {
})
export class DotBinaryFieldComponent implements OnInit {
//Inputs
acceptedTypes: string[] = [];
@Input() set accept(accept: string) {
this.acceptedTypes = accept.split(',').map((type) => type.trim());
}

@Input() accept: string[] = [];
@Input() maxFileSize: number;
@Input() helperText: string;

Expand All @@ -91,6 +90,8 @@ export class DotBinaryFieldComponent implements OnInit {
readonly BINARY_FIELD_MODE = BINARY_FIELD_MODE;
readonly vm$ = this.dotBinaryFieldStore.vm$;

dialogOpen = false;

constructor(
private readonly dotBinaryFieldStore: DotBinaryFieldStore,
private readonly dotMessageService: DotMessageService
Expand Down Expand Up @@ -145,20 +146,26 @@ export class DotBinaryFieldComponent implements OnInit {
* @memberof DotBinaryFieldComponent
*/
openDialog(mode: BINARY_FIELD_MODE) {
this.dotBinaryFieldStore.openDialog(mode);
this.dialogOpen = true;
this.dotBinaryFieldStore.setMode(mode);
}

/**
* Listen to dialog visibility change
* and set mode to dropzone when dialog is closed
* Close Dialog
*
* @param {boolean} visibily
* @memberof DotBinaryFieldComponent
*/
visibleChange(visibily: boolean) {
if (!visibily) {
this.dotBinaryFieldStore.closeDialog();
}
closeDialog() {
this.dialogOpen = false;
}

/**
* Listen to dialog close event
*
* @memberof DotBinaryFieldComponent
*/
afterDialogClose() {
this.dotBinaryFieldStore.setMode(null);
}

/**
Expand Down Expand Up @@ -195,8 +202,8 @@ export class DotBinaryFieldComponent implements OnInit {
// TODO: Implement - Write Code
}

handleExternalSourceFile(_event) {
// TODO: Implement - FROM URL
setTempFile(tempFile: DotCMSTempFile) {
this.dotBinaryFieldStore.setTempFile(tempFile);
}

/**
Expand All @@ -210,7 +217,7 @@ export class DotBinaryFieldComponent implements OnInit {
fileTypeMismatch,
maxFileSizeExceeded
}: DropZoneFileValidity): UiMessageI {
const acceptedTypes = this.acceptedTypes.join(', ');
const acceptedTypes = this.accept.join(', ');
const maxSize = `${this.maxFileSize} bytes`;
let uiMessage: UiMessageI;

Expand Down
@@ -0,0 +1,49 @@
<form
class="url-mode__form"
*ngIf="vm$ | async as vm"
[formGroup]="form"
(ngSubmit)="onSubmit()"
data-testId="form">
<div class="url-mode__input-container">
rjvelazco marked this conversation as resolved.
Show resolved Hide resolved
<label for="url-input">{{ 'dot.binary.field.action.import.from.url' | dm }}</label>
<input
id="url-input"
type="text"
formControlName="url"
pInputText
placeholder="https://www.dotcms.com/image.png"
aria-label="URL input field"
data-testId="url-input" />
<dot-field-validation-message
rjvelazco marked this conversation as resolved.
Show resolved Hide resolved
[message]="vm.error || defaultError | dm"
[field]="form.get('url')"
data-testId="error-message"></dot-field-validation-message>
</div>
<div class="url-mode__actions">
<p-button
class="p-button-outlined"
[label]="'dot.common.cancel' | dm"
(click)="cancelUpload()"
type="button"
aria-label="Cancel button"
data-testId="cancel-button"></p-button>
rjvelazco marked this conversation as resolved.
Show resolved Hide resolved

<div class="url-mode__actions--main">
<p-button
rjvelazco marked this conversation as resolved.
Show resolved Hide resolved
*ngIf="!vm.isLoading; else loadingButton"
[label]="'dot.common.import' | dm"
[icon]="'pi pi-download'"
type="submit"
aria-label="Import button"
data-testId="import-button"></p-button>

<ng-template #loadingButton>
<p-button
[icon]="'pi pi-spin pi-spinner'"
type="button"
aria-label="Loading button"
data-testId="loading-button"></p-button>
</ng-template>
</div>
</div>
</form>