From 893bb5b7fa369c83e80550ad3f549f491775a892 Mon Sep 17 00:00:00 2001 From: Lone Iversen <108085781+loivsen@users.noreply.github.com> Date: Mon, 24 Jun 2024 15:08:43 +0200 Subject: [PATCH] init --- .../image-cropper-editor-field.element.ts | 252 ++++++++++++++++++ .../image-cropper-editor/components/index.ts | 1 + .../image-cropper-editor/components/types.ts | 20 ++ .../image-cropper-editor-modal.element.ts | 38 ++- 4 files changed, 299 insertions(+), 12 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media/modals/image-cropper-editor/components/image-cropper-editor-field.element.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media/modals/image-cropper-editor/components/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/media/media/modals/image-cropper-editor/components/types.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/image-cropper-editor/components/image-cropper-editor-field.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/image-cropper-editor/components/image-cropper-editor-field.element.ts new file mode 100644 index 0000000000..563aea7e7c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/image-cropper-editor/components/image-cropper-editor-field.element.ts @@ -0,0 +1,252 @@ +import type { UmbImageCropperElement } from '../../../components/input-image-cropper/image-cropper.element.js'; +import type { + UmbImageCropperCrop, + UmbImageCropperCrops, + UmbImageCropperFocalPoint, + UmbImageCropperPropertyEditorValue, +} from './types.js'; +import { css, customElement, html, property, repeat, state, when } from '@umbraco-cms/backoffice/external/lit'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; + +@customElement('umb-image-cropper-editor-field') +export class UmbImageCropperEditorFieldElement extends UmbLitElement { + @property({ attribute: false }) + get value() { + return this.#value; + } + set value(value) { + if (!value) { + this.crops = []; + this.focalPoint = { left: 0.5, top: 0.5 }; + this.src = ''; + this.#value = undefined; + } else { + this.crops = [...value.crops]; + // TODO: This is a temporary solution to make sure we have a focal point + this.focalPoint = value.focalPoint || { left: 0.5, top: 0.5 }; + this.src = value.src; + this.#value = value; + } + + this.requestUpdate(); + } + #value?: UmbImageCropperPropertyEditorValue; + + @state() + crops: UmbImageCropperCrops = []; + + @state() + currentCrop?: UmbImageCropperCrop; + + @property({ attribute: false }) + file?: File; + + @property() + fileDataUrl?: string; + + @state() + focalPoint: UmbImageCropperFocalPoint = { left: 0.5, top: 0.5 }; + + @property({ type: Boolean }) + hideFocalPoint = false; + + @state() + src = ''; + + get source() { + if (this.fileDataUrl) return this.fileDataUrl; + if (this.src) return this.src; + return ''; + } + + updated(changedProperties: Map) { + super.updated(changedProperties); + + if (changedProperties.has('file')) { + if (this.file) { + const reader = new FileReader(); + reader.onload = (event) => { + this.fileDataUrl = event.target?.result as string; + }; + reader.readAsDataURL(this.file); + } else { + this.fileDataUrl = undefined; + } + } + } + + #onCropClick(crop: any) { + const index = this.crops.findIndex((c) => c.alias === crop.alias); + + if (index === -1) return; + + this.currentCrop = { ...this.crops[index] }; + } + + #onCropChange = (event: CustomEvent) => { + const target = event.target as UmbImageCropperElement; + const value = target.value; + + if (!value) return; + + const index = this.crops.findIndex((crop) => crop.alias === value.alias); + + if (index === undefined) return; + + this.crops[index] = value; + this.currentCrop = undefined; + this.#updateValue(); + }; + + #onFocalPointChange = (event: CustomEvent) => { + this.focalPoint = event.detail; + this.#updateValue(); + }; + + #updateValue() { + this.#value = { + crops: [...this.crops], + focalPoint: this.focalPoint, + src: this.src, + }; + + this.dispatchEvent(new UmbChangeEvent()); + } + + #onResetFocalPoint = () => { + this.focalPoint = { left: 0.5, top: 0.5 }; + this.#updateValue(); + }; + + render() { + return html` +
${this.#renderMain()}
+
${this.#renderSide()}
+ `; + } + + #renderMain() { + if (this.currentCrop) { + return html` + + + `; + } + + return html` + + +
+ + ${when( + !this.hideFocalPoint, + () => + html` + + ${this.localize.term('content_resetFocalPoint')} + `, + )} +
+ `; + } + + #renderSide() { + if (!this.value || !this.crops) return; + + return html` + ${repeat( + this.crops, + (crop) => crop.alias + JSON.stringify(crop.coordinates), + (crop) => + html` this.#onCropClick(crop)} + .crop=${crop} + .focalPoint=${this.focalPoint} + .src=${this.source}>`, + )}`; + } + + #resetCurrentCrop() { + this.currentCrop = undefined; + } + + static styles = css` + :host { + display: flex; + width: 100%; + box-sizing: border-box; + gap: var(--uui-size-space-3); + height: 400px; + } + #main { + width: 100%; + height: 100%; + display: flex; + gap: var(--uui-size-space-1); + flex-direction: column; + } + #actions { + display: flex; + justify-content: space-between; + } + + #reset-focal-point uui-icon { + padding-right: var(--uui-size-3); + } + + #reset-current-crop { + --uui-menu-item-flat-structure: 1; + width: 100%; + background-color: var(--uui-color-surface); + } + + slot[name='actions'] { + display: block; + flex: 1; + } + + [current], + #reset-current-crop[current] { + background-color: var(--uui-color-surface-alt); + } + + umb-image-cropper-focus-setter { + height: calc(100% - 33px - var(--uui-size-space-1)); /* Temp solution to make room for actions */ + } + #side { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); + flex: none; + gap: var(--uui-size-space-3); + flex-grow: 1; + overflow-y: auto; + height: fit-content; + max-height: 100%; + } + `; +} + +declare global { + interface HTMLElementTagNameMap { + 'umb-image-cropper-editor-field': UmbImageCropperEditorFieldElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/image-cropper-editor/components/index.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/image-cropper-editor/components/index.ts new file mode 100644 index 0000000000..ad8611ea4f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/image-cropper-editor/components/index.ts @@ -0,0 +1 @@ +export * from './image-cropper-editor-field.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/image-cropper-editor/components/types.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/image-cropper-editor/components/types.ts new file mode 100644 index 0000000000..649368b8a7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/image-cropper-editor/components/types.ts @@ -0,0 +1,20 @@ +export type UmbImageCropperPropertyEditorValue = { + temporaryFileId?: string | null; + crops: Array<{ + alias: string; + coordinates?: { + x1: number; + x2: number; + y1: number; + y2: number; + }; + height: number; + width: number; + }>; + focalPoint: { left: number; top: number }; + src: string; +}; + +export type UmbImageCropperCrop = UmbImageCropperPropertyEditorValue['crops'][number]; +export type UmbImageCropperCrops = UmbImageCropperPropertyEditorValue['crops']; +export type UmbImageCropperFocalPoint = UmbImageCropperPropertyEditorValue['focalPoint']; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/image-cropper-editor/image-cropper-editor-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/image-cropper-editor/image-cropper-editor-modal.element.ts index 7b4763193b..dc15306125 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/image-cropper-editor/image-cropper-editor-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/image-cropper-editor/image-cropper-editor-modal.element.ts @@ -11,6 +11,7 @@ import { css, customElement, html, state } from '@umbraco-cms/backoffice/externa import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router'; import { UMB_MODAL_MANAGER_CONTEXT, UMB_WORKSPACE_MODAL, UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; import type { UmbModalManagerContext } from '@umbraco-cms/backoffice/modal'; +import './components/image-cropper-editor-field.element.js'; /** TODO Make some of the components from property editor image cropper reuseable for this modal... */ @@ -131,20 +132,22 @@ export class UmbImageCropperEditorModalElement extends UmbModalBaseElement< #renderBody() { return html`
- -
- - - - - - -
+ @change=${this.#onChange}> +
+ + ${this.localize.term('mediaPicker_changeMedia')} + + + ${this.localize.term('mediaPicker_openMedia')} + +
+
`; } @@ -157,6 +160,17 @@ export class UmbImageCropperEditorModalElement extends UmbModalBaseElement< flex-direction: column; justify-content: space-between; } + umb-image-cropper-editor-field { + flex-grow: 1; + } + + #actions { + display: inline-flex; + gap: var(--uui-size-space-3); + } + uui-icon { + padding-right: var(--uui-size-3); + } #options { display: flex;