diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 8eb55c55d8..8f052df085 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -41,6 +41,7 @@ "./extension-registry": "./dist-cms/packages/core/extension-registry/index.js", "./icon": "./dist-cms/packages/core/icon-registry/index.js", "./id": "./dist-cms/packages/core/id/index.js", + "./imaging": "./dist-cms/packages/core/imaging/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", diff --git a/src/Umbraco.Web.UI.Client/src/assets/css/umbraco-blockgridlayout-flexbox.css b/src/Umbraco.Web.UI.Client/src/assets/css/umbraco-blockgridlayout-flexbox.css new file mode 100644 index 0000000000..3f921c0470 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/css/umbraco-blockgridlayout-flexbox.css @@ -0,0 +1,35 @@ +/** Example of how a grid layout stylehseet could be done with Flex box: */ + +.umb-block-grid__layout-container { + position: relative; + display: flex; + flex-wrap: wrap; + gap: var(--umb-block-grid--row-gap, 0) var(--umb-block-grid--column-gap, 0); +} +.umb-block-grid__layout-item { + position: relative; + --umb-block-grid__layout-item-calc: calc(var(--umb-block-grid--item-column-span) / var(--umb-block-grid--grid-columns)); + width: calc(var(--umb-block-grid__layout-item-calc) * 100% - (1 - var(--umb-block-grid__layout-item-calc)) * var(--umb-block-grid--column-gap, 0px)); +} + + +.umb-block-grid__area-container, .umb-block-grid__block--view::part(area-container) { + position: relative; + display: flex; + flex-wrap: wrap; + width: 100%; + gap: var(--umb-block-grid--areas-row-gap, 0) var(--umb-block-grid--areas-column-gap, 0); +} +.umb-block-grid__area { + position: relative; + height: 100%; + display: flex; + flex-direction: column; + --umb-block-grid__area-calc: calc(var(--umb-block-grid--area-column-span) / var(--umb-block-grid--area-grid-columns, 1)); + width: calc(var(--umb-block-grid__area-calc) * 100% - (1 - var(--umb-block-grid__area-calc)) * var(--umb-block-grid--areas-column-gap, 0px)); +} + + +.umb-block-grid__actions { + clear: both; +} diff --git a/src/Umbraco.Web.UI.Client/src/assets/css/umbraco-blockgridlayout.css b/src/Umbraco.Web.UI.Client/src/assets/css/umbraco-blockgridlayout.css new file mode 100644 index 0000000000..8a4f567cac --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/assets/css/umbraco-blockgridlayout.css @@ -0,0 +1,46 @@ +.umb-block-grid__layout-container { + position: relative; + display: grid; + grid-template-columns: repeat(var(--umb-block-grid--grid-columns, 1), minmax(0, 1fr)); + grid-auto-flow: row; + grid-auto-rows: minmax(50px, min-content); + + column-gap: var(--umb-block-grid--column-gap, 0); + row-gap: var(--umb-block-grid--row-gap, 0); +} +.umb-block-grid__layout-item { + position: relative; + /* For small devices we scale columnSpan by three, to make everything bigger than 1/3 take full width: */ + grid-column-end: span min(calc(var(--umb-block-grid--item-column-span, 1) * 3), var(--umb-block-grid--grid-columns)); + grid-row: span var(--umb-block-grid--item-row-span, 1); +} + + +.umb-block-grid__area-container, .umb-block-grid__block--view::part(area-container) { + position: relative; + display: grid; + grid-template-columns: repeat(var(--umb-block-grid--area-grid-columns, var(--umb-block-grid--grid-columns, 1)), minmax(0, 1fr)); + grid-auto-flow: row; + grid-auto-rows: minmax(50px, min-content); + + column-gap: var(--umb-block-grid--areas-column-gap, 0); + row-gap: var(--umb-block-grid--areas-row-gap, 0); +} +.umb-block-grid__area { + position: relative; + height: 100%; + display: flex; + flex-direction: column; + /* For small devices we scale columnSpan by three, to make everything bigger than 1/3 take full width: */ + grid-column-end: span min(calc(var(--umb-block-grid--area-column-span, 1) * 3), var(--umb-block-grid--area-grid-columns)); + grid-row: span var(--umb-block-grid--area-row-span, 1); +} + +@media (min-width:1024px) { + .umb-block-grid__layout-item { + grid-column-end: span min(var(--umb-block-grid--item-column-span, 1), var(--umb-block-grid--grid-columns)); + } + .umb-block-grid__area { + grid-column-end: span min(var(--umb-block-grid--area-column-span, 1), var(--umb-block-grid--area-grid-columns)); + } +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models.ts b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models.ts index 554f627a7c..18dc20cc0f 100644 --- a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models.ts +++ b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models.ts @@ -2583,6 +2583,12 @@ value: string key: string }; +export type UserExternalLoginProviderModel = { + providerSchemeName: string +isLinkedOnUser: boolean +hasManualLinkingEnabled: boolean + }; + export type UserGroupItemResponseModel = { id: string name: string @@ -5234,6 +5240,7 @@ PostUserUnlock: { ,PostUserCurrentAvatar: string ,PostUserCurrentChangePassword: string ,GetUserCurrentConfiguration: CurrenUserConfigurationResponseModel + ,GetUserCurrentLoginProviders: Array ,GetUserCurrentLogins: LinkedLoginsRequestModel ,GetUserCurrentPermissions: UserPermissionsResponseModel ,GetUserCurrentPermissionsDocument: Array diff --git a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/services.ts b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/services.ts index 797ead124c..c634a46642 100644 --- a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/services.ts +++ b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/services.ts @@ -8686,6 +8686,21 @@ requestBody }); } + /** + * @returns unknown Success + * @throws ApiError + */ + public static getUserCurrentLoginProviders(): CancelablePromise { + + return __request(OpenAPI, { + method: 'GET', + url: '/umbraco/management/api/v1/user/current/login-providers', + errors: { + 401: `The resource is protected and requires an authentication token`, + }, + }); + } + /** * @returns unknown Success * @throws ApiError diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/Umbraco.BlockGrid.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/Umbraco.BlockGrid.ts index b6d7ac4518..4c9e5eb5df 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/Umbraco.BlockGrid.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/Umbraco.BlockGrid.ts @@ -19,6 +19,7 @@ export const manifest: ManifestPropertyEditorSchema = { label: 'Amount', propertyEditorUiAlias: 'Umb.PropertyEditorUi.NumberRange', config: [{ alias: 'validationRange', value: { min: 0, max: Infinity } }], + weight: 100, }, ], defaultData: [ diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/manifests.ts index 26879650f8..7ce77f5c5f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/manifests.ts @@ -20,6 +20,7 @@ export const manifests: Array = [ alias: 'blockGroups', label: '', propertyEditorUiAlias: 'Umb.PropertyEditorUi.BlockTypeGroupConfiguration', + weight: 1, }, { alias: 'useLiveEditing', @@ -42,9 +43,12 @@ export const manifests: Array = [ { alias: 'gridColumns', label: 'Grid Columns', - description: 'Set the number of columns for the layout. (defaults to 12)', + description: 'Set the number of columns for the layout.', propertyEditorUiAlias: 'Umb.PropertyEditorUi.Integer', - config: [{ alias: 'min', value: 0 }], + config: [ + { alias: 'min', value: 0 }, + { alias: 'placeholder', value: '12' }, + ], }, { alias: 'layoutStylesheet', diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-layout-stylesheet/property-editor-ui-block-grid-layout-stylesheet.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-layout-stylesheet/property-editor-ui-block-grid-layout-stylesheet.element.ts index 9cd3218e1c..0b3351a30e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-layout-stylesheet/property-editor-ui-block-grid-layout-stylesheet.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-layout-stylesheet/property-editor-ui-block-grid-layout-stylesheet.element.ts @@ -44,7 +44,7 @@ export class UmbPropertyEditorUIBlockGridLayoutStylesheetElement .min=${0} .max=${1}>
- Link to default layout stylesheet + Link to default layout stylesheet `; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/workspace/views/block-grid-type-workspace-view-areas.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/workspace/views/block-grid-type-workspace-view-areas.element.ts index e2b39bb072..025fd3e564 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/workspace/views/block-grid-type-workspace-view-areas.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/workspace/views/block-grid-type-workspace-view-areas.element.ts @@ -1,5 +1,5 @@ import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit'; +import { css, html, customElement, state, nothing } from '@umbraco-cms/backoffice/external/lit'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import type { UmbWorkspaceViewElement } from '@umbraco-cms/backoffice/extension-registry'; import type { UmbPropertyEditorConfig } from '@umbraco-cms/backoffice/property-editor'; @@ -8,6 +8,9 @@ import { UMB_DATA_TYPE_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/data-ty @customElement('umb-block-grid-type-workspace-view-areas') export class UmbBlockGridTypeWorkspaceViewAreasElement extends UmbLitElement implements UmbWorkspaceViewElement { // + @state() + _areaColumnsConfigurationObject?: UmbPropertyEditorConfig; + @state() _areaConfigConfigurationObject?: UmbPropertyEditorConfig; @@ -18,7 +21,8 @@ export class UmbBlockGridTypeWorkspaceViewAreasElement extends UmbLitElement imp this.observe( await context.propertyValueByAlias('gridColumns'), (value) => { - const dataTypeGridColumns = value ? parseInt(value, 10) : undefined; + const dataTypeGridColumns = value ? parseInt(value, 10) : 12; + this._areaColumnsConfigurationObject = [{ alias: 'placeholder', value: dataTypeGridColumns }]; this._areaConfigConfigurationObject = [{ alias: 'defaultAreaGridColumns', value: dataTypeGridColumns }]; }, 'observeGridColumns', @@ -27,21 +31,24 @@ export class UmbBlockGridTypeWorkspaceViewAreasElement extends UmbLitElement imp } render() { - return html` - - - > - - `; + return this._areaConfigConfigurationObject + ? html` + + + > + + ` + : nothing; } static styles = [ diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/property-editor-ui-block-list.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/property-editor-ui-block-list.element.ts index 7c8a9a9297..37270913ec 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/property-editor-ui-block-list.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/property-editor-ui-block-list.element.ts @@ -82,20 +82,23 @@ export class UmbPropertyEditorUIBlockListElement extends UmbLitElement implement const blocks = config.getValueByAlias>('blocks') ?? []; this.#managerContext.setBlockTypes(blocks); + const useInlineEditingAsDefault = config.getValueByAlias('useInlineEditingAsDefault'); + this.#managerContext.setInlineEditingMode(useInlineEditingAsDefault); + this.style.maxWidth = config.getValueByAlias('maxPropertyWidth') ?? ''; + // TODO: + //config.useSingleBlockMode, not done jet + + this.#managerContext.setEditorConfiguration(config); + const customCreateButtonLabel = config.getValueByAlias('createLabel'); if (customCreateButtonLabel) { this._createButtonLabel = customCreateButtonLabel; } else if (blocks.length === 1) { - this._createButtonLabel = `${this.localize.term('general_add')} ${blocks[0].label}`; + this.#managerContext.contentTypesLoaded.then(() => { + const firstContentTypeName = this.#managerContext.getContentTypeNameOf(blocks[0].contentElementTypeKey); + this._createButtonLabel = `${this.localize.term('general_add')} ${firstContentTypeName}`; + }); } - - const useInlineEditingAsDefault = config.getValueByAlias('useInlineEditingAsDefault'); - this.#managerContext.setInlineEditingMode(useInlineEditingAsDefault); - // TODO: - //config.useSingleBlockMode, not done jet - this.style.maxWidth = config.getValueByAlias('maxPropertyWidth') ?? ''; - - this.#managerContext.setEditorConfiguration(config); } @state() diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/block-type-card/block-type-card.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/block-type-card/block-type-card.element.ts index da664eabd4..9ede814398 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/block-type-card/block-type-card.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/block-type-card/block-type-card.element.ts @@ -18,6 +18,9 @@ export class UmbBlockTypeCardElement extends UmbLitElement { @property({ type: String, attribute: false }) href?: string; + @property({ type: String, attribute: false }) + iconFile?: string; + @property({ type: String, attribute: false }) iconColor?: string; @@ -41,7 +44,7 @@ export class UmbBlockTypeCardElement extends UmbLitElement { private _elementTypeKey?: string | undefined; @state() - _fallbackName?: string; + _name?: string; @state() _fallbackIcon?: string | null; @@ -53,7 +56,7 @@ export class UmbBlockTypeCardElement extends UmbLitElement { const item = items[0]; if (item) { this._fallbackIcon = item.icon; - this._fallbackName = item.name; + this._name = item.name; } }); } @@ -63,9 +66,11 @@ export class UmbBlockTypeCardElement extends UmbLitElement { return html` - + ${this.iconFile + ? html`` + : html``} `; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/input-block-type/input-block-type.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/input-block-type/input-block-type.element.ts index ef77263298..521dcc51b0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/input-block-type/input-block-type.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/input-block-type/input-block-type.element.ts @@ -7,7 +7,10 @@ import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import type { UmbPropertyDatasetContext } from '@umbraco-cms/backoffice/property'; import { UMB_PROPERTY_DATASET_CONTEXT } from '@umbraco-cms/backoffice/property'; import { UmbDeleteEvent } from '@umbraco-cms/backoffice/event'; -import { UMB_DOCUMENT_TYPE_PICKER_MODAL } from '@umbraco-cms/backoffice/document-type'; +import { + UMB_DOCUMENT_TYPE_ITEM_STORE_CONTEXT, + UMB_DOCUMENT_TYPE_PICKER_MODAL, +} from '@umbraco-cms/backoffice/document-type'; import { UmbSorterController } from '@umbraco-cms/backoffice/sorter'; /** TODO: Look into sending a "change" event when there is a change, rather than create, delete, and change event. Make sure it doesn't break move for RTE/List/Grid. [LI] */ @@ -123,10 +126,13 @@ export class UmbInputBlockTypeElement< } async #onRequestDelete(item: BlockType) { + const store = await this.getContext(UMB_DOCUMENT_TYPE_ITEM_STORE_CONTEXT); + const contentType = store.getItems([item.contentElementTypeKey]); await umbConfirmModal(this, { color: 'danger', - headline: `Remove [TODO: Get name]?`, - content: 'Are you sure you want to remove this block type?', + headline: `Remove ${contentType[0]?.name}?`, + // TODO: Translations: [NL] + content: 'Are you sure you want to remove this Block Type Configuration?', confirmLabel: 'Remove', }); this.deleteItem(item.contentElementTypeKey); @@ -143,6 +149,7 @@ export class UmbInputBlockTypeElement< extends UmbContextBase { // + get contentTypesLoaded() { + return Promise.all(this.#contentTypeRequests); + } + #contentTypeRequests: Array> = []; #contentTypeRepository = new UmbDocumentTypeDetailRepository(this); #propertyAlias = new UmbStringState(undefined); @@ -115,7 +119,9 @@ export abstract class UmbBlockManagerContext< async #ensureContentType(unique: string) { if (this.#contentTypes.getValue().find((x) => x.unique === unique)) return; - const { data } = await this.#contentTypeRepository.requestByUnique(unique); + const contentTypeRequest = this.#contentTypeRepository.requestByUnique(unique); + this.#contentTypeRequests.push(contentTypeRequest); + const { data } = await contentTypeRequest; if (!data) { this.#contentTypes.removeOne(unique); return; @@ -132,6 +138,9 @@ export abstract class UmbBlockManagerContext< contentTypeNameOf(contentTypeKey: string) { return this.#contentTypes.asObservablePart((source) => source.find((x) => x.unique === contentTypeKey)?.name); } + getContentTypeNameOf(contentTypeKey: string) { + return this.#contentTypes.getValue().find((x) => x.unique === contentTypeKey)?.name; + } blockTypeOf(contentTypeKey: string) { return this.#blockTypes.asObservablePart((source) => source.find((x) => x.contentElementTypeKey === contentTypeKey), diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/property-editor.model.ts b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/property-editor.model.ts index 9dd8113023..4d88b28827 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/property-editor.model.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/property-editor.model.ts @@ -51,6 +51,7 @@ export interface PropertyEditorSettingsProperty { alias: string; propertyEditorUiAlias: string; config?: UmbPropertyEditorConfig; + weight?: number; } export interface PropertyEditorSettingsDefaultData { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/imaging/imaging.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/core/imaging/imaging.repository.ts new file mode 100644 index 0000000000..a675e2ff5e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/imaging/imaging.repository.ts @@ -0,0 +1,28 @@ +import type { UmbImagingModel } from './types.js'; +import { UmbImagingServerDataSource } from './imaging.server.data.js'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; +import type { UmbApi } from '@umbraco-cms/backoffice/extension-api'; + +export class UmbImagingRepository extends UmbControllerBase implements UmbApi { + #itemSource: UmbImagingServerDataSource; + + constructor(host: UmbControllerHost) { + super(host); + this.#itemSource = new UmbImagingServerDataSource(host); + } + + /** + * Requests the items for the given uniques + * @param {Array} uniques + * @return {*} + * @memberof UmbImagingRepository + */ + async requestResizedItems(uniques: Array, imagingModel?: UmbImagingModel) { + if (!uniques.length) throw new Error('Uniques are missing'); + + const { data, error: _error } = await this.#itemSource.getItems(uniques, imagingModel); + const error: any = _error; + return { data, error }; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/imaging/imaging.server.data.ts b/src/Umbraco.Web.UI.Client/src/packages/core/imaging/imaging.server.data.ts new file mode 100644 index 0000000000..e64b32c34d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/imaging/imaging.server.data.ts @@ -0,0 +1,53 @@ +import type { UmbImagingModel } from './types.js'; +import { ImagingService, type MediaUrlInfoResponseModel } from '@umbraco-cms/backoffice/external/backend-api'; +import type { UmbMediaUrlModel } from '@umbraco-cms/backoffice/media'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; + +/** + * A data source for the Imaging Service that resizes a media item from the server + * @export + * @class UmbImagingServerDataSource + * @implements {RepositoryDetailDataSource} + */ +export class UmbImagingServerDataSource { + #host: UmbControllerHost; + + /** + * Creates an instance of UmbImagingServerDataSource. + * @param {UmbControllerHost} host + * @memberof UmbImagingServerDataSource + */ + constructor(host: UmbControllerHost) { + this.#host = host; + } + + /** + * Fetches the URL for the given media items as resized images + * @param {string} unique + * @memberof UmbImagingServerDataSource + */ + async getItems(uniques: Array, imagingModel?: UmbImagingModel) { + if (!uniques.length) throw new Error('Uniques are missing'); + + const { data, error } = await tryExecuteAndNotify( + this.#host, + ImagingService.getImagingResizeUrls({ id: uniques, ...imagingModel }), + ); + + if (data) { + const items = data.map((item) => this.#mapper(item)); + return { data: items }; + } + + return { error }; + } + + #mapper(item: MediaUrlInfoResponseModel): UmbMediaUrlModel { + const url = item.urlInfos[0]?.url; + return { + unique: item.id, + url: url, + }; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/imaging/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/imaging/index.ts new file mode 100644 index 0000000000..e775891ff3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/imaging/index.ts @@ -0,0 +1,2 @@ +export { UmbImagingRepository } from './imaging.repository.js'; +export { UMB_IMAGING_REPOSITORY_ALIAS } from './manifests.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/imaging/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/imaging/manifests.ts new file mode 100644 index 0000000000..323441b6da --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/imaging/manifests.ts @@ -0,0 +1,13 @@ +import { UmbImagingRepository } from './imaging.repository.js'; +import type { ManifestRepository, ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; + +export const UMB_IMAGING_REPOSITORY_ALIAS = 'Umb.Repository.Imaging'; + +const repository: ManifestRepository = { + type: 'repository', + alias: UMB_IMAGING_REPOSITORY_ALIAS, + name: 'Imaging Repository', + api: UmbImagingRepository, +}; + +export const manifests: Array = [repository]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/imaging/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/imaging/types.ts new file mode 100644 index 0000000000..41df2f5b95 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/imaging/types.ts @@ -0,0 +1,7 @@ +import type { ImageCropModeModel } from '@umbraco-cms/backoffice/external/backend-api'; + +export interface UmbImagingModel { + height?: number; + width?: number; + mode?: ImageCropModeModel; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/manifests.ts index 460f7acc8d..22d8b9f67f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/manifests.ts @@ -7,6 +7,7 @@ import { manifests as debugManifests } from './debug/manifests.js'; import { manifests as entityActionManifests } from './entity-action/manifests.js'; import { manifests as extensionManifests } from './extension-registry/manifests.js'; import { manifests as iconRegistryManifests } from './icon-registry/manifests.js'; +import { manifests as imagingManifests } from './imaging/manifests.js'; import { manifests as localizationManifests } from './localization/manifests.js'; import { manifests as modalManifests } from './modal/common/manifests.js'; import { manifests as propertyActionManifests } from './property-action/manifests.js'; @@ -24,6 +25,7 @@ export const manifests: Array = [ ...authManifests, ...extensionManifests, ...iconRegistryManifests, + ...imagingManifests, ...cultureManifests, ...localizationManifests, ...themeManifests, diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/store/store-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/store/store-base.ts index d333c1f4a2..1d51ad902a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/store/store-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/store/store-base.ts @@ -74,7 +74,7 @@ export class UmbStoreBase extends UmbContextBase imple * @returns {Array} * @memberof UmbStoreBase */ - getItems(uniques: Array) { + getItems(uniques: Array): Array { return this._data.getValue().filter((item) => uniques.includes(this._data.getUniqueMethod(item) as string)); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/data-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/data-type-workspace.context.ts index 0808e07aff..d95b3b83cf 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/data-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/data-type-workspace.context.ts @@ -58,7 +58,9 @@ export class UmbDataTypeWorkspaceContext readonly propertyEditorUiAlias = this.#currentData.asObservablePart((data) => data?.editorUiAlias); readonly propertyEditorSchemaAlias = this.#currentData.asObservablePart((data) => data?.editorAlias); - #properties = new UmbArrayState([], (x) => x.alias); + #properties = new UmbArrayState([], (x) => x.alias).sortBy( + (a, b) => (a.weight || 0) - (b.weight || 0), + ); readonly properties = this.#properties.asObservable(); #defaults = new UmbArrayState([], (entry) => entry.alias); @@ -164,7 +166,11 @@ export class UmbDataTypeWorkspaceContext return this.observe( umbExtensionsRegistry.byTypeAndAlias('propertyEditorSchema', propertyEditorSchemaAlias), (manifest) => { - this.#propertyEditorSchemaSettingsProperties = manifest?.meta.settings?.properties || []; + // Maps properties to have a weight, so they can be sorted + this.#propertyEditorSchemaSettingsProperties = (manifest?.meta.settings?.properties ?? []).map((x, i) => ({ + ...x, + weight: x.weight ?? i, + })); this.#propertyEditorSchemaSettingsDefaultData = manifest?.meta.settings?.defaultData || []; this.#propertyEditorSchemaConfigDefaultUIAlias = manifest?.meta.defaultPropertyEditorUiAlias || null; }, @@ -180,7 +186,11 @@ export class UmbDataTypeWorkspaceContext this.#propertyEditorUiName.setValue(manifest?.name || null); this.#propertyEditorUISettingsSchemaAlias = manifest?.meta.propertyEditorSchemaAlias; - this.#propertyEditorUISettingsProperties = manifest?.meta.settings?.properties || []; + // Maps properties to have a weight, so they can be sorted, notice UI properties have a +1000 weight compared to schema properties. + this.#propertyEditorUISettingsProperties = (manifest?.meta.settings?.properties ?? []).map((x, i) => ({ + ...x, + weight: x.weight ?? 1000 + i, + })); this.#propertyEditorUISettingsDefaultData = manifest?.meta.settings?.defaultData || []; }, 'editorUi', diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/item/document-type-item-store.context-token.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/item/document-type-item-store.context-token.ts new file mode 100644 index 0000000000..8a07283038 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/item/document-type-item-store.context-token.ts @@ -0,0 +1,6 @@ +import type { UmbDocumentTypeItemStore } from './document-type-item.store.js'; +import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; + +export const UMB_DOCUMENT_TYPE_ITEM_STORE_CONTEXT = new UmbContextToken( + 'UmbDocumentTypeItemStore', +); diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/item/document-type-item.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/item/document-type-item.repository.ts index c8a5d0fa7e..80362b89ee 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/item/document-type-item.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/item/document-type-item.repository.ts @@ -1,6 +1,6 @@ import type { UmbDocumentTypeItemModel } from './types.js'; import { UmbDocumentTypeItemServerDataSource } from './document-type-item.server.data-source.js'; -import { UMB_DOCUMENT_TYPE_ITEM_STORE_CONTEXT } from './document-type-item.store.js'; +import { UMB_DOCUMENT_TYPE_ITEM_STORE_CONTEXT } from './document-type-item-store.context-token.js'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbItemRepositoryBase } from '@umbraco-cms/backoffice/repository'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/item/document-type-item.store.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/item/document-type-item.store.ts index 7c9bae5794..3362035543 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/item/document-type-item.store.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/item/document-type-item.store.ts @@ -1,5 +1,5 @@ import type { UmbDocumentTypeItemModel } from './types.js'; -import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; +import { UMB_DOCUMENT_TYPE_ITEM_STORE_CONTEXT } from './document-type-item-store.context-token.js'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbItemStoreBase } from '@umbraco-cms/backoffice/store'; @@ -20,7 +20,3 @@ export class UmbDocumentTypeItemStore extends UmbItemStoreBase( - 'UmbDocumentTypeItemStore', -); diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/item/index.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/item/index.ts index 2ff626ece5..635759aa63 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/item/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/item/index.ts @@ -1,3 +1,4 @@ -export { UmbDocumentTypeItemRepository } from './document-type-item.repository.js'; export { UMB_DOCUMENT_TYPE_ITEM_REPOSITORY_ALIAS, UMB_DOCUMENT_TYPE_ITEM_STORE_ALIAS } from './manifests.js'; +export { UmbDocumentTypeItemRepository } from './document-type-item.repository.js'; +export * from './document-type-item-store.context-token.js'; export * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/media-collection.context.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/media-collection.context.ts index bcf2f7ffe2..ee33b64eea 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/media-collection.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/media-collection.context.ts @@ -1,5 +1,7 @@ +import { UmbImagingRepository } from '@umbraco-cms/backoffice/imaging'; import type { UmbMediaCollectionFilterModel, UmbMediaCollectionItemModel } from './types.js'; import { UMB_MEDIA_GRID_COLLECTION_VIEW_ALIAS } from './views/index.js'; +import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api'; import { UmbDefaultCollectionContext } from '@umbraco-cms/backoffice/collection'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; @@ -7,7 +9,26 @@ export class UmbMediaCollectionContext extends UmbDefaultCollectionContext< UmbMediaCollectionItemModel, UmbMediaCollectionFilterModel > { + #imagingRepository: UmbImagingRepository; + + #thumbnailItems = new UmbArrayState([], (x) => x); + public readonly thumbnailItems = this.#thumbnailItems.asObservable(); + constructor(host: UmbControllerHost) { super(host, UMB_MEDIA_GRID_COLLECTION_VIEW_ALIAS); + this.#imagingRepository = new UmbImagingRepository(host); + + this.observe(this.items, async (items) => { + if (!items?.length) return; + + const { data } = await this.#imagingRepository.requestResizedItems(items.map((m) => m.unique)); + + this.#thumbnailItems.setValue( + items.map((item) => { + const thumbnail = data?.find((m) => m.unique === item.unique)?.url; + return { ...item, url: thumbnail }; + }), + ); + }); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/types.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/types.ts index 729e57f8b2..d12a162972 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/types.ts @@ -20,10 +20,10 @@ export interface UmbMediaCollectionItemModel { updateDate: Date; updater?: string | null; values: Array<{ alias: string; value: string }>; + url?: string; } export interface UmbEditableMediaCollectionItemModel { item: UmbMediaCollectionItemModel; editPath: string; } - diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/grid/media-grid-collection-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/grid/media-grid-collection-view.element.ts index 43201b8839..d8e8f2d220 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/grid/media-grid-collection-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/grid/media-grid-collection-view.element.ts @@ -26,7 +26,7 @@ export class UmbMediaGridCollectionViewElement extends UmbLitElement { constructor() { super(); this.consumeContext(UMB_COLLECTION_CONTEXT, (collectionContext) => { - this.#collectionContext = collectionContext; + this.#collectionContext = collectionContext as UmbMediaCollectionContext; this.#observeCollectionContext(); }); @@ -51,7 +51,7 @@ export class UmbMediaGridCollectionViewElement extends UmbLitElement { this.observe(this.#collectionContext.loading, (loading) => (this._loading = loading), '_observeLoading'); - this.observe(this.#collectionContext.items, (items) => (this._items = items), '_observeItems'); + this.observe(this.#collectionContext.thumbnailItems, (items) => (this._items = items), '_observeItems'); this.observe( this.#collectionContext.selection.selection, @@ -128,6 +128,7 @@ export class UmbMediaGridCollectionViewElement extends UmbLitElement { @deselected=${() => this.#onDeselect(item)} class="media-item" file-ext="${item.icon}"> + ${item.url ? html`${item.name}` : html``} @@ -151,9 +152,12 @@ export class UmbMediaGridCollectionViewElement extends UmbLitElement { #media-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); - grid-template-rows: repeat(auto-fill, 200px); + grid-auto-rows: 200px; gap: var(--uui-size-space-5); } + umb-icon { + font-size: var(--uui-size-24); + } `, ]; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/table/media-table-collection-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/table/media-table-collection-view.element.ts index 86d2451247..559c9f62df 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/table/media-table-collection-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/table/media-table-collection-view.element.ts @@ -253,7 +253,6 @@ export class UmbMediaTableCollectionViewElement extends UmbLitElement { box-sizing: border-box; height: auto; width: 100%; - padding: var(--uui-size-space-3) var(--uui-size-space-6); } .container { diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/media-picker/components/media-picker-folder-path.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/media-picker/components/media-picker-folder-path.element.ts index cb03f97e42..838c7b5f5f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/media-picker/components/media-picker-folder-path.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/media-picker/components/media-picker-folder-path.element.ts @@ -2,24 +2,23 @@ import type { UmbMediaPathModel } from '../types.js'; import type { UmbMediaDetailModel } from '../../../types.js'; import { UmbMediaDetailRepository } from '../../../repository/index.js'; import { UmbMediaTreeRepository } from '../../../tree/index.js'; -import { UMB_MEDIA_ROOT_ENTITY_TYPE } from '../../../entity.js'; +import { UMB_MEDIA_ENTITY_TYPE, UMB_MEDIA_ROOT_ENTITY_TYPE } from '../../../entity.js'; import { css, html, customElement, state, repeat, property } from '@umbraco-cms/backoffice/external/lit'; import type { UUIInputElement, UUIInputEvent } from '@umbraco-cms/backoffice/external/uui'; import { UmbId } from '@umbraco-cms/backoffice/id'; import { getUmbracoFolderUnique } from '@umbraco-cms/backoffice/media-type'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; -// TODO: get root from tree repository -const root = { name: 'Media', unique: null, entityType: UMB_MEDIA_ROOT_ENTITY_TYPE }; +const root: UmbMediaPathModel = { name: 'Media', unique: null, entityType: UMB_MEDIA_ROOT_ENTITY_TYPE }; @customElement('umb-media-picker-folder-path') export class UmbMediaPickerFolderPathElement extends UmbLitElement { #mediaTreeRepository = new UmbMediaTreeRepository(this); // used to get file structure #mediaDetailRepository = new UmbMediaDetailRepository(this); // used to create folders - @property({ type: Object, attribute: false }) - public set currentMedia(value: UmbEntityModel | undefined) { + @property({ attribute: false }) + public set currentMedia(value: UmbMediaPathModel) { if (value !== this._currentMedia) { this._currentMedia = value; this.#loadPath(); @@ -31,7 +30,7 @@ export class UmbMediaPickerFolderPathElement extends UmbLitElement { } @state() - private _currentMedia: UmbEntityModel | undefined; + private _currentMedia: UmbMediaPathModel = root; @state() private _paths: Array = [root]; @@ -45,32 +44,30 @@ export class UmbMediaPickerFolderPathElement extends UmbLitElement { } async #loadPath() { - const unique = this._currentMedia?.unique; - const entityType = this._currentMedia?.entityType; + const unique = this._currentMedia.unique; - if (unique && entityType) { - const { data } = await this.#mediaTreeRepository.requestTreeItemAncestors({ - treeItem: { - unique, - entityType, - }, - }); + const items = unique + ? ( + await this.#mediaTreeRepository.requestTreeItemAncestors({ + treeItem: { unique, entityType: UMB_MEDIA_ENTITY_TYPE }, + }) + ).data + : undefined; - if (data) { - this._paths = [ - root, - ...data.map((item) => ({ name: item.name, unique: item.unique, entityType: item.entityType })), - ]; - return; - } + if (items) { + this._paths = [ + root, + ...items.map((item) => ({ name: item.name, unique: item.unique, entityType: item.entityType })), + ]; + return; } - this._paths = [root]; } - #goToFolder(entity: UmbEntityModel) { + #goToFolder(entity: UmbMediaPathModel) { this._paths = [...this._paths].slice(0, this._paths.findIndex((path) => path.unique === entity.unique) + 1); this.currentMedia = entity; + this.dispatchEvent(new UmbChangeEvent()); } #focusFolderInput() { @@ -84,6 +81,7 @@ export class UmbMediaPickerFolderPathElement extends UmbLitElement { async #addFolder(e: UUIInputEvent) { e.stopPropagation(); + const newName = e.target.value as string; this._typingNewFolder = false; if (!newName) return; @@ -118,7 +116,8 @@ export class UmbMediaPickerFolderPathElement extends UmbLitElement { const entityType = data.entityType; this._paths = [...this._paths, { name, unique, entityType }]; - this.currentMedia = { unique, entityType }; + this.currentMedia = { name, unique, entityType }; + this.dispatchEvent(new UmbChangeEvent()); } render() { @@ -130,8 +129,8 @@ export class UmbMediaPickerFolderPathElement extends UmbLitElement { html` this.#goToFolder({ unique: path.unique, entityType: path.entityType })}> this.#goToFolder(path)}>/`, )}${this._typingNewFolder ? html` { #mediaTreeRepository = new UmbMediaTreeRepository(this); // used to get file structure #mediaUrlRepository = new UmbMediaUrlRepository(this); // used to get urls - #mediaItemRepository = new UmbMediaItemRepository(this); // used to search & get media type of current path + #mediaItemRepository = new UmbMediaItemRepository(this); // used to search + #imagingRepository = new UmbImagingRepository(this); // used to get image renditions #mediaItemsCurrentFolder: Array = []; @@ -27,7 +30,8 @@ export class UmbMediaPickerModalElement extends UmbModalBaseElement { super.connectedCallback(); @@ -35,7 +39,7 @@ export class UmbMediaPickerModalElement extends UmbModalBaseElement): Promise> { if (!items.length) return []; - const { data } = await this.#mediaUrlRepository.requestItems(items.map((item) => item.unique)); + const { data } = await this.#imagingRepository.requestResizedItems(items.map((item) => item.unique)); return items.map((item): UmbMediaCardItemModel => { const url = data?.find((media) => media.unique === item.unique)?.url; - const extension = url?.split('.').pop(); - //TODO Eventually we will get a renderable img from the server. Use this for the url. [LI] - return { name: item.name, unique: item.unique, url, extension, entityType: item.entityType }; + return { name: item.name, unique: item.unique, url, icon: item.mediaType.icon, entityType: item.entityType }; }); } #onOpen(item: UmbMediaCardItemModel) { this._currentMediaEntity = { + name: item.name, unique: item.unique, entityType: UMB_MEDIA_ROOT_ENTITY_TYPE, }; @@ -189,9 +192,10 @@ export class UmbMediaPickerModalElement extends UmbModalBaseElement this.#onSelected(item)} @deselected=${() => this.#onDeselected(item)} ?selected=${this.value?.selection?.find((value) => value === item.unique)} - selectable - file-ext=${ifDefined(item.extension)}> - ${item.url ? html`${ifDefined(item.name)}` : ''} + selectable> + ${item.url + ? html`${ifDefined(item.name)}` + : html``} `; } @@ -199,7 +203,7 @@ export class UmbMediaPickerModalElement extends UmbModalBaseElement`; } @@ -227,10 +231,23 @@ export class UmbMediaPickerModalElement extends UmbModalBaseElement'); + background-size: 10px 10px; + background-repeat: repeat; + } + + #actions { + max-width: 100%; + } `, ]; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/media-picker/types.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/media-picker/types.ts index 434bd4c12a..768968bff8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/media-picker/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/modals/media-picker/types.ts @@ -6,7 +6,7 @@ export interface UmbMediaCardItemModel { unique: string; entityType: UmbMediaEntityType; url?: string; - extension?: string; + icon?: string; } export interface UmbMediaPathModel extends UmbEntityModel { diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/index.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/index.ts index 30dfbb63e1..cc92e27b5f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/index.ts @@ -2,4 +2,5 @@ export { UmbMediaDetailRepository, UMB_MEDIA_DETAIL_REPOSITORY_ALIAS } from './d export { UmbMediaItemRepository, UMB_MEDIA_ITEM_REPOSITORY_ALIAS } from './item/index.js'; export { UmbMediaUrlRepository, UMB_MEDIA_URL_REPOSITORY_ALIAS } from './url/index.js'; +export type { UmbMediaUrlModel } from './url/types.js'; export type { UmbMediaItemModel } from './item/types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/number/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/number/manifests.ts index e64c47d910..a93dbdbdc3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/number/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/number/manifests.ts @@ -14,7 +14,14 @@ export const manifests: Array = [ icon: 'icon-autofill', group: 'common', settings: { - properties: [], + properties: [ + { + alias: 'placeholder', + label: 'Placeholder text', + description: 'Enter the text to be displayed when the value is empty', + propertyEditorUiAlias: 'Umb.PropertyEditorUi.TextBox', + }, + ], defaultData: [ { alias: 'step', diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/number/property-editor-ui-number.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/number/property-editor-ui-number.element.ts index f4317acc81..e95cd74ca5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/number/property-editor-ui-number.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/number/property-editor-ui-number.element.ts @@ -18,11 +18,15 @@ export class UmbPropertyEditorUINumberElement extends UmbLitElement implements U @state() private _step?: number; + @state() + private _placeholder?: string; + public set config(config: UmbPropertyEditorConfigCollection | undefined) { if (!config) return; this._min = this.#parseInt(config.getValueByAlias('min')); this._max = this.#parseInt(config.getValueByAlias('max')); this._step = this.#parseInt(config.getValueByAlias('step')); + this._placeholder = config.getValueByAlias('placeholder'); } #parseInt(input: unknown): number | undefined { @@ -42,7 +46,8 @@ export class UmbPropertyEditorUINumberElement extends UmbLitElement implements U min=${ifDefined(this._min)} max=${ifDefined(this._max)} step=${ifDefined(this._step)} - .value=${this.value ?? 0} + placeholder=${ifDefined(this._placeholder)} + .value=${this.value ?? (this._placeholder ? undefined : 0)} @input=${this.#onInput}> `; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-access/user-workspace-access.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-access/user-workspace-access.element.ts index 7b9ad681c5..d303ea8569 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-access/user-workspace-access.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-access/user-workspace-access.element.ts @@ -40,12 +40,15 @@ export class UmbUserWorkspaceAccessElement extends UmbLitElement { #renderDocumentStartNodes() { return html` Content `; + .uniques=${this._user?.documentStartNodeUniques.map((reference) => reference.unique) || + []}>`; } #renderMediaStartNodes() { return html` Media - `; + reference.unique) || + []}>`; } static styles = [ diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-assign-access/user-workspace-assign-access.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-assign-access/user-workspace-assign-access.element.ts index 3ff486de9e..7a39ff8589 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-assign-access/user-workspace-assign-access.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/components/user-workspace-assign-access/user-workspace-assign-access.element.ts @@ -130,7 +130,7 @@ export class UmbUserWorkspaceAssignAccessElement extends UmbLitElement { description="${this.localize.term('user_groupsHelp')}"> reference.unique)} @change=${this.#onUserGroupsChange}> `; } @@ -152,7 +152,7 @@ export class UmbUserWorkspaceAssignAccessElement extends UmbLitElement { ? html` reference.unique)} @change=${this.#onDocumentStartNodeChange}> ` : nothing} @@ -177,7 +177,7 @@ export class UmbUserWorkspaceAssignAccessElement extends UmbLitElement { ? html` reference.unique)} @change=${this.#onMediaStartNodeChange}> ` : nothing} diff --git a/src/Umbraco.Web.UI.Client/staticwebapp.config.json b/src/Umbraco.Web.UI.Client/staticwebapp.config.json index d08bfb5cc1..4ff06b6140 100644 --- a/src/Umbraco.Web.UI.Client/staticwebapp.config.json +++ b/src/Umbraco.Web.UI.Client/staticwebapp.config.json @@ -1,10 +1,7 @@ { - "navigationFallback": { - "rewrite": "/index.html", - "exclude": [ - "*.{jpg,jpeg,gif,png,svg}", - "/assets/*" - ] - }, - "trailingSlash": "never" -} \ No newline at end of file + "navigationFallback": { + "rewrite": "/index.html", + "exclude": ["*.{jpg,jpeg,gif,png,svg,css}", "/assets/*"] + }, + "trailingSlash": "never" +} diff --git a/src/Umbraco.Web.UI.Client/tsconfig.json b/src/Umbraco.Web.UI.Client/tsconfig.json index 316fbbe6d5..86aa97925d 100644 --- a/src/Umbraco.Web.UI.Client/tsconfig.json +++ b/src/Umbraco.Web.UI.Client/tsconfig.json @@ -66,6 +66,7 @@ DON'T EDIT THIS FILE DIRECTLY. It is generated by /devops/tsconfig/index.js "@umbraco-cms/backoffice/extension-registry": ["./src/packages/core/extension-registry/index.ts"], "@umbraco-cms/backoffice/icon": ["./src/packages/core/icon-registry/index.ts"], "@umbraco-cms/backoffice/id": ["./src/packages/core/id/index.ts"], + "@umbraco-cms/backoffice/imaging": ["./src/packages/core/imaging/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"],