diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/index.ts index 1fd80d6f7e..b6141b90b2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/index.ts @@ -33,3 +33,4 @@ export * from './tooltip-menu/index.js'; export * from './variant-selector/index.js'; export * from './popover-layout/index.js'; export * from './multiple-text-string-input/index.js'; +export * from './multiple-color-picker-input/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-image-cropper/image-cropper-focus-setter.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-image-cropper/image-cropper-focus-setter.element.ts index 4042a2a5ec..d8d9399b94 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-image-cropper/image-cropper-focus-setter.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-image-cropper/image-cropper-focus-setter.element.ts @@ -13,9 +13,9 @@ import { @customElement('umb-image-cropper-focus-setter') export class UmbImageCropperFocusSetterElement extends LitElement { - @query('#image') imageElement!: HTMLImageElement; - @query('#wrapper') wrapperElement!: HTMLImageElement; - @query('#focal-point') focalPointElement!: HTMLImageElement; + @query('#image') imageElement?: HTMLImageElement; + @query('#wrapper') wrapperElement?: HTMLImageElement; + @query('#focal-point') focalPointElement?: HTMLImageElement; @property({ type: String }) src?: string; @property({ attribute: false }) focalPoint: UmbImageCropperFocalPoint = { left: 0.5, top: 0.5 }; @@ -35,7 +35,7 @@ export class UmbImageCropperFocusSetterElement extends LitElement { protected updated(_changedProperties: PropertyValueMap | Map): void { super.updated(_changedProperties); - if (_changedProperties.has('focalPoint')) { + if (_changedProperties.has('focalPoint') && this.focalPointElement) { this.focalPointElement.style.left = `calc(${this.focalPoint.left * 100}% - ${this.#DOT_RADIUS}px)`; this.focalPointElement.style.top = `calc(${this.focalPoint.top * 100}% - ${this.#DOT_RADIUS}px)`; } @@ -45,23 +45,28 @@ export class UmbImageCropperFocusSetterElement extends LitElement { super.firstUpdated(_changedProperties); this.style.setProperty('--dot-radius', `${this.#DOT_RADIUS}px`); - this.focalPointElement.style.left = `calc(${this.focalPoint.left * 100}% - ${this.#DOT_RADIUS}px)`; - this.focalPointElement.style.top = `calc(${this.focalPoint.top * 100}% - ${this.#DOT_RADIUS}px)`; - this.imageElement.onload = () => { - const imageAspectRatio = this.imageElement.naturalWidth / this.imageElement.naturalHeight; + if (this.focalPointElement) { + this.focalPointElement.style.left = `calc(${this.focalPoint.left * 100}% - ${this.#DOT_RADIUS}px)`; + this.focalPointElement.style.top = `calc(${this.focalPoint.top * 100}% - ${this.#DOT_RADIUS}px)`; + } + if (this.imageElement) { + this.imageElement.onload = () => { + if (!this.imageElement || !this.wrapperElement) return; + const imageAspectRatio = this.imageElement.naturalWidth / this.imageElement.naturalHeight; - if (imageAspectRatio > 1) { - this.imageElement.style.width = '100%'; - this.wrapperElement.style.width = '100%'; - } else { - this.imageElement.style.height = '100%'; - this.wrapperElement.style.height = '100%'; - } + if (imageAspectRatio > 1) { + this.imageElement.style.width = '100%'; + this.wrapperElement.style.width = '100%'; + } else { + this.imageElement.style.height = '100%'; + this.wrapperElement.style.height = '100%'; + } - this.imageElement.style.aspectRatio = `${imageAspectRatio}`; - this.wrapperElement.style.aspectRatio = `${imageAspectRatio}`; - }; + this.imageElement.style.aspectRatio = `${imageAspectRatio}`; + this.wrapperElement.style.aspectRatio = `${imageAspectRatio}`; + }; + } } async #addEventListeners() { @@ -92,6 +97,7 @@ export class UmbImageCropperFocusSetterElement extends LitElement { #onSetFocalPoint(event: MouseEvent) { event.preventDefault(); + if (!this.focalPointElement || !this.imageElement) return; const image = this.imageElement.getBoundingClientRect(); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-image-cropper/input-image-cropper.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-image-cropper/input-image-cropper.element.ts index 63282ae372..9a93d83a37 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/input-image-cropper/input-image-cropper.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/input-image-cropper/input-image-cropper.element.ts @@ -109,8 +109,8 @@ export class UmbInputImageCropperElement extends LitElement { .focalPoint=${this.focalPoint} .src=${this.src}>
- Remove files (NOT IMPLEMENTED YET) - Reset focal point + Remove files (NOT IMPLEMENTED YET) + Reset focal point
`; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-color-picker-input/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-color-picker-input/index.ts new file mode 100644 index 0000000000..77207cd43b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-color-picker-input/index.ts @@ -0,0 +1,2 @@ +export * from './multiple-color-picker-input.element.js'; +export * from './multiple-color-picker-item-input.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-color-picker-input/multiple-color-picker-input.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-color-picker-input/multiple-color-picker-input.element.ts new file mode 100644 index 0000000000..815ebff28c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-color-picker-input/multiple-color-picker-input.element.ts @@ -0,0 +1,254 @@ +import { UMB_DATA_TYPE_WORKSPACE_CONTEXT } from '../../data-type/workspace/data-type-workspace.context.js'; +import { UmbMultipleColorPickerItemInputElement } from './multiple-color-picker-item-input.element.js'; +import type { UmbSwatchDetails } from '@umbraco-cms/backoffice/models'; +import { + css, + html, + nothing, + repeat, + customElement, + property, + state, + ifDefined, +} from '@umbraco-cms/backoffice/external/lit'; +import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui'; +import { UmbInputEvent, UmbChangeEvent, UmbDeleteEvent } from '@umbraco-cms/backoffice/event'; +import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; +import { UmbSorterConfig, UmbSorterController } from '@umbraco-cms/backoffice/sorter'; + +const SORTER_CONFIG: UmbSorterConfig = { + compareElementToModel: (element: HTMLElement, model: UmbSwatchDetails) => { + return element.getAttribute('data-sort-entry-id') === model.value; + }, + querySelectModelToElement: (container: HTMLElement, modelEntry: UmbSwatchDetails) => { + return container.querySelector('data-sort-entry-id=[' + modelEntry.value + ']'); + }, + identifier: 'Umb.SorterIdentifier.ColorEditor', + itemSelector: 'umb-multiple-color-picker-item-input', + containerSelector: '#sorter-wrapper', +}; + +/** + * @element umb-multiple-color-picker-input + */ +@customElement('umb-multiple-color-picker-input') +export class UmbMultipleColorPickerInputElement extends FormControlMixin(UmbLitElement) { + #prevalueSorter = new UmbSorterController(this, { + ...SORTER_CONFIG, + + performItemInsert: (args) => { + const frozenArray = [...this.items]; + const indexToMove = frozenArray.findIndex((x) => x.value === args.item.value); + + frozenArray.splice(indexToMove, 1); + frozenArray.splice(args.newIndex, 0, args.item); + this.items = frozenArray; + + this.dispatchEvent(new UmbChangeEvent()); + + return true; + }, + performItemRemove: (args) => { + return true; + }, + }); + + /** + * This is a minimum amount of selected items in this input. + * @type {number} + * @attr + * @default undefined + */ + @property({ type: Number }) + min?: number; + + /** + * Min validation message. + * @type {boolean} + * @attr + * @default + */ + @property({ type: String, attribute: 'min-message' }) + minMessage = 'This field need more items'; + + /** + * This is a maximum amount of selected items in this input. + * @type {number} + * @attr + * @default undefined + */ + @property({ type: Number }) + max?: number; + + /** + * Max validation message. + * @type {boolean} + * @attr + * @default + */ + @property({ type: String, attribute: 'min-message' }) + maxMessage = 'This field exceeds the allowed amount of items'; + + /** + * Disables the input + * @type {boolean} + * @attr + * @default false + */ + @property({ type: Boolean, reflect: true }) + disabled = false; + + /** + * Makes the input readonly + * @type {boolean} + * @attr + * @default false + */ + @property({ type: Boolean, reflect: true }) + readonly = false; + + @property({ type: Boolean }) + showLabels = true; + + constructor() { + super(); + + this.consumeContext(UMB_DATA_TYPE_WORKSPACE_CONTEXT, (instance) => { + const workspace = instance; + this.observe(workspace.data, (data) => { + const property = data?.values.find((setting) => setting.alias === 'useLabel'); + if (property) this.showLabels = property.value as boolean; + }); + }); + + this.addValidator( + 'rangeUnderflow', + () => this.minMessage, + () => !!this.min && this._items.length < this.min, + ); + this.addValidator( + 'rangeOverflow', + () => this.maxMessage, + () => !!this.max && this._items.length > this.max, + ); + } + + @state() + private _items: Array = []; + + @property({ type: Array }) + public get items(): Array { + return this._items; + } + public set items(items: Array) { + this._items = items ?? []; + this.#prevalueSorter.setModel(this.items); + } + + #onAdd() { + this._items = [...this._items, { value: '', label: '' }]; + this.pristine = false; + this.dispatchEvent(new UmbChangeEvent()); + this.#focusNewItem(); + } + + #onChange(event: UmbInputEvent, currentIndex: number) { + event.stopPropagation(); + const target = event.currentTarget as UmbMultipleColorPickerItemInputElement; + const value = target.value as string; + const label = target.label as string; + + this.items = this._items.map((item, index) => (index === currentIndex ? { value, label } : item)); + + this.dispatchEvent(new UmbChangeEvent()); + } + + async #focusNewItem() { + await this.updateComplete; + const items = this.shadowRoot?.querySelectorAll( + 'umb-multiple-color-picker-item-input', + ) as NodeListOf; + const newItem = items[items.length - 1]; + newItem.focus(); + } + + #deleteItem(event: UmbDeleteEvent, itemIndex: number) { + event.stopPropagation(); + this._items = this._items.filter((item, index) => index !== itemIndex); + this.pristine = false; + this.dispatchEvent(new UmbChangeEvent()); + } + + protected getFormElement() { + return undefined; + } + + render() { + return html`
${this.#renderItems()}
+ ${this.#renderAddButton()} `; + } + + #renderItems() { + return html` + ${repeat( + this._items, + (item) => item.value, + (item, index) => + html` this.#onChange(event, index)} + @delete="${(event: UmbDeleteEvent) => this.#deleteItem(event, index)}" + ?disabled=${this.disabled} + ?readonly=${this.readonly} + required + required-message="Item ${index + 1} is missing a value">`, + )} + `; + } + + #renderAddButton() { + return html` + ${this.disabled || this.readonly + ? nothing + : html``} + `; + } + + static styles = [ + css` + #action { + display: block; + } + + .--umb-sorter-placeholder { + position: relative; + visibility: hidden; + } + .--umb-sorter-placeholder::after { + content: ''; + position: absolute; + inset: 0px; + border-radius: var(--uui-border-radius); + border: 1px dashed var(--uui-color-divider-emphasis); + } + `, + ]; +} + +export default UmbMultipleColorPickerInputElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-multiple-color-picker-input': UmbMultipleColorPickerInputElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-color-picker-input/multiple-color-picker-item-input.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-color-picker-input/multiple-color-picker-item-input.element.ts new file mode 100644 index 0000000000..ff24d8c5ae --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-color-picker-input/multiple-color-picker-item-input.element.ts @@ -0,0 +1,231 @@ +import { css, html, nothing, customElement, property, query, ifDefined } from '@umbraco-cms/backoffice/external/lit'; +import { + FormControlMixin, + UUIColorPickerElement, + UUIInputElement, + UUIInputEvent, +} from '@umbraco-cms/backoffice/external/uui'; +import { + UmbModalManagerContext, + UMB_MODAL_MANAGER_CONTEXT_TOKEN, + UMB_CONFIRM_MODAL, +} from '@umbraco-cms/backoffice/modal'; +import { UmbChangeEvent, UmbInputEvent, UmbDeleteEvent } from '@umbraco-cms/backoffice/event'; +import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; + +/** + * @element umb-multiple-color-picker-item-input + */ +@customElement('umb-multiple-color-picker-item-input') +export class UmbMultipleColorPickerItemInputElement extends FormControlMixin(UmbLitElement) { + /** + * Disables the input + * @type {boolean} + * @attr + * @default false + */ + @property({ type: Boolean, reflect: true }) + disabled = false; + + /** + * Disables the input + * @type {boolean} + * @attr + * @default false + */ + @property({ type: Boolean, reflect: true }) + readonly = false; + + @property({ type: String }) + label?: string; + + @query('#input') + protected _input?: UUIInputElement; + + @query('#color') + protected _colorPicker!: UUIColorPickerElement; + + private _modalContext?: UmbModalManagerContext; + + @property({ type: Boolean }) + showLabels = true; + + constructor() { + super(); + + this.consumeContext(UMB_MODAL_MANAGER_CONTEXT_TOKEN, (instance) => { + this._modalContext = instance; + }); + } + + #onDelete() { + const modalContext = this._modalContext?.open(UMB_CONFIRM_MODAL, { + data: { + headline: `${this.localize.term('actions_delete')} ${this.value || ''}`, + content: this.localize.term('content_nestedContentDeleteItem'), + color: 'danger', + confirmLabel: this.localize.term('actions_delete'), + } + }); + + modalContext?.onSubmit().then(() => { + this.dispatchEvent(new UmbDeleteEvent()); + }); + } + + #onLabelInput(event: UUIInputEvent) { + event.stopPropagation(); + this.label = event.target.value as string; + this.dispatchEvent(new UmbInputEvent()); + } + + #onLabelChange(event: UUIInputEvent) { + event.stopPropagation(); + this.label = event.target.value as string; + this.dispatchEvent(new UmbChangeEvent()); + } + + #onValueChange(event: UUIInputEvent) { + event.stopPropagation(); + this.value = event.target.value; + this.dispatchEvent(new UmbChangeEvent()); + } + + #onValueInput(event: UUIInputEvent) { + event.stopPropagation(); + this.value = event.target.value; + this.dispatchEvent(new UmbInputEvent()); + } + + #onColorInput(event: InputEvent) { + event.stopPropagation(); + this.value = this._colorPicker.value; + this.dispatchEvent(new UmbChangeEvent()); + } + + // Prevent valid events from bubbling outside the message element + #onValid(event: any) { + event.stopPropagation(); + } + + // Prevent invalid events from bubbling outside the message element + #onInvalid(event: any) { + event.stopPropagation(); + } + + public async focus() { + await this.updateComplete; + this._input?.focus(); + } + + protected getFormElement() { + return undefined; + } + + #onColorClick() { + this._colorPicker.click(); + } + + render() { + //TODO: Using native input=color element instead of uui-color-picker due to its huge size and bad adaptability as a pop up + return html` + +
+ ${this.disabled || this.readonly ? nothing : html``} +
+ + + + +
+ ${this.showLabels + ? html` ` + : nothing} + ${this.readonly + ? nothing + : html` + + `} +
+
+ `; + } + + static styles = [ + css` + :host { + display: flex; + align-items: center; + margin-bottom: var(--uui-size-space-3); + gap: var(--uui-size-space-3); + } + + #item { + position: relative; + display: flex; + gap: var(--uui-size-1); + align-items: center; + } + uui-input { + flex: 1; + } + + uui-color-swatch { + padding: var(--uui-size-1); + } + + .color-wrapper { + position: relative; + flex: 1; + display: flex; + flex-direction: column; + } + + uui-input, + #validation-message { + flex: 1; + } + + input[type='color'] { + visibility: hidden; + width: 0px; + padding: 0; + margin: 0; + position: absolute; + } + `, + ]; +} + +export default UmbMultipleColorPickerItemInputElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-multiple-color-picker-item-input': UmbMultipleColorPickerItemInputElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/schemas/Umbraco.ColorPicker.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/schemas/Umbraco.ColorPicker.ts index 00af93e6f3..38b40daf26 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/schemas/Umbraco.ColorPicker.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/schemas/Umbraco.ColorPicker.ts @@ -19,7 +19,7 @@ export const manifest: ManifestPropertyEditorSchema = { alias: 'items', label: 'Colors', description: 'Add, remove or sort colors', - propertyEditorUiAlias: 'Umb.PropertyEditorUi.ColorPicker', + propertyEditorUiAlias: 'Umb.PropertyEditorUi.ColorEditor', }, ], }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/color-editor/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/color-editor/manifests.ts new file mode 100644 index 0000000000..922d03fc43 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/color-editor/manifests.ts @@ -0,0 +1,13 @@ +import type { ManifestPropertyEditorUi } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifest: ManifestPropertyEditorUi = { + type: 'propertyEditorUi', + alias: 'Umb.PropertyEditorUi.ColorEditor', + name: 'Color Editor Property Editor UI', + js: () => import('./property-editor-ui-color-editor.element.js'), + meta: { + label: 'Color Editor', + icon: 'icon-page-add', + group: 'Umbraco.DropDown.Flexible', + }, +}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/color-editor/property-editor-ui-color-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/color-editor/property-editor-ui-color-editor.element.ts new file mode 100644 index 0000000000..a1e12b4b8c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/color-editor/property-editor-ui-color-editor.element.ts @@ -0,0 +1,46 @@ +import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; +import { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry'; +import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; +import type { UmbSwatchDetails } from '@umbraco-cms/backoffice/models'; +import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor'; +import { UmbMultipleColorPickerInputElement } from '@umbraco-cms/backoffice/components'; + +/** + * @element umb-property-editor-ui-color-editor + */ +@customElement('umb-property-editor-ui-color-editor') +export class UmbPropertyEditorUIColorEditorElement extends UmbLitElement implements UmbPropertyEditorUiElement { + #defaultShowLabels = true; + + @property({ type: Array }) + value: Array = []; + + @state() + private _showLabels = this.#defaultShowLabels; + + @property({ attribute: false }) + public set config(config: UmbPropertyEditorConfigCollection | undefined) { + this._showLabels = config?.getValueByAlias('useLabel') ?? this.#defaultShowLabels; + this.value = config?.getValueByAlias('items') ?? []; + } + + #onChange(event: CustomEvent) { + this.value = (event.target as UmbMultipleColorPickerInputElement).items; + this.dispatchEvent(new CustomEvent('property-value-change')); + } + + render() { + return html``; + } +} + +export default UmbPropertyEditorUIColorEditorElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-property-editor-ui-color-editor': UmbPropertyEditorUIColorEditorElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/image-crops-configuration/property-editor-ui-image-crops-configuration.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/image-crops-configuration/property-editor-ui-image-crops-configuration.element.ts index 2077022012..bc03872c53 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/image-crops-configuration/property-editor-ui-image-crops-configuration.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/image-crops-configuration/property-editor-ui-image-crops-configuration.element.ts @@ -103,17 +103,17 @@ export class UmbPropertyEditorUIImageCropsConfigurationElement
Alias - +
Width - + px
Height - + px
@@ -130,8 +130,8 @@ export class UmbPropertyEditorUIImageCropsConfigurationElement ${item.alias} (${item.width} x ${item.height}px)
- this.#onEdit(item)}>Edit - this.#onRemove(item.alias)}>Remove + this.#onEdit(item)}>Edit + this.#onRemove(item.alias)}>Remove
`, diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/manifests.ts index d24e002b7e..49f304aaff 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/manifests.ts @@ -1,4 +1,5 @@ import { manifest as colorPicker } from './color-picker/manifests.js'; +import { manifest as colorEditor } from './color-editor/manifests.js'; import { manifest as datePicker } from './date-picker/manifests.js'; import { manifest as eyeDropper } from './eye-dropper/manifests.js'; import { manifest as multiUrlPicker } from './multi-url-picker/manifests.js'; @@ -34,6 +35,7 @@ import type { ManifestPropertyEditorUi } from '@umbraco-cms/backoffice/extension export const manifests: Array = [ colorPicker, + colorEditor, datePicker, eyeDropper, multiUrlPicker, diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/config/max-image-size/property-editor-ui-tiny-mce-maximagesize-configuration.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/config/max-image-size/property-editor-ui-tiny-mce-maximagesize-configuration.element.ts index 86fa1a9750..323f500713 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/config/max-image-size/property-editor-ui-tiny-mce-maximagesize-configuration.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/uis/tiny-mce/config/max-image-size/property-editor-ui-tiny-mce-maximagesize-configuration.element.ts @@ -15,7 +15,7 @@ export class UmbPropertyEditorUITinyMceMaxImageSizeConfigurationElement value: number = 0; render() { - return html``; + return html``; } static styles = [UmbTextStyles]; 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 b9bc23e05d..fa1b7f6662 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 @@ -691,6 +691,7 @@ export class UmbSorterController implements UmbController { if (movingItemIndex !== -1 && movingItemIndex <= movingItemIndex) { newIndex--; } + if (nextEl) { // We had a reference element, we want to get the index of it. // This is might a problem if a item is being moved forward? (was also like this in the AngularJS version...) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/variant/variant-id.class.ts b/src/Umbraco.Web.UI.Client/src/packages/core/variant/variant-id.class.ts index 0bffb824dc..752e8bcf11 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/variant/variant-id.class.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/variant/variant-id.class.ts @@ -39,6 +39,14 @@ export class UmbVariantId { return this.segment || ''; } + public isCultureInvariant(): boolean { + return this.culture === null; + } + + public isSegmentInvariant(): boolean { + return this.segment === null; + } + public isInvariant(): boolean { return this.culture === null && this.segment === null; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/document.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/document.repository.ts index 4e6e9e87a1..68eb162e4d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/document.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/document.repository.ts @@ -219,31 +219,12 @@ export class UmbDocumentRepository return { error }; } - async saveAndPublish(id: string, item: UpdateDocumentRequestModel, variantIds: Array) { - if (!id) throw new Error('id is missing'); - if (!variantIds) throw new Error('variant IDs are missing'); - //await this.#init; - - await this.save(id, item); - - const { error } = await this.#detailDataSource.saveAndPublish(id, variantIds); - - if (!error) { - // TODO: Update other stores based on above effect. - - const notification = { data: { message: `Document saved and published` } }; - this.#notificationContext?.peek('positive', notification); - } - - return { error }; - } - async publish(id: string, variantIds: Array) { if (!id) throw new Error('id is missing'); if (!variantIds) throw new Error('variant IDs are missing'); await this.#init; - const { error } = await this.#detailDataSource.saveAndPublish(id, variantIds); + const { error } = await this.#detailDataSource.publish(id, variantIds); if (!error) { // TODO: Update other stores based on above effect. diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/sources/document.server.data.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/sources/document.server.data.ts index 1f26836858..1f823b5305 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/sources/document.server.data.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/sources/document.server.data.ts @@ -124,12 +124,14 @@ export class UmbDocumentServerDataSource * @return {*} * @memberof UmbDocumentServerDataSource */ - async saveAndPublish(id: string, variantIds: Array) { + async publish(id: string, variantIds: Array) { if (!id) throw new Error('Id is missing'); // TODO: THIS DOES NOT TAKE SEGMENTS INTO ACCOUNT!!!!!! const requestBody: PublishDocumentRequestModel = { - cultures: variantIds.map((variant) => variant.toCultureString()), + cultures: variantIds + .map((variant) => (variant.isCultureInvariant() ? null : variant.toCultureString())) + .filter((x) => x !== null) as Array, }; return tryExecuteAndNotify(this.#host, DocumentResource.putDocumentByIdPublish({ id, requestBody })); diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/actions/save-and-publish.action.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/actions/save-and-publish.action.ts index 91caa2c9ec..0d210cfabc 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/actions/save-and-publish.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/actions/save-and-publish.action.ts @@ -14,6 +14,6 @@ export class UmbDocumentSaveAndPublishWorkspaceAction extends UmbWorkspaceAction //const document = this.workspaceContext.getData(); // TODO: handle errors //if (!document) return; - this.workspaceContext.publish(); + this.workspaceContext.saveAndPublish(); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts index 2d0977fdb4..3e4fdc864e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts @@ -187,9 +187,8 @@ export class UmbDocumentWorkspaceContext } } - async save() { - if (!this.#currentData.value) return; - if (!this.#currentData.value.id) return; + async #createOrSave() { + if (!this.#currentData.value?.id) throw new Error('Id is missing'); if (this.getIsNew()) { // TODO: typescript hack until we get the create type @@ -200,6 +199,10 @@ export class UmbDocumentWorkspaceContext } else { await this.repository.save(this.#currentData.value.id, this.#currentData.value); } + } + + async save() { + await this.#createOrSave(); this.saveComplete(this.getData()); } @@ -211,6 +214,19 @@ export class UmbDocumentWorkspaceContext } } + public async saveAndPublish() { + await this.#createOrSave(); + // TODO: This might be right to publish all, but we need a method that just saves and publishes a declared range of variants. + const currentData = this.#currentData.value; + if (currentData) { + const variantIds = currentData.variants?.map((x) => UmbVariantId.Create(x)); + const id = currentData.id; + if (variantIds && id) { + await this.repository.publish(id, variantIds); + } + } + } + public async publish() { // TODO: This might be right to publish all, but we need a method that just publishes a declared range of variants. const currentData = this.#currentData.value; @@ -223,18 +239,6 @@ export class UmbDocumentWorkspaceContext } } - public async saveAndPublish() { - // TODO: This might be right to publish all, but we need a method that just saves and publishes a declared range of variants. - const currentData = this.#currentData.value; - if (currentData) { - const variantIds = currentData.variants?.map((x) => UmbVariantId.Create(x)); - const id = currentData.id; - if (variantIds && id) { - await this.repository.saveAndPublish(id, currentData, variantIds); - } - } - } - public async unpublish() { // TODO: This might be right to unpublish all, but we need a method that just publishes a declared range of variants. const currentData = this.#currentData.value;