diff --git a/src/Umbraco.Web.UI.Client/.vscode/settings.json b/src/Umbraco.Web.UI.Client/.vscode/settings.json index 8259117d7e..9aec9745fe 100644 --- a/src/Umbraco.Web.UI.Client/.vscode/settings.json +++ b/src/Umbraco.Web.UI.Client/.vscode/settings.json @@ -14,6 +14,7 @@ "pickable", "popovertarget", "Registrator", + "stylesheet", "svgs", "templating", "tinymce", diff --git a/src/Umbraco.Web.UI.Client/e2e/installer.spec.ts b/src/Umbraco.Web.UI.Client/e2e/installer.spec.ts index 9ecfc9d40f..e85cf58dca 100644 --- a/src/Umbraco.Web.UI.Client/e2e/installer.spec.ts +++ b/src/Umbraco.Web.UI.Client/e2e/installer.spec.ts @@ -1,6 +1,6 @@ const { rest } = window.MockServiceWorker; -import { umbracoPath } from '@umbraco-cms/backoffice/utils'; +import { umbracoPath } from 'src/packages/core/utils/index.js'; import { ProblemDetails, RuntimeLevelModel, diff --git a/src/Umbraco.Web.UI.Client/e2e/upgrader.spec.ts b/src/Umbraco.Web.UI.Client/e2e/upgrader.spec.ts index 85def2dff4..d89979df55 100644 --- a/src/Umbraco.Web.UI.Client/e2e/upgrader.spec.ts +++ b/src/Umbraco.Web.UI.Client/e2e/upgrader.spec.ts @@ -1,5 +1,5 @@ const { rest } = window.MockServiceWorker; -import { umbracoPath } from '@umbraco-cms/backoffice/utils'; +import { umbracoPath } from 'src/packages/core/utils/index.js'; import { ProblemDetails, RuntimeLevelModel, diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index d69947fb60..8db4b1de0f 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -14,70 +14,71 @@ "./localization-api": "./dist-cms/libs/localization-api/index.js", "./observable-api": "./dist-cms/libs/observable-api/index.js", "./auth": "./dist-cms/shared/auth/index.js", - "./event": "./dist-cms/packages/core/event/index.js", - "./lit-element": "./dist-cms/packages/core/lit-element/index.js", "./icon": "./dist-cms/shared/icon-registry/index.js", "./models": "./dist-cms/shared/models/index.js", "./resources": "./dist-cms/shared/resources/index.js", "./router": "./dist-cms/shared/router/index.js", "./style": "./dist-cms/shared/style/index.js", - "./utils": "./dist-cms/shared/utils/index.js", "./action": "./dist-cms/packages/core/action/index.js", + "./audit-log": "./dist-cms/packages/audit-log/index.js", + "./block": "./dist-cms/packages/block/index.js", + "./code-editor": "./dist-cms/packages/templating/code-editor/index.js", "./collection": "./dist-cms/packages/core/collection/index.js", "./components": "./dist-cms/packages/core/components/index.js", "./content-type": "./dist-cms/packages/core/content-type/index.js", "./culture": "./dist-cms/packages/core/culture/index.js", + "./current-user": "./dist-cms/packages/user/current-user/index.js", "./data-type": "./dist-cms/packages/core/data-type/index.js", "./debug": "./dist-cms/packages/core/debug/index.js", + "./dictionary": "./dist-cms/packages/dictionary/index.js", + "./document-blueprint": "./dist-cms/packages/documents/document-blueprints/index.js", + "./document-type": "./dist-cms/packages/documents/document-types/index.js", + "./document": "./dist-cms/packages/documents/documents/index.js", + "./dynamic-root": "./dist-cms/packages/dynamic-root/index.js", "./entity-action": "./dist-cms/packages/core/entity-action/index.js", "./entity-bulk-action": "./dist-cms/packages/core/entity-bulk-action/index.js", + "./event": "./dist-cms/packages/core/event/index.js", "./extension-registry": "./dist-cms/packages/core/extension-registry/index.js", - "./server-file-system": "./dist-cms/packages/core/server-file-system/index.js", "./id": "./dist-cms/packages/core/id/index.js", + "./language": "./dist-cms/packages/language/index.js", + "./lit-element": "./dist-cms/packages/core/lit-element/index.js", "./localization": "./dist-cms/packages/core/localization/index.js", + "./log-viewer": "./dist-cms/packages/log-viewer/index.js", + "./media-type": "./dist-cms/packages/media/media-types/index.js", + "./media": "./dist-cms/packages/media/media/index.js", + "./member-group": "./dist-cms/packages/members/member-group/index.js", + "./member-type": "./dist-cms/packages/members/member-type/index.js", + "./member": "./dist-cms/packages/members/member/index.js", "./menu": "./dist-cms/packages/core/menu/index.js", "./modal": "./dist-cms/packages/core/modal/index.js", "./notification": "./dist-cms/packages/core/notification/index.js", + "./object-type": "./dist-cms/packages/object-type/index.js", + "./package": "./dist-cms/packages/packages/package/index.js", + "./partial-view": "./dist-cms/packages/templating/partial-views/index.js", "./picker-input": "./dist-cms/packages/core/picker-input/index.js", - "./property": "./dist-cms/packages/core/property/index.js", "./property-action": "./dist-cms/packages/core/property-action/index.js", "./property-editor": "./dist-cms/packages/core/property-editor/index.js", - "./section": "./dist-cms/packages/core/section/index.js", - "./sorter": "./dist-cms/packages/core/sorter/index.js", - "./store": "./dist-cms/packages/core/store/index.js", - "./themes": "./dist-cms/packages/core/themes/index.js", - "./tree": "./dist-cms/packages/core/tree/index.js", - "./variant": "./dist-cms/packages/core/variant/index.js", - "./workspace": "./dist-cms/packages/core/workspace/index.js", - "./repository": "./dist-cms/packages/core/repository/index.js", - "./temporary-file": "./dist-cms/packages/core/temporary-file/index.js", - "./block": "./dist-cms/packages/block/index.js", - "./audit-log": "./dist-cms/packages/audit-log/index.js", - "./dictionary": "./dist-cms/packages/dictionary/index.js", - "./document": "./dist-cms/packages/documents/documents/index.js", - "./document-blueprint": "./dist-cms/packages/documents/document-blueprints/index.js", - "./document-type": "./dist-cms/packages/documents/document-types/index.js", - "./media": "./dist-cms/packages/media/media/index.js", - "./media-type": "./dist-cms/packages/media/media-types/index.js", - "./member": "./dist-cms/packages/members/member/index.js", - "./member-group": "./dist-cms/packages/members/member-group/index.js", - "./member-type": "./dist-cms/packages/members/member-type/index.js", - "./package": "./dist-cms/packages/packages/package/index.js", - "./language": "./dist-cms/packages/language/index.js", - "./dynamic-root": "./dist-cms/packages/dynamic-root/index.js", - "./log-viewer": "./dist-cms/packages/log-viewer/index.js", + "./property": "./dist-cms/packages/core/property/index.js", "./relation-type": "./dist-cms/packages/relations/relation-types/index.js", "./relations": "./dist-cms/packages/relations/relations/index.js", - "./tags": "./dist-cms/packages/tags/index.js", + "./repository": "./dist-cms/packages/core/repository/index.js", + "./section": "./dist-cms/packages/core/section/index.js", + "./server-file-system": "./dist-cms/packages/core/server-file-system/index.js", + "./sorter": "./dist-cms/packages/core/sorter/index.js", "./static-file": "./dist-cms/packages/static-file/index.js", - "./partial-view": "./dist-cms/packages/templating/partial-views/index.js", + "./store": "./dist-cms/packages/core/store/index.js", "./stylesheet": "./dist-cms/packages/templating/stylesheets/index.js", + "./tags": "./dist-cms/packages/tags/index.js", "./template": "./dist-cms/packages/templating/templates/index.js", + "./temporary-file": "./dist-cms/packages/core/temporary-file/index.js", + "./themes": "./dist-cms/packages/core/themes/index.js", + "./tree": "./dist-cms/packages/core/tree/index.js", "./user-group": "./dist-cms/packages/user/user-group/index.js", - "./current-user": "./dist-cms/packages/user/current-user/index.js", - "./user": "./dist-cms/packages/user/user/index.js", "./user-permission": "./dist-cms/packages/user/user-permission/index.js", - "./code-editor": "./dist-cms/packages/templating/code-editor/index.js", + "./user": "./dist-cms/packages/user/user/index.js", + "./utils": "./dist-cms/packages/core/utils/index.js", + "./variant": "./dist-cms/packages/core/variant/index.js", + "./workspace": "./dist-cms/packages/core/workspace/index.js", "./external/backend-api": "./dist-cms/external/backend-api/index.js", "./external/dompurify": "./dist-cms/external/dompurify/index.js", "./external/lit": "./dist-cms/external/lit/index.js", diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts index 25b8a9c31b..7c1dc0ca0d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts @@ -1,3 +1,8 @@ +import { + getAccumulatedValueOfIndex, + getInterpolatedIndexOfPositionInWeightMap, + isWithinRect, +} from '@umbraco-cms/backoffice/utils'; import { UmbBlockGridEntriesContext } from '../../context/block-grid-entries.context.js'; import type { UmbBlockGridEntryElement } from '../block-grid-entry/index.js'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; @@ -11,62 +16,14 @@ import { type resolveVerticalDirectionArgs, } from '@umbraco-cms/backoffice/sorter'; -// Utils: -// TODO: Move these methods into their own files: - -function expandRect(rect: DOMRect, verticalAdd: number, horizontalAdd: number) { - return new DOMRect( - rect.left - horizontalAdd, - rect.top - verticalAdd, - rect.width + verticalAdd * 2, - rect.height + horizontalAdd * 2, - ); -} - -function getInterpolatedIndexOfPositionInWeightMap(target: number, weights: Array) { - const map = [0]; - weights.reduce((a, b, i) => { - return (map[i + 1] = a + b); - }, 0); - const foundValue = map.reduce((a, b) => { - const aDiff = Math.abs(a - target); - const bDiff = Math.abs(b - target); - - if (aDiff === bDiff) { - return a < b ? a : b; - } else { - return bDiff < aDiff ? b : a; - } - }); - const foundIndex = map.indexOf(foundValue); - const targetDiff = target - foundValue; - let interpolatedIndex = foundIndex; - if (targetDiff < 0 && foundIndex === 0) { - // Don't adjust. - } else if (targetDiff > 0 && foundIndex === map.length - 1) { - // Don't adjust. - } else { - const foundInterpolationWeight = weights[targetDiff >= 0 ? foundIndex : foundIndex - 1]; - interpolatedIndex += foundInterpolationWeight === 0 ? interpolatedIndex : targetDiff / foundInterpolationWeight; - } - return interpolatedIndex; -} - -function getAccumulatedValueOfIndex(index: number, weights: Array) { - const len = Math.min(index, weights.length); - let i = 0, - calc = 0; - while (i < len) { - calc += weights[i++]; - } - return calc; -} - +/** + * Notice this utility method is not really shareable with others as it also takes areas into account. [NL] + */ function resolveVerticalDirectionAsGrid( args: resolveVerticalDirectionArgs, ) { - // If this has areas, we do not want to move: - if (args.relatedModel.areas.length > 0 && expandRect(args.relatedRect, 0, 0)) { + // If this has areas, we do not want to move, unless we are at the edge + if (args.relatedModel.areas.length > 0 && isWithinRect(args.pointerX, args.pointerY, args.relatedRect, -10)) { return null; } @@ -154,6 +111,12 @@ export class UmbBlockGridEntriesElement extends UmbLitElement { // #sorter = new UmbSorterController(this, { ...SORTER_CONFIG, + onStart: () => { + this.#context.onDragStart(); + }, + onEnd: () => { + this.#context.onDragEnd(); + }, onChange: ({ model }) => { this.#context.setLayouts(model); }, @@ -295,13 +258,18 @@ export class UmbBlockGridEntriesElement extends UmbLitElement { uui-button-group { padding-top: 1px; - display: grid; grid-template-columns: 1fr auto; + + --umb-block-grid--is-dragging--variable: var(--umb-block-grid--is-dragging) none; + display: var(--umb-block-grid--is-dragging--variable, grid); } - /*.umb-block-grid__layout-container[data-area-length='0'] { - min-height: 100px; - }*/ + .umb-block-grid__layout-container[data-area-length='0'] { + --umb-block-grid--is-dragging--variable: var(--umb-block-grid--is-dragging) 1; + min-height: calc(var(--umb-block-grid--is-dragging--variable, 0) * var(--uui-size-11)); + border: calc(var(--umb-block-grid--is-dragging--variable, 0) * 1px) dashed var(--uui-color-border); + border-radius: var(--uui-border-radius); + } `, ]; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entries.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entries.context.ts index cc9f12f89b..039d16f8e9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entries.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entries.context.ts @@ -289,4 +289,12 @@ export class UmbBlockGridEntriesContext .indexOf(content.contentTypeKey) !== -1 ); } + + onDragStart() { + this._manager?.onDragStart(); + } + + onDragEnd() { + this._manager?.onDragEnd(); + } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entry.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entry.context.ts index 42f08053e5..a2564d85bd 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entry.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entry.context.ts @@ -1,3 +1,4 @@ +import { closestColumnSpanOption } from '../utils/index.js'; import { UMB_BLOCK_GRID_MANAGER_CONTEXT } from './block-grid-manager.context.js'; import { UMB_BLOCK_GRID_ENTRIES_CONTEXT } from './block-grid-entries.context-token.js'; import { @@ -18,28 +19,6 @@ import { } from '@umbraco-cms/backoffice/observable-api'; import { combineLatest } from '@umbraco-cms/backoffice/external/rxjs'; -function closestColumnSpanOption(target: number, map: Array, max: number) { - if (map.length > 0) { - const result = map.reduce((a, b) => { - if (a > max) { - return b; - } - const aDiff = Math.abs(a - target); - const bDiff = Math.abs(b - target); - - if (aDiff === bDiff) { - return a < b ? a : b; - } else { - return bDiff < aDiff ? b : a; - } - }); - if (result) { - return result; - } - } - return; -} - export class UmbBlockGridEntryContext extends UmbBlockEntryContext< typeof UMB_BLOCK_GRID_MANAGER_CONTEXT, diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-manager.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-manager.context.ts index 8b2e113b98..95e741b008 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-manager.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-manager.context.ts @@ -53,6 +53,14 @@ export class UmbBlockGridManagerContext< return true; } + + onDragStart() { + (this.getHostElement() as HTMLElement).style.setProperty('--umb-block-grid--is-dragging', ' '); + } + + onDragEnd() { + (this.getHostElement() as HTMLElement).style.removeProperty('--umb-block-grid--is-dragging'); + } } // TODO: Make discriminator method for this: diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-scale-manager/block-grid-scale-manager.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-scale-manager/block-grid-scale-manager.controller.ts index 663058e4c9..456076f1f6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-scale-manager/block-grid-scale-manager.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-scale-manager/block-grid-scale-manager.controller.ts @@ -1,70 +1,8 @@ +import { getAccumulatedValueOfIndex, getInterpolatedIndexOfPositionInWeightMap } from '@umbraco-cms/backoffice/utils'; +import { closestColumnSpanOption } from '../../utils/index.js'; import { UmbBaseController } from '@umbraco-cms/backoffice/class-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -// Utils: -// TODO: Move these methods into their own files: - -function getInterpolatedIndexOfPositionInWeightMap(target: number, weights: Array) { - const map = [0]; - weights.reduce((a, b, i) => { - return (map[i + 1] = a + b); - }, 0); - const foundValue = map.reduce((a, b) => { - const aDiff = Math.abs(a - target); - const bDiff = Math.abs(b - target); - - if (aDiff === bDiff) { - return a < b ? a : b; - } else { - return bDiff < aDiff ? b : a; - } - }); - const foundIndex = map.indexOf(foundValue); - const targetDiff = target - foundValue; - let interpolatedIndex = foundIndex; - if (targetDiff < 0 && foundIndex === 0) { - // Don't adjust. - } else if (targetDiff > 0 && foundIndex === map.length - 1) { - // Don't adjust. - } else { - const foundInterpolationWeight = weights[targetDiff >= 0 ? foundIndex : foundIndex - 1]; - interpolatedIndex += foundInterpolationWeight === 0 ? interpolatedIndex : targetDiff / foundInterpolationWeight; - } - return interpolatedIndex; -} - -function getAccumulatedValueOfIndex(index: number, weights: Array) { - const len = Math.min(index, weights.length); - let i = 0, - calc = 0; - while (i < len) { - calc += weights[i++]; - } - return calc; -} - -function closestColumnSpanOption(target: number, map: Array, max: number) { - if (map.length > 0) { - const result = map.reduce((a, b) => { - if (a > max) { - return b; - } - const aDiff = Math.abs(a - target); - const bDiff = Math.abs(b - target); - - if (aDiff === bDiff) { - return a < b ? a : b; - } else { - return bDiff < aDiff ? b : a; - } - }); - if (result) { - return result; - } - } - return; -} - // This might be more generic than Block Grid, but this is where it belongs currently: export interface UmbBlockGridScalableContext extends UmbControllerHost { setColumnSpan: (columnSpan: number) => void; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/utils/index.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/utils/index.ts new file mode 100644 index 0000000000..f46dced98d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/utils/index.ts @@ -0,0 +1,21 @@ +export function closestColumnSpanOption(target: number, map: Array, max: number) { + if (map.length > 0) { + const result = map.reduce((a, b) => { + if (a > max) { + return b; + } + const aDiff = Math.abs(a - target); + const bDiff = Math.abs(b - target); + + if (aDiff === bDiff) { + return a < b ? a : b; + } else { + return bDiff < aDiff ? b : a; + } + }); + if (result) { + return result; + } + } + return; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/input-tiny-mce.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/input-tiny-mce.element.ts index a7594ac3a4..e328c75095 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/input-tiny-mce.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-tiny-mce/input-tiny-mce.element.ts @@ -1,22 +1,50 @@ +import { getProcessedImageUrl } from '@umbraco-cms/backoffice/utils'; import { defaultFallbackConfig } from './input-tiny-mce.defaults.js'; import { pastePreProcessHandler } from './input-tiny-mce.handlers.js'; import { availableLanguages } from './input-tiny-mce.languages.js'; import { uriAttributeSanitizer } from './input-tiny-mce.sanitizer.js'; import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui'; -import type { Editor, RawEditorOptions } from '@umbraco-cms/backoffice/external/tinymce'; +import type { EditorEvent, Editor, RawEditorOptions } from '@umbraco-cms/backoffice/external/tinymce'; import type { TinyMcePluginArguments, UmbTinyMcePluginBase } from '@umbraco-cms/backoffice/components'; import { loadManifestApi } from '@umbraco-cms/backoffice/extension-api'; import { type ManifestTinyMcePlugin, umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; import type { PropertyValueMap } from '@umbraco-cms/backoffice/external/lit'; import { css, customElement, html, property, query, state } from '@umbraco-cms/backoffice/external/lit'; import { firstValueFrom } from '@umbraco-cms/backoffice/external/rxjs'; -import { UmbMediaHelper } from '@umbraco-cms/backoffice/utils'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor'; import { UMB_APP_CONTEXT } from '@umbraco-cms/backoffice/app'; import { UmbStylesheetDetailRepository, UmbStylesheetRuleManager } from '@umbraco-cms/backoffice/stylesheet'; import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; +/** + * Handles the resize event + */ +// TODO: This does somehow not belong as a utility method as it is very specific to this implementation. [NL] +async function onResize( + e: EditorEvent<{ + target: HTMLElement; + width: number; + height: number; + origin: string; + }>, +) { + const srcAttr = e.target.getAttribute('src'); + + if (!srcAttr) { + return; + } + + const path = srcAttr.split('?')[0]; + const resizedPath = await getProcessedImageUrl(path, { + width: e.width, + height: e.height, + mode: 'max', + }); + + e.target.setAttribute('data-mce-src', resizedPath); +} + @customElement('umb-input-tiny-mce') export class UmbInputTinyMceElement extends FormControlMixin(UmbLitElement) { @property({ attribute: false }) @@ -27,7 +55,6 @@ export class UmbInputTinyMceElement extends FormControlMixin(UmbLitElement) { // eslint-disable-next-line @typescript-eslint/consistent-type-imports #renderEditor?: typeof import('@umbraco-cms/backoffice/external/tinymce').renderEditor; - #mediaHelper = new UmbMediaHelper(); #plugins: Array UmbTinyMcePluginBase> = []; #editorRef?: Editor | null = null; #stylesheetRepository: UmbStylesheetDetailRepository; @@ -303,7 +330,7 @@ export class UmbInputTinyMceElement extends FormControlMixin(UmbLitElement) { }); editor.on('ObjectResized', (e) => { - this.#mediaHelper.onResize(e); + onResize(e); this.#onChange(editor.getContent()); }); @@ -338,7 +365,7 @@ export class UmbInputTinyMceElement extends FormControlMixin(UmbLitElement) { } /** - * Nothing rendered by default - TinyMCE initialisation creates + * Nothing rendered by default - TinyMCE initialization creates * a target div and binds the RTE to that element */ render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/index.ts index 58bc8ce5a7..1896412056 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/index.ts @@ -22,14 +22,12 @@ export * from './picker-input/index.js'; export * from './property-action/index.js'; export * from './property-editor/index.js'; export * from './section/index.js'; -export * from './sorter/index.js'; export * from './store/index.js'; export * from './tree/index.js'; export * from './variant/index.js'; export * from './workspace/index.js'; export * from './culture/index.js'; export * from './temporary-file/index.js'; -export * from './object-type/index.js'; export const onInit: UmbEntryPointOnInit = (host, extensionRegistry) => { new UmbExtensionsApiInitializer(host, extensionRegistry, 'globalContext', [host]); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts index 4a49b2c150..d2434310d2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/plugins/tiny-mce-mediapicker.plugin.ts @@ -1,12 +1,12 @@ import type { TinyMcePluginArguments } from '@umbraco-cms/backoffice/components'; import { UmbTinyMcePluginBase } from '@umbraco-cms/backoffice/components'; -import { UmbMediaHelper } from '@umbraco-cms/backoffice/utils'; import type { UmbModalManagerContext } from '@umbraco-cms/backoffice/modal'; import { UMB_MEDIA_TREE_PICKER_MODAL, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; import type { UMB_CURRENT_USER_CONTEXT, UmbCurrentUserModel } from '@umbraco-cms/backoffice/current-user'; import type { RawEditorOptions } from '@umbraco-cms/backoffice/external/tinymce'; import { UmbTemporaryFileRepository } from '@umbraco-cms/backoffice/temporary-file'; import { UmbId } from '@umbraco-cms/backoffice/id'; +import { sizeImageInEditor, uploadBlobImages } from '@umbraco-cms/backoffice/media'; interface MediaPickerTargetData { altText?: string; @@ -26,7 +26,6 @@ interface MediaPickerResultData { } export default class UmbTinyMceMediaPickerPlugin extends UmbTinyMcePluginBase { - #mediaHelper: UmbMediaHelper; #currentUser?: UmbCurrentUserModel; #modalContext?: UmbModalManagerContext; #currentUserContext?: typeof UMB_CURRENT_USER_CONTEXT.TYPE; @@ -35,7 +34,6 @@ export default class UmbTinyMceMediaPickerPlugin extends UmbTinyMcePluginBase { constructor(args: TinyMcePluginArguments) { super(args); - this.#mediaHelper = new UmbMediaHelper(); this.#temporaryFileRepository = new UmbTemporaryFileRepository(args.host); this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (modalContext) => { @@ -79,7 +77,7 @@ export default class UmbTinyMceMediaPickerPlugin extends UmbTinyMcePluginBase { const content = e.content; // Handle images that are pasted in - this.#mediaHelper.uploadBlobImages(this.editor, content); + uploadBlobImages(this.editor, content); }); } } @@ -203,7 +201,7 @@ export default class UmbTinyMceMediaPickerPlugin extends UmbTinyMcePluginBase { // When image is loaded we are ready to call sizeImageInEditor. const onImageLoaded = () => { - this.#mediaHelper?.sizeImageInEditor(this.editor, imgElm, img.url); + sizeImageInEditor(this.editor, imgElm, img.url); this.editor.dispatch('Change'); }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts index b24415de07..452170dcb6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts @@ -1,13 +1,10 @@ +import { isWithinRect } from '@umbraco-cms/backoffice/utils'; import { UmbBaseController } from '@umbraco-cms/backoffice/class-api'; import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; const autoScrollSensitivity = 50; const autoScrollSpeed = 16; -function isWithinRect(x: number, y: number, rect: DOMRect, modifier = 0) { - return x > rect.left - modifier && x < rect.right + modifier && y > rect.top - modifier && y < rect.bottom + modifier; -} - function getParentScrollElement(el: Element, includeSelf: boolean) { if (!el || !el.getBoundingClientRect) return null; @@ -71,6 +68,8 @@ export type resolveVerticalDirectionArgs = { relatedRect: DOMRect; placeholderIsInThisRow: boolean; horizontalPlaceAfter: boolean; + pointerX: number; + pointerY: number; }; type INTERNAL_UmbSorterConfig = { @@ -617,6 +616,8 @@ export class UmbSorterController use backend cli when available + const result = await fetch('/umbraco/management/api/v1/images/GetProcessedImageUrl'); + const url = (await result.json()) as string; + + return url; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/index.ts new file mode 100644 index 0000000000..40eaffd8a1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/index.ts @@ -0,0 +1,19 @@ +export * from './get-processed-image-url.function.js'; +export * from './math/math.js'; +export * from './pagination-manager/pagination.manager.js'; +export * from './path/ensure-path-ends-with-slash.function.js'; +export * from './path/path-decode.function.js'; +export * from './path/path-encode.function.js'; +export * from './path/path-folder-name.function.js'; +export * from './path/umbraco-path.function.js'; +export * from './selection-manager/selection.manager.js'; +export * from './string/generate-umbraco-alias.function.js'; +export * from './string/increment-string.function.js'; +export * from './string/split-string-to-array.js'; +export * from './type/diff.type.js'; + +declare global { + interface Window { + Umbraco: any; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/shared/utils/math.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/math/math.ts similarity index 74% rename from src/Umbraco.Web.UI.Client/src/shared/utils/math.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/utils/math/math.ts index daa6d6b9e1..275f6db11b 100644 --- a/src/Umbraco.Web.UI.Client/src/shared/utils/math.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/math/math.ts @@ -157,3 +157,66 @@ export function calculateExtrapolatedValue(initialValue: number, increaseFactor: return initialValue / (1 - increaseFactor); } + +/** + * Find the index for a target value on an array of individual values. + * @param {number} target - The target value to interpolate to. + * @param {Array} weights - An array of values to interpolate between. + * @returns + */ +export function getInterpolatedIndexOfPositionInWeightMap(target: number, weights: Array): number { + const map = [0]; + weights.reduce((a, b, i) => { + return (map[i + 1] = a + b); + }, 0); + const foundValue = map.reduce((a, b) => { + const aDiff = Math.abs(a - target); + const bDiff = Math.abs(b - target); + + if (aDiff === bDiff) { + return a < b ? a : b; + } else { + return bDiff < aDiff ? b : a; + } + }); + const foundIndex = map.indexOf(foundValue); + const targetDiff = target - foundValue; + let interpolatedIndex = foundIndex; + if (targetDiff < 0 && foundIndex === 0) { + // Don't adjust. + } else if (targetDiff > 0 && foundIndex === map.length - 1) { + // Don't adjust. + } else { + const foundInterpolationWeight = weights[targetDiff >= 0 ? foundIndex : foundIndex - 1]; + interpolatedIndex += foundInterpolationWeight === 0 ? interpolatedIndex : targetDiff / foundInterpolationWeight; + } + return interpolatedIndex; +} + +/** + * Combine the values of an array up to a certain index. + * @param {number} index - The index to accumulate to, everything after this index will not be accumulated. + * @param {Array} weights - An array of values to accumulate. + * @returns + */ +export function getAccumulatedValueOfIndex(index: number, weights: Array): number { + const len = Math.min(index, weights.length); + let i = 0, + calc = 0; + while (i < len) { + calc += weights[i++]; + } + return calc; +} + +/** + * + * @param {number} x - The x coordinate. + * @param {number} y - The y coordinate. + * @param {DOMRect} rect - The rectangle to check. + * @param {number} expand - The amount to expand or contract the rectangle. + * @returns + */ +export function isWithinRect(x: number, y: number, rect: DOMRect, expand = 0) { + return x > rect.left - expand && x < rect.right + expand && y > rect.top - expand && y < rect.bottom + expand; +} diff --git a/src/Umbraco.Web.UI.Client/src/shared/utils/pagination-manager/pagination.manager.test.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/pagination-manager/pagination.manager.test.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/shared/utils/pagination-manager/pagination.manager.test.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/utils/pagination-manager/pagination.manager.test.ts diff --git a/src/Umbraco.Web.UI.Client/src/shared/utils/pagination-manager/pagination.manager.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/pagination-manager/pagination.manager.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/shared/utils/pagination-manager/pagination.manager.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/utils/pagination-manager/pagination.manager.ts diff --git a/src/Umbraco.Web.UI.Client/src/shared/utils/ensure-path-ends-with-slash.function.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/path/ensure-path-ends-with-slash.function.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/shared/utils/ensure-path-ends-with-slash.function.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/utils/path/ensure-path-ends-with-slash.function.ts diff --git a/src/Umbraco.Web.UI.Client/src/shared/utils/path-decode.function.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/path/path-decode.function.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/shared/utils/path-decode.function.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/utils/path/path-decode.function.ts diff --git a/src/Umbraco.Web.UI.Client/src/shared/utils/path-encode.function.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/path/path-encode.function.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/shared/utils/path-encode.function.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/utils/path/path-encode.function.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/path/path-folder-name.function.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/path/path-folder-name.function.ts new file mode 100644 index 0000000000..3644903404 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/path/path-folder-name.function.ts @@ -0,0 +1,3 @@ +import { generateAlias } from '../string/generate-umbraco-alias.function.js'; + +export const pathFolderName = generateAlias; diff --git a/src/Umbraco.Web.UI.Client/src/shared/utils/umbraco-path.function.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/path/umbraco-path.function.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/shared/utils/umbraco-path.function.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/utils/path/umbraco-path.function.ts diff --git a/src/Umbraco.Web.UI.Client/src/shared/utils/selection-manager/selection.manager.test.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/selection-manager/selection.manager.test.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/shared/utils/selection-manager/selection.manager.test.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/utils/selection-manager/selection.manager.test.ts diff --git a/src/Umbraco.Web.UI.Client/src/shared/utils/selection-manager/selection.manager.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/selection-manager/selection.manager.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/shared/utils/selection-manager/selection.manager.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/utils/selection-manager/selection.manager.ts diff --git a/src/Umbraco.Web.UI.Client/src/shared/utils/generate-umbraco-alias.function.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/string/generate-umbraco-alias.function.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/shared/utils/generate-umbraco-alias.function.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/utils/string/generate-umbraco-alias.function.ts diff --git a/src/Umbraco.Web.UI.Client/src/shared/utils/increment-string.function.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/string/increment-string.function.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/shared/utils/increment-string.function.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/utils/string/increment-string.function.ts diff --git a/src/Umbraco.Web.UI.Client/src/shared/utils/split-string-to-array.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/string/split-string-to-array.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/shared/utils/split-string-to-array.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/utils/string/split-string-to-array.ts diff --git a/src/Umbraco.Web.UI.Client/src/shared/utils/diff.type.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/type/diff.type.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/shared/utils/diff.type.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/utils/type/diff.type.ts diff --git a/src/Umbraco.Web.UI.Client/src/shared/utils/utils.test.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/utils.test.ts similarity index 93% rename from src/Umbraco.Web.UI.Client/src/shared/utils/utils.test.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/utils/utils.test.ts index b481c3d0da..ba1c7c10a0 100644 --- a/src/Umbraco.Web.UI.Client/src/shared/utils/utils.test.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/utils.test.ts @@ -1,5 +1,5 @@ import { expect } from '@open-wc/testing'; -import { splitStringToArray } from './split-string-to-array.js'; +import { splitStringToArray } from './string/split-string-to-array.js'; describe('splitStringToArray', () => { it('splits and cleans a comma-separated string', () => { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-editor/workspace-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-editor/workspace-editor.element.ts index 55f23b95cd..e489e80b7a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-editor/workspace-editor.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-editor/workspace-editor.element.ts @@ -6,7 +6,6 @@ import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registr import { UmbExtensionsManifestInitializer, createExtensionElement } from '@umbraco-cms/backoffice/extension-api'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { componentHasManifestProperty } from '@umbraco-cms/backoffice/utils'; /** * @element umb-workspace-editor @@ -63,8 +62,8 @@ export class UmbWorkspaceEditorElement extends UmbLitElement { path: `view/${manifest.meta.pathname}`, component: () => createExtensionElement(manifest), setup: (component) => { - if (component && componentHasManifestProperty(component)) { - component.manifest = manifest; + if (component) { + (component as any).manifest = manifest; } }, } as UmbRoute; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/index.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/index.ts index f2f41d5d5a..33e13a490f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/index.ts @@ -1,11 +1,12 @@ import './components/index.js'; -export * from './repository/index.js'; -export * from './workspace/index.js'; -export * from './tracked-reference/index.js'; -export * from './user-permissions/index.js'; export * from './components/index.js'; export * from './entity.js'; +export * from './repository/index.js'; +export * from './tracked-reference/index.js'; +export * from './user-permissions/index.js'; +export * from './utils/index.js'; +export * from './workspace/index.js'; export { UMB_MEDIA_TREE_ALIAS } from './tree/index.js'; export { UMB_MEDIA_COLLECTION_ALIAS } from './collection/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/utils/index.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/utils/index.ts new file mode 100644 index 0000000000..49550cabaa --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/utils/index.ts @@ -0,0 +1,124 @@ +// TODO => this is NOT a full reimplementation of the existing media helper service, currently +// contains only functions referenced by the TinyMCE editor +// TODO: This should not be done in this way, we need to split this into seperate defined helper methods. This is also very specific to TinyMCE, so should be named that way. + +import { getProcessedImageUrl } from '@umbraco-cms/backoffice/utils'; +import type { Editor } from '@umbraco-cms/backoffice/external/tinymce'; + +/** + * Sizes an image in the editor + */ +export async function sizeImageInEditor(editor: Editor, imageDomElement: HTMLElement, imgUrl?: string) { + const size = editor.dom.getSize(imageDomElement); + const maxImageSize = editor.options.get('maxImageSize'); + + if (maxImageSize && maxImageSize > 0) { + const newSize = scaleToMaxSize(maxImageSize, size.w, size.h); + + editor.dom.setAttribs(imageDomElement, { width: Math.round(newSize.width), height: Math.round(newSize.height) }); + + // Images inserted via Media Picker will have a URL we can use for ImageResizer QueryStrings + // Images pasted/dragged in are not persisted to media until saved & thus will need to be added + if (imgUrl) { + const resizedImgUrl = await getProcessedImageUrl(imgUrl, { + width: newSize.width, + height: newSize.height, + }); + + editor.dom.setAttrib(imageDomElement, 'data-mce-src', resizedImgUrl); + } + + editor.execCommand('mceAutoResize', false); + } +} + +/** + * Scales an image to the max size + */ +export function scaleToMaxSize(maxSize: number, width: number, height: number) { + const retval = { width, height }; + + const maxWidth = maxSize; // Max width for the image + const maxHeight = maxSize; // Max height for the image + let ratio = 0; // Used for aspect ratio + + // Check if the current width is larger than the max + if (width > maxWidth) { + ratio = maxWidth / width; // get ratio for scaling image + + retval.width = maxWidth; + retval.height = height * ratio; + + height = height * ratio; // Reset height to match scaled image + width = width * ratio; // Reset width to match scaled image + } + + // Check if current height is larger than max + if (height > maxHeight) { + ratio = maxHeight / height; // get ratio for scaling image + + retval.height = maxHeight; + retval.width = width * ratio; + width = width * ratio; // Reset width to match scaled image + } + + return retval; +} + +/** + * Uploads blob images to the server + */ +export async function uploadBlobImages(editor: Editor, newContent?: string) { + const content = newContent ?? editor.getContent(); + + // Upload BLOB images (dragged/pasted ones) + // find src attribute where value starts with `blob:` + // search is case-insensitive and allows single or double quotes + if (content.search(/src=["']blob:.*?["']/gi) !== -1) { + const data = await editor.uploadImages(); + // Once all images have been uploaded + data.forEach((item) => { + // Skip items that failed upload + if (item.status === false) { + return; + } + + // Select img element + const img = item.element; + + // Get img src + const imgSrc = img.getAttribute('src'); + const tmpLocation = sessionStorage.getItem(`tinymce__${imgSrc}`); + + // Select the img & add new attr which we can search for + // When its being persisted in RTE property editor + // To create a media item & delete this tmp one etc + editor.dom.setAttrib(img, 'data-tmpimg', tmpLocation); + + // Resize the image to the max size configured + // NOTE: no imagesrc passed into func as the src is blob://... + // We will append ImageResizing Querystrings on perist to DB with node save + sizeImageInEditor(editor, img); + }); + + // Get all img where src starts with blob: AND does NOT have a data=tmpimg attribute + // This is most likely seen as a duplicate image that has already been uploaded + // editor.uploadImages() does not give us any indiciation that the image been uploaded already + const blobImageWithNoTmpImgAttribute = editor.dom.select('img[src^="blob:"]:not([data-tmpimg])'); + + //For each of these selected items + blobImageWithNoTmpImgAttribute.forEach((imageElement) => { + const blobSrcUri = editor.dom.getAttrib(imageElement, 'src'); + + // Find the same image uploaded (Should be in SessionStorage) + // May already exist in the editor as duplicate image + // OR added to the RTE, deleted & re-added again + // So lets fetch the tempurl out of sessionStorage for that blob URI item + const tmpLocation = sessionStorage.getItem(`tinymce__${blobSrcUri}`); + if (tmpLocation) { + sizeImageInEditor(editor, imageElement); + editor.dom.setAttrib(imageElement, 'data-tmpimg', tmpLocation); + } + }); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/object-type/index.ts b/src/Umbraco.Web.UI.Client/src/packages/object-type/index.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/core/object-type/index.ts rename to src/Umbraco.Web.UI.Client/src/packages/object-type/index.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/object-type/input-object-type.element.ts b/src/Umbraco.Web.UI.Client/src/packages/object-type/input-object-type.element.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/core/object-type/input-object-type.element.ts rename to src/Umbraco.Web.UI.Client/src/packages/object-type/input-object-type.element.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/object-type/object-type.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/object-type/object-type.repository.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/core/object-type/object-type.repository.ts rename to src/Umbraco.Web.UI.Client/src/packages/object-type/object-type.repository.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/relations/relation-types/workspace/views/relation-type/relation-type-workspace-view-relation-type.element.ts b/src/Umbraco.Web.UI.Client/src/packages/relations/relation-types/workspace/views/relation-type/relation-type-workspace-view-relation-type.element.ts index ccb41338f7..9281acc4e0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/relations/relation-types/workspace/views/relation-type/relation-type-workspace-view-relation-type.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/relations/relation-types/workspace/views/relation-type/relation-type-workspace-view-relation-type.element.ts @@ -10,6 +10,7 @@ import { css, html, customElement, state, ifDefined } from '@umbraco-cms/backoff import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import type { RelationTypeResponseModel } from '@umbraco-cms/backoffice/external/backend-api'; import type { UmbWorkspaceViewElement } from '@umbraco-cms/backoffice/extension-registry'; +import '@umbraco-cms/backoffice/object-type'; @customElement('umb-relation-type-workspace-view-relation-type') export class UmbRelationTypeWorkspaceViewRelationTypeElement extends UmbLitElement implements UmbWorkspaceViewElement { diff --git a/src/Umbraco.Web.UI.Client/src/shared/utils/component-has-manifest-property.function.ts b/src/Umbraco.Web.UI.Client/src/shared/utils/component-has-manifest-property.function.ts deleted file mode 100644 index 0316d08536..0000000000 --- a/src/Umbraco.Web.UI.Client/src/shared/utils/component-has-manifest-property.function.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { ManifestBase } from '@umbraco-cms/backoffice/extension-api'; - -export function componentHasManifestProperty( - component: HTMLElement, -): component is HTMLElement & { manifest: ManifestBase } { - return component ? 'manifest' in component : false; -} diff --git a/src/Umbraco.Web.UI.Client/src/shared/utils/index.ts b/src/Umbraco.Web.UI.Client/src/shared/utils/index.ts deleted file mode 100644 index f68fb1cb1e..0000000000 --- a/src/Umbraco.Web.UI.Client/src/shared/utils/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -export * from './component-has-manifest-property.function.js'; -export * from './diff.type.js'; -export * from './ensure-path-ends-with-slash.function.js'; -export * from './generate-umbraco-alias.function.js'; -export * from './increment-string.function.js'; -export * from './media-helper.service.js'; -export * from './pagination-manager/pagination.manager.js'; -export * from './path-decode.function.js'; -export * from './path-encode.function.js'; -export * from './path-folder-name.function.js'; -export * from './selection-manager/selection.manager.js'; -export * from './umbraco-path.function.js'; -export * from './math.js'; -export * from './split-string-to-array.js'; - -declare global { - interface Window { - Umbraco: any; - } -} diff --git a/src/Umbraco.Web.UI.Client/src/shared/utils/media-helper.service.ts b/src/Umbraco.Web.UI.Client/src/shared/utils/media-helper.service.ts deleted file mode 100644 index b90178f579..0000000000 --- a/src/Umbraco.Web.UI.Client/src/shared/utils/media-helper.service.ts +++ /dev/null @@ -1,167 +0,0 @@ -// TODO => this is NOT a full reimplementation of the existing media helper service, currently -// contains only functions referenced by the TinyMCE editor -// TODO: This should not be done in this way, we need to split this into seperate defined helper methods. This is also very specific to TinyMCE, so should be named that way. - -import type { Editor, EditorEvent } from '@umbraco-cms/backoffice/external/tinymce'; - -export class UmbMediaHelper { - /** - * Sizes an image in the editor - */ - async sizeImageInEditor(editor: Editor, imageDomElement: HTMLElement, imgUrl?: string) { - const size = editor.dom.getSize(imageDomElement); - const maxImageSize = editor.options.get('maxImageSize'); - - if (maxImageSize && maxImageSize > 0) { - const newSize = this.scaleToMaxSize(maxImageSize, size.w, size.h); - - editor.dom.setAttribs(imageDomElement, { width: Math.round(newSize.width), height: Math.round(newSize.height) }); - - // Images inserted via Media Picker will have a URL we can use for ImageResizer QueryStrings - // Images pasted/dragged in are not persisted to media until saved & thus will need to be added - if (imgUrl) { - const resizedImgUrl = await this.getProcessedImageUrl(imgUrl, { - width: newSize.width, - height: newSize.height, - }); - - editor.dom.setAttrib(imageDomElement, 'data-mce-src', resizedImgUrl); - } - - editor.execCommand('mceAutoResize', false); - } - } - - /** - * Scales an image to the max size - */ - scaleToMaxSize(maxSize: number, width: number, height: number) { - const retval = { width, height }; - - const maxWidth = maxSize; // Max width for the image - const maxHeight = maxSize; // Max height for the image - let ratio = 0; // Used for aspect ratio - - // Check if the current width is larger than the max - if (width > maxWidth) { - ratio = maxWidth / width; // get ratio for scaling image - - retval.width = maxWidth; - retval.height = height * ratio; - - height = height * ratio; // Reset height to match scaled image - width = width * ratio; // Reset width to match scaled image - } - - // Check if current height is larger than max - if (height > maxHeight) { - ratio = maxHeight / height; // get ratio for scaling image - - retval.height = maxHeight; - retval.width = width * ratio; - width = width * ratio; // Reset width to match scaled image - } - - return retval; - } - - /** - * Returns the URL of the processed image - */ - async getProcessedImageUrl(imagePath: string, options: any) { - if (!options) { - return imagePath; - } - - // TODO => use backend cli when available - const result = await fetch('/umbraco/management/api/v1/images/GetProcessedImageUrl'); - const url = (await result.json()) as string; - - return url; - } - - /** - * Uploads blob images to the server - */ - async uploadBlobImages(editor: Editor, newContent?: string) { - const content = newContent ?? editor.getContent(); - - // Upload BLOB images (dragged/pasted ones) - // find src attribute where value starts with `blob:` - // search is case-insensitive and allows single or double quotes - if (content.search(/src=["']blob:.*?["']/gi) !== -1) { - const data = await editor.uploadImages(); - // Once all images have been uploaded - data.forEach((item) => { - // Skip items that failed upload - if (item.status === false) { - return; - } - - // Select img element - const img = item.element; - - // Get img src - const imgSrc = img.getAttribute('src'); - const tmpLocation = sessionStorage.getItem(`tinymce__${imgSrc}`); - - // Select the img & add new attr which we can search for - // When its being persisted in RTE property editor - // To create a media item & delete this tmp one etc - editor.dom.setAttrib(img, 'data-tmpimg', tmpLocation); - - // Resize the image to the max size configured - // NOTE: no imagesrc passed into func as the src is blob://... - // We will append ImageResizing Querystrings on perist to DB with node save - this.sizeImageInEditor(editor, img); - }); - - // Get all img where src starts with blob: AND does NOT have a data=tmpimg attribute - // This is most likely seen as a duplicate image that has already been uploaded - // editor.uploadImages() does not give us any indiciation that the image been uploaded already - const blobImageWithNoTmpImgAttribute = editor.dom.select('img[src^="blob:"]:not([data-tmpimg])'); - - //For each of these selected items - blobImageWithNoTmpImgAttribute.forEach((imageElement) => { - const blobSrcUri = editor.dom.getAttrib(imageElement, 'src'); - - // Find the same image uploaded (Should be in SessionStorage) - // May already exist in the editor as duplicate image - // OR added to the RTE, deleted & re-added again - // So lets fetch the tempurl out of sessionStorage for that blob URI item - const tmpLocation = sessionStorage.getItem(`tinymce__${blobSrcUri}`); - if (tmpLocation) { - this.sizeImageInEditor(editor, imageElement); - editor.dom.setAttrib(imageElement, 'data-tmpimg', tmpLocation); - } - }); - } - } - - /** - * Handles the resize event - */ - async onResize( - e: EditorEvent<{ - target: HTMLElement; - width: number; - height: number; - origin: string; - }>, - ) { - const srcAttr = e.target.getAttribute('src'); - - if (!srcAttr) { - return; - } - - const path = srcAttr.split('?')[0]; - const resizedPath = await this.getProcessedImageUrl(path, { - width: e.width, - height: e.height, - mode: 'max', - }); - - e.target.setAttribute('data-mce-src', resizedPath); - } -} diff --git a/src/Umbraco.Web.UI.Client/src/shared/utils/path-folder-name.function.ts b/src/Umbraco.Web.UI.Client/src/shared/utils/path-folder-name.function.ts deleted file mode 100644 index b5fc256a9a..0000000000 --- a/src/Umbraco.Web.UI.Client/src/shared/utils/path-folder-name.function.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { generateAlias } from './generate-umbraco-alias.function.js'; - -export const pathFolderName = generateAlias; diff --git a/src/Umbraco.Web.UI.Client/tsconfig.json b/src/Umbraco.Web.UI.Client/tsconfig.json index c5b50ee612..8fb5920d7f 100644 --- a/src/Umbraco.Web.UI.Client/tsconfig.json +++ b/src/Umbraco.Web.UI.Client/tsconfig.json @@ -32,70 +32,71 @@ "@umbraco-cms/backoffice/localization-api": ["./src/libs/localization-api/index.ts"], "@umbraco-cms/backoffice/observable-api": ["./src/libs/observable-api/index.ts"], "@umbraco-cms/backoffice/auth": ["./src/shared/auth/index.ts"], - "@umbraco-cms/backoffice/event": ["./src/packages/core/event/index.ts"], - "@umbraco-cms/backoffice/lit-element": ["./src/packages/core/lit-element/index.ts"], "@umbraco-cms/backoffice/icon": ["./src/shared/icon-registry/index.ts"], "@umbraco-cms/backoffice/models": ["./src/shared/models/index.ts"], "@umbraco-cms/backoffice/resources": ["./src/shared/resources/index.ts"], "@umbraco-cms/backoffice/router": ["./src/shared/router/index.ts"], "@umbraco-cms/backoffice/style": ["./src/shared/style/index.ts"], - "@umbraco-cms/backoffice/utils": ["./src/shared/utils/index.ts"], + "@umbraco-cms/backoffice/utils": ["src/packages/core/utils/index.ts"], "@umbraco-cms/backoffice/action": ["./src/packages/core/action/index.ts"], + "@umbraco-cms/backoffice/audit-log": ["./src/packages/audit-log/index.ts"], + "@umbraco-cms/backoffice/block": ["./src/packages/block/index.ts"], + "@umbraco-cms/backoffice/code-editor": ["./src/packages/templating/code-editor/index.ts"], "@umbraco-cms/backoffice/collection": ["./src/packages/core/collection/index.ts"], "@umbraco-cms/backoffice/components": ["./src/packages/core/components/index.ts"], "@umbraco-cms/backoffice/content-type": ["./src/packages/core/content-type/index.ts"], "@umbraco-cms/backoffice/culture": ["./src/packages/core/culture/index.ts"], + "@umbraco-cms/backoffice/current-user": ["./src/packages/user/current-user/index.ts"], "@umbraco-cms/backoffice/data-type": ["./src/packages/core/data-type/index.ts"], "@umbraco-cms/backoffice/debug": ["./src/packages/core/debug/index.ts"], + "@umbraco-cms/backoffice/dictionary": ["./src/packages/dictionary/index.ts"], + "@umbraco-cms/backoffice/document-blueprint": ["./src/packages/documents/document-blueprints/index.ts"], + "@umbraco-cms/backoffice/document-type": ["./src/packages/documents/document-types/index.ts"], + "@umbraco-cms/backoffice/document": ["./src/packages/documents/documents/index.ts"], + "@umbraco-cms/backoffice/dynamic-root": ["./src/packages/dynamic-root/index.ts"], "@umbraco-cms/backoffice/entity-action": ["./src/packages/core/entity-action/index.ts"], "@umbraco-cms/backoffice/entity-bulk-action": ["./src/packages/core/entity-bulk-action/index.ts"], + "@umbraco-cms/backoffice/event": ["./src/packages/core/event/index.ts"], "@umbraco-cms/backoffice/extension-registry": ["./src/packages/core/extension-registry/index.ts"], - "@umbraco-cms/backoffice/server-file-system": ["./src/packages/core/server-file-system/index.ts"], "@umbraco-cms/backoffice/id": ["./src/packages/core/id/index.ts"], + "@umbraco-cms/backoffice/language": ["./src/packages/language/index.ts"], + "@umbraco-cms/backoffice/lit-element": ["./src/packages/core/lit-element/index.ts"], "@umbraco-cms/backoffice/localization": ["./src/packages/core/localization/index.ts"], + "@umbraco-cms/backoffice/log-viewer": ["./src/packages/log-viewer/index.ts"], + "@umbraco-cms/backoffice/media-type": ["./src/packages/media/media-types/index.ts"], + "@umbraco-cms/backoffice/media": ["./src/packages/media/media/index.ts"], + "@umbraco-cms/backoffice/member-group": ["./src/packages/members/member-group/index.ts"], + "@umbraco-cms/backoffice/member-type": ["./src/packages/members/member-type/index.ts"], + "@umbraco-cms/backoffice/member": ["./src/packages/members/member/index.ts"], "@umbraco-cms/backoffice/menu": ["./src/packages/core/menu/index.ts"], "@umbraco-cms/backoffice/modal": ["./src/packages/core/modal/index.ts"], "@umbraco-cms/backoffice/notification": ["./src/packages/core/notification/index.ts"], + "@umbraco-cms/backoffice/object-type": ["./src/packages/object-type/index.ts"], + "@umbraco-cms/backoffice/package": ["./src/packages/packages/package/index.ts"], + "@umbraco-cms/backoffice/partial-view": ["./src/packages/templating/partial-views/index.ts"], "@umbraco-cms/backoffice/picker-input": ["./src/packages/core/picker-input/index.ts"], - "@umbraco-cms/backoffice/property": ["./src/packages/core/property/index.ts"], "@umbraco-cms/backoffice/property-action": ["./src/packages/core/property-action/index.ts"], "@umbraco-cms/backoffice/property-editor": ["./src/packages/core/property-editor/index.ts"], - "@umbraco-cms/backoffice/section": ["./src/packages/core/section/index.ts"], - "@umbraco-cms/backoffice/sorter": ["./src/packages/core/sorter/index.ts"], - "@umbraco-cms/backoffice/store": ["./src/packages/core/store/index.ts"], - "@umbraco-cms/backoffice/themes": ["./src/packages/core/themes/index.ts"], - "@umbraco-cms/backoffice/tree": ["./src/packages/core/tree/index.ts"], - "@umbraco-cms/backoffice/variant": ["./src/packages/core/variant/index.ts"], - "@umbraco-cms/backoffice/workspace": ["./src/packages/core/workspace/index.ts"], - "@umbraco-cms/backoffice/repository": ["./src/packages/core/repository/index.ts"], - "@umbraco-cms/backoffice/temporary-file": ["./src/packages/core/temporary-file/index.ts"], - "@umbraco-cms/backoffice/block": ["./src/packages/block/index.ts"], - "@umbraco-cms/backoffice/audit-log": ["./src/packages/audit-log/index.ts"], - "@umbraco-cms/backoffice/dictionary": ["./src/packages/dictionary/index.ts"], - "@umbraco-cms/backoffice/document": ["./src/packages/documents/documents/index.ts"], - "@umbraco-cms/backoffice/document-blueprint": ["./src/packages/documents/document-blueprints/index.ts"], - "@umbraco-cms/backoffice/document-type": ["./src/packages/documents/document-types/index.ts"], - "@umbraco-cms/backoffice/media": ["./src/packages/media/media/index.ts"], - "@umbraco-cms/backoffice/media-type": ["./src/packages/media/media-types/index.ts"], - "@umbraco-cms/backoffice/member": ["./src/packages/members/member/index.ts"], - "@umbraco-cms/backoffice/member-group": ["./src/packages/members/member-group/index.ts"], - "@umbraco-cms/backoffice/member-type": ["./src/packages/members/member-type/index.ts"], - "@umbraco-cms/backoffice/package": ["./src/packages/packages/package/index.ts"], - "@umbraco-cms/backoffice/language": ["./src/packages/language/index.ts"], - "@umbraco-cms/backoffice/dynamic-root": ["./src/packages/dynamic-root/index.ts"], - "@umbraco-cms/backoffice/log-viewer": ["./src/packages/log-viewer/index.ts"], + "@umbraco-cms/backoffice/property": ["./src/packages/core/property/index.ts"], "@umbraco-cms/backoffice/relation-type": ["./src/packages/relations/relation-types/index.ts"], "@umbraco-cms/backoffice/relations": ["./src/packages/relations/relations/index.ts"], - "@umbraco-cms/backoffice/tags": ["./src/packages/tags/index.ts"], + "@umbraco-cms/backoffice/repository": ["./src/packages/core/repository/index.ts"], + "@umbraco-cms/backoffice/section": ["./src/packages/core/section/index.ts"], + "@umbraco-cms/backoffice/server-file-system": ["./src/packages/core/server-file-system/index.ts"], + "@umbraco-cms/backoffice/sorter": ["./src/packages/core/sorter/index.ts"], "@umbraco-cms/backoffice/static-file": ["./src/packages/static-file/index.ts"], - "@umbraco-cms/backoffice/partial-view": ["./src/packages/templating/partial-views/index.ts"], + "@umbraco-cms/backoffice/store": ["./src/packages/core/store/index.ts"], "@umbraco-cms/backoffice/stylesheet": ["./src/packages/templating/stylesheets/index.ts"], + "@umbraco-cms/backoffice/tags": ["./src/packages/tags/index.ts"], "@umbraco-cms/backoffice/template": ["./src/packages/templating/templates/index.ts"], + "@umbraco-cms/backoffice/temporary-file": ["./src/packages/core/temporary-file/index.ts"], + "@umbraco-cms/backoffice/themes": ["./src/packages/core/themes/index.ts"], + "@umbraco-cms/backoffice/tree": ["./src/packages/core/tree/index.ts"], "@umbraco-cms/backoffice/user-group": ["./src/packages/user/user-group/index.ts"], - "@umbraco-cms/backoffice/current-user": ["./src/packages/user/current-user/index.ts"], - "@umbraco-cms/backoffice/user": ["./src/packages/user/user/index.ts"], "@umbraco-cms/backoffice/user-permission": ["./src/packages/user/user-permission/index.ts"], - "@umbraco-cms/backoffice/code-editor": ["./src/packages/templating/code-editor/index.ts"], + "@umbraco-cms/backoffice/user": ["./src/packages/user/user/index.ts"], + "@umbraco-cms/backoffice/variant": ["./src/packages/core/variant/index.ts"], + "@umbraco-cms/backoffice/workspace": ["./src/packages/core/workspace/index.ts"], "@umbraco-cms/backoffice/external/backend-api": ["./src/external/backend-api/index.ts"], "@umbraco-cms/backoffice/external/dompurify": ["./src/external/dompurify/index.ts"], "@umbraco-cms/backoffice/external/lit": ["./src/external/lit/index.ts"],