diff --git a/projects/solidify-frontend/src/lib/core/directives/data-test/data-test.directive.ts b/projects/solidify-frontend/src/lib/core/directives/data-test/data-test.directive.ts index d7e91a471c9244bf412947f10fc44ce9c01430d4..6e96e99ae00fb09af72ebb47cbf023be8b95ca9b 100644 --- a/projects/solidify-frontend/src/lib/core/directives/data-test/data-test.directive.ts +++ b/projects/solidify-frontend/src/lib/core/directives/data-test/data-test.directive.ts @@ -21,7 +21,6 @@ * ----------------------------------------------------------------------------------------------%% */ - import { Directive, ElementRef, diff --git a/projects/solidify-frontend/src/lib/core/directives/file-drop-zone/file-drop-zone.directive.ts b/projects/solidify-frontend/src/lib/core/directives/file-drop-zone/file-drop-zone.directive.ts index 219f3ae63913e10b3b720d19097e2567a79493f1..f5bce8e8ffeed2cd8ceeb1285248fc0d2886162b 100644 --- a/projects/solidify-frontend/src/lib/core/directives/file-drop-zone/file-drop-zone.directive.ts +++ b/projects/solidify-frontend/src/lib/core/directives/file-drop-zone/file-drop-zone.directive.ts @@ -21,12 +21,12 @@ * ----------------------------------------------------------------------------------------------%% */ - import { Directive, ElementRef, HostBinding, HostListener, + Input, OnInit, Output, Renderer2, @@ -35,26 +35,23 @@ import { BehaviorSubject, Observable, } from "rxjs"; -import { - debounceTime, - distinctUntilChanged, - filter, - tap, -} from "rxjs/operators"; +import {SOLIDIFY_CONSTANTS} from "../../constants"; import {ObservableUtil} from "../../utils/observable.util"; +import {SsrUtil} from "../../utils/ssr.util"; import {CoreAbstractDirective} from "../core-abstract/core-abstract.directive"; @Directive({ selector: "[solidifyFileDropZone]", }) export class FileDropZoneDirective extends CoreAbstractDirective implements OnInit { - @HostBinding("class.file-over") - get isFileOver(): boolean { - return this._fileOverBS.value; - } + private readonly _DIRECTIVE_CLASS: string = "file-drop-zone"; + private readonly _ATTRIBUTE_IS_FILE_DRAG: string = "is-file-drag"; + + private _windowDragFileCounter: number = 0; + private _elementDragFileCounter: number = 0; - @HostBinding("class.pointer-events-all-child") - preventEventsAllChild: boolean; + @Input("solidifyFileDropZoneDisabled") + dragDisabled: boolean = false; private readonly _fileDroppedBS: BehaviorSubject<FileList | undefined> = new BehaviorSubject<FileList | undefined>(undefined); @Output("fileDropped") @@ -64,48 +61,106 @@ export class FileDropZoneDirective extends CoreAbstractDirective implements OnIn @Output("fileOver") readonly fileOverObs: Observable<boolean | undefined> = ObservableUtil.asObservable(this._fileOverBS); - constructor(private readonly _elementRef: ElementRef, - private readonly _renderer: Renderer2) { + constructor(private readonly _renderer: Renderer2, + private readonly _elementRef: ElementRef) { super(); + _renderer.addClass(_elementRef.nativeElement, this._DIRECTIVE_CLASS); } - ngOnInit(): void { - super.ngOnInit(); - - this.subscribe(this.fileOverObs.pipe( - distinctUntilChanged(), - filter(isOver => !isOver), - debounceTime(250), - filter(isOver => !this._fileOverBS.value), - tap(isOver => { - this._renderer.removeClass(this._elementRef.nativeElement, "pointer-events-all-child"); - }), - )); + @HostBinding("class.file-over") + get isFileOver(): boolean { + return this._fileOverBS.value; } - @HostListener("dragover", ["$event"]) - onDragOver($event: Event): void { + @HostListener("window:dragenter") + onDragEnterWindow(): void { + if (this.dragDisabled === true) { + return; + } + this._windowDragFileCounter++; + this._computeWindowDragFileClass(); + } + + @HostListener("window:dragover", ["$event"]) + onDragOverWindow($event: Event): void { + if (this.dragDisabled === true) { + return; + } $event.preventDefault(); - $event.stopPropagation(); - this._fileOverBS.next(true); - this._renderer.addClass(this._elementRef.nativeElement, "pointer-events-all-child"); } - @HostListener("dragleave", ["$event"]) - public onDragLeave($event: Event): void { + @HostListener("window:dragleave") + onDragLeaveWindow(): void { + if (this.dragDisabled === true) { + return; + } + this._windowDragFileCounter--; + this._computeWindowDragFileClass(); + } + + @HostListener("window:drop", ["$event"]) + onDropWindow($event: DragEvent): void { + if (this.dragDisabled === true) { + return; + } + $event.preventDefault(); // avoid to open file dragged in elemet in new tab + this._windowDragFileCounter = 0; + this._computeWindowDragFileClass(); + } + + @HostListener("dragenter") + onDragEnterElement(): void { + if (this.dragDisabled === true) { + return; + } + this._elementDragFileCounter++; + this._computeElementDragFile(); + } + + @HostListener("dragover", ["$event"]) + onDragOverElement($event: Event): void { + if (this.dragDisabled === true) { + return; + } $event.preventDefault(); - $event.stopPropagation(); - this._fileOverBS.next(false); + } + + @HostListener("dragleave") + onDragLeaveElement(): void { + if (this.dragDisabled === true) { + return; + } + this._elementDragFileCounter--; + this._computeElementDragFile(); } @HostListener("drop", ["$event"]) - public ondrop($event: DragEvent): void { - $event.preventDefault(); - $event.stopPropagation(); - this._fileOverBS.next(false); + onDropElement($event: DragEvent): void { + if (this.dragDisabled === true) { + return; + } + $event.preventDefault(); // avoid to open file dragged in elemet in new tab + this._elementDragFileCounter = 0; + this._computeElementDragFile(); const files = $event.dataTransfer.files; if (files.length > 0) { this._fileDroppedBS.next(files); } } + + private _computeWindowDragFileClass(): void { + if (this._windowDragFileCounter > 0) { + this._renderer.setAttribute(SsrUtil.document.body, this._ATTRIBUTE_IS_FILE_DRAG, SOLIDIFY_CONSTANTS.STRING_TRUE); + } else { + this._renderer.removeAttribute(SsrUtil.document.body, this._ATTRIBUTE_IS_FILE_DRAG); + } + } + + private _computeElementDragFile(): void { + if (this._elementDragFileCounter > 0 && this.isFileOver !== true) { + this._fileOverBS.next(true); + } else if (this._elementDragFileCounter === 0 && this.isFileOver === true) { + this._fileOverBS.next(false); + } + } } diff --git a/projects/solidify-frontend/src/lib/core/scss/abstracts/_mixins.scss b/projects/solidify-frontend/src/lib/core/scss/abstracts/_mixins.scss index a6aa005520af4416a19a019dc21b157dccfb9585..622e2fa0aee06cfa20891778861f3b2ee01b272f 100644 --- a/projects/solidify-frontend/src/lib/core/scss/abstracts/_mixins.scss +++ b/projects/solidify-frontend/src/lib/core/scss/abstracts/_mixins.scss @@ -202,3 +202,14 @@ $breakpoints-media-interval: ( } } +@mixin isFileDrag($inGlobalScss: false) { + @if $inGlobalScss { + body[is-file-drag='true'] { + @content; + } + } @else { + :host-context(body[is-file-drag='true']) { + @content; + } + } +} diff --git a/projects/solidify-frontend/src/lib/core/scss/override/default-browser-override.scss b/projects/solidify-frontend/src/lib/core/scss/override/default-browser-override.scss index d273444f00e837563d7768bd8709fb1a678f450e..afac4e2c4171e1f108b525bab4c2efea95bcbb4a 100644 --- a/projects/solidify-frontend/src/lib/core/scss/override/default-browser-override.scss +++ b/projects/solidify-frontend/src/lib/core/scss/override/default-browser-override.scss @@ -134,13 +134,19 @@ dl { margin: 0; } -.pointer-events-all-child { - * { - pointer-events: none; +@include isFileDrag(true) { + .file-drop-zone { + outline: 4px dashed $intermediate-grey; + outline-offset: 5px; + + &.file-over { + outline-offset: 8px; + transition: all 0.1s ease-in-out 0s; + outline-color: $primary-color; + } } } - .solidify-alternative-button { background-color: transparent; border-radius: 50px; diff --git a/projects/solidify-frontend/src/lib/image/components/containers/abstract-image-upload-wrapper/abstract-image-upload-wrapper.container.ts b/projects/solidify-frontend/src/lib/image/components/containers/abstract-image-upload-wrapper/abstract-image-upload-wrapper.container.ts index 83391067943d9f4ec990f98b0f200f36538e5dcb..6c3e1f98273d4cf1c45607f4e391198d374c6179 100644 --- a/projects/solidify-frontend/src/lib/image/components/containers/abstract-image-upload-wrapper/abstract-image-upload-wrapper.container.ts +++ b/projects/solidify-frontend/src/lib/image/components/containers/abstract-image-upload-wrapper/abstract-image-upload-wrapper.container.ts @@ -124,7 +124,7 @@ export abstract class AbstractImageUploadWrapperContainer extends AbstractIntern return this._imagePendingUploadUrl; } - @ViewChild("fileInput", {static: false}) + @ViewChild("fileInput") fileInput: ElementRef; protected readonly _logoChangeBS: BehaviorSubject<Blob | undefined | null> = new BehaviorSubject<Blob | undefined | null>(undefined); @@ -190,28 +190,32 @@ export abstract class AbstractImageUploadWrapperContainer extends AbstractIntern } } - fileChangeEvent(event: FileEvent): void { + fileChangeEventByInput(event: FileEvent): void { const target = event.target; if (isNullOrUndefined(target.value) || isEmptyString(target.value)) { return; } - const file = target.files[0]; + this.fileChangeEvent(target.files); + this.fileInput.nativeElement.value = null; + } + + fileChangeEvent(files: FileList): void { + const file = files[0]; if (this._LIST_FORMAT_BYPASS_CROP.includes(file.type)) { this._uploadImage(file); } else { - this._cropImage(event); + this._cropImage(file); } } - private _cropImage(event: Event): void { - this._sharedUploadImageDialogData.imageChangedEvent = event; + private _cropImage(file: File): void { + this._sharedUploadImageDialogData.file = file; this.subscribe(DialogUtil.open(this._dialog, UploadImageDialog, this._sharedUploadImageDialogData, { width: "90%", }, blob => { this._uploadImage(blob); this._cd.detectChanges(); }, () => { - this.fileInput.nativeElement.value = null; })); } @@ -230,6 +234,5 @@ export abstract class AbstractImageUploadWrapperContainer extends AbstractIntern this.imagePendingUpload = blob; this._logoChangeBS.next(blob); } - this.fileInput.nativeElement.value = null; } } diff --git a/projects/solidify-frontend/src/lib/image/components/containers/avatar-upload-wrapper/avatar-upload-wrapper.container.html b/projects/solidify-frontend/src/lib/image/components/containers/avatar-upload-wrapper/avatar-upload-wrapper.container.html index 0734ce14da38430cab25ed280145b02ace0af68b..d34641cbe671caefc120ac7aefa2600102e4357c 100644 --- a/projects/solidify-frontend/src/lib/image/components/containers/avatar-upload-wrapper/avatar-upload-wrapper.container.html +++ b/projects/solidify-frontend/src/lib/image/components/containers/avatar-upload-wrapper/avatar-upload-wrapper.container.html @@ -2,6 +2,10 @@ [class.is-readonly]="!isEditable" [solidifySpinner]="isLoadingObs | async" class="wrapper-avatar" + solidifyFileDropZone + (fileDropped)="fileChangeEvent($event)" + [solidifyFileDropZoneDisabled]="!isEditable" + > <div *ngIf="imagePendingUploadUrl; else noPhoto" class="photo-wrapper" @@ -33,7 +37,7 @@ </div> <input #fileInput - (change)="fileChangeEvent($any($event))" + (change)="fileChangeEventByInput($any($event))" [accept]="filesAccepted" class="hide" type="file" diff --git a/projects/solidify-frontend/src/lib/image/components/containers/avatar-upload-wrapper/avatar-upload-wrapper.container.ts b/projects/solidify-frontend/src/lib/image/components/containers/avatar-upload-wrapper/avatar-upload-wrapper.container.ts index abb4fc9409ad1bdb418bc9fbd5c542a76a01c317..45b19fdda528c19e49d6e56baccb198b6357526e 100644 --- a/projects/solidify-frontend/src/lib/image/components/containers/avatar-upload-wrapper/avatar-upload-wrapper.container.ts +++ b/projects/solidify-frontend/src/lib/image/components/containers/avatar-upload-wrapper/avatar-upload-wrapper.container.ts @@ -39,6 +39,7 @@ import { Actions, Store, } from "@ngxs/store"; +import {by} from "ng-packagr/lib/graph/select"; import { take, tap, @@ -105,7 +106,7 @@ export class AvatarUploadWrapperContainer extends AbstractImageUploadWrapperCont confirmToTranslate: this.labelTranslateInterface.imageUploadAvatarDialogConfirm, cancelToTranslate: this.labelTranslateInterface.imageUploadAvatarDialogCancel, imageNotSupportedToTranslate: this.labelTranslateInterface.imageUploadAvatarNotSupported, - imageChangedEvent: undefined, + file: undefined, isLoadingObs: this.isLoadingObs, aspectRatio: this.aspectRatio, roundCropped: this.roundCropped, diff --git a/projects/solidify-frontend/src/lib/image/components/containers/image-upload-wrapper/image-upload-wrapper.container.html b/projects/solidify-frontend/src/lib/image/components/containers/image-upload-wrapper/image-upload-wrapper.container.html index 5b885d9f74cca531edfa69a1aefbe8b73e9d7bb5..238a770b66973008f020e560fe89fe0a0d904844 100644 --- a/projects/solidify-frontend/src/lib/image/components/containers/image-upload-wrapper/image-upload-wrapper.container.html +++ b/projects/solidify-frontend/src/lib/image/components/containers/image-upload-wrapper/image-upload-wrapper.container.html @@ -2,6 +2,9 @@ [class.is-readonly]="!isEditable" [solidifySpinner]="isLoadingObs | async" class="wrapper-avatar" + solidifyFileDropZone + (fileDropped)="fileChangeEvent($event)" + [solidifyFileDropZoneDisabled]="!isEditable" > <div *ngIf="imagePendingUploadUrl; else noPhoto" class="photo-wrapper" @@ -39,7 +42,7 @@ </div> <input #fileInput - (change)="fileChangeEvent($any($event))" + (change)="fileChangeEventByInput($any($event))" [accept]="filesAccepted" class="hide" type="file" diff --git a/projects/solidify-frontend/src/lib/image/components/containers/image-upload-wrapper/image-upload-wrapper.container.ts b/projects/solidify-frontend/src/lib/image/components/containers/image-upload-wrapper/image-upload-wrapper.container.ts index e89c9d62ce8da36fcb48fe0393540ff8f77a2a51..a3fca7f3ab0ddecea49f97ed481b28a5b9f7002a 100644 --- a/projects/solidify-frontend/src/lib/image/components/containers/image-upload-wrapper/image-upload-wrapper.container.ts +++ b/projects/solidify-frontend/src/lib/image/components/containers/image-upload-wrapper/image-upload-wrapper.container.ts @@ -21,7 +21,6 @@ * ----------------------------------------------------------------------------------------------%% */ - import { ChangeDetectionStrategy, ChangeDetectorRef, @@ -91,7 +90,7 @@ export class ImageUploadWrapperContainer extends AbstractImageUploadWrapperConta confirmToTranslate: this.labelTranslateInterface.imageUploadImageDialogConfirm, cancelToTranslate: this.labelTranslateInterface.imageUploadImageDialogCancel, imageNotSupportedToTranslate: this.labelTranslateInterface.imageUploadImageNotSupported, - imageChangedEvent: undefined, + file: undefined, isLoadingObs: this.isLoadingObs, aspectRatio: this.aspectRatio, roundCropped: this.roundCropped, diff --git a/projects/solidify-frontend/src/lib/image/components/dialogs/upload-image/upload-image.dialog.html b/projects/solidify-frontend/src/lib/image/components/dialogs/upload-image/upload-image.dialog.html index 2a4b9a8e0ec981463956109ae478816a9452e1ac..06ede3c540e7e489c48ee091ebbdbf4141f55b6e 100644 --- a/projects/solidify-frontend/src/lib/image/components/dialogs/upload-image/upload-image.dialog.html +++ b/projects/solidify-frontend/src/lib/image/components/dialogs/upload-image/upload-image.dialog.html @@ -10,7 +10,7 @@ [maintainAspectRatio]="data.aspectRatio | isNumber" [aspectRatio]="data.aspectRatio" [containWithinAspectRatio]="data.aspectRatio | isNumber" - [imageChangedEvent]="data.imageChangedEvent" + [imageFile]="data.file" [imageQuality]="80" [onlyScaleDown]="true" [resizeToHeight]="data.resizeToHeight" diff --git a/projects/solidify-frontend/src/lib/image/components/dialogs/upload-image/upload-image.dialog.ts b/projects/solidify-frontend/src/lib/image/components/dialogs/upload-image/upload-image.dialog.ts index 2b42c48ecab4da8c290c178aee0a8dd57b5e61a0..f28f60644098a0fdae1517c1fa29973f1edbe999 100644 --- a/projects/solidify-frontend/src/lib/image/components/dialogs/upload-image/upload-image.dialog.ts +++ b/projects/solidify-frontend/src/lib/image/components/dialogs/upload-image/upload-image.dialog.ts @@ -82,8 +82,7 @@ export class UploadImageDialog extends AbstractInternalDialog<UploadImageDialogD confirm(): void { const blob = this.croppedImage; - const fileList: FileList = this.data.imageChangedEvent.target.files; - Object.assign(blob, {name: fileList.item(0).name}); + Object.assign(blob, {name: this.data.file.name}); this.submit(blob); } @@ -107,7 +106,7 @@ export interface UploadImageDialogData { confirmToTranslate: string; cancelToTranslate: string; imageNotSupportedToTranslate: string; - imageChangedEvent: any; + file: File; isLoadingObs: Observable<boolean>; aspectRatio: number; roundCropped: boolean; diff --git a/projects/solidify-frontend/src/lib/input/containers/file-upload-input/file-upload-input.container.html b/projects/solidify-frontend/src/lib/input/containers/file-upload-input/file-upload-input.container.html index a142d317f228204a93b329d2f403b7aed42213a4..748ec545e4a3eda30f5d3c4bfeaa54584a122c00 100644 --- a/projects/solidify-frontend/src/lib/input/containers/file-upload-input/file-upload-input.container.html +++ b/projects/solidify-frontend/src/lib/input/containers/file-upload-input/file-upload-input.container.html @@ -73,13 +73,15 @@ solidifyAlternativeButton class="browse-button" type="button" + solidifyFileDropZone + (fileDropped)="onFileChange($event)" > <solidify-icon [iconName]="uploadButtonIcon" class="icon" ></solidify-icon> <span>{{uploadButtonLabelToTranslate | translate}}</span> <input #fileInput - (change)="onFileChange($any($event))" + (change)="onFileChangeByInput($any($event))" class="hide" type="file" > diff --git a/projects/solidify-frontend/src/lib/input/containers/file-upload-input/file-upload-input.container.ts b/projects/solidify-frontend/src/lib/input/containers/file-upload-input/file-upload-input.container.ts index a3b01c0f8d39d8bd85b78968b6d93c4ee77a3629..9aa994051cab40358b59f8986aeb770a941e7cc6 100644 --- a/projects/solidify-frontend/src/lib/input/containers/file-upload-input/file-upload-input.container.ts +++ b/projects/solidify-frontend/src/lib/input/containers/file-upload-input/file-upload-input.container.ts @@ -324,16 +324,20 @@ export class FileUploadInputContainer extends AbstractInternalContainer implemen return fileUploadType; } - onFileChange(event: any): void { + onFileChangeByInput(event: any): void { if (event.target.files.length > 0) { const files: FileList = event.target.files; - this.browserFile = files[0]; + this.onFileChange(files); } if (isNotNullNorUndefined(this.fileInput)) { this.fileInput.nativeElement.value = null; } } + onFileChange(files: FileList): void { + this.browserFile = files[0]; + } + deleteFile(): void { this.browserFile = null; }