From 9891a052591fc4aa21eff10367cd3c9e6db312e8 Mon Sep 17 00:00:00 2001 From: Lone Iversen <108085781+loivsen@users.noreply.github.com> Date: Wed, 15 May 2024 16:37:55 +0200 Subject: [PATCH] Feature: Imaging Endpoint --- src/Umbraco.Web.UI.Client/package.json | 1 + .../src/external/backend-api/src/models.ts | 36 ++++++++++++ .../src/external/backend-api/src/services.ts | 46 +++++++++++++++- .../core/imaging/imaging.repository.ts | 40 ++++++++++++++ .../core/imaging/imaging.server.data.ts | 53 ++++++++++++++++++ .../packages/core/imaging/imaging.store.ts | 26 +++++++++ .../src/packages/core/imaging/index.ts | 2 + .../src/packages/core/imaging/manifests.ts | 13 +++++ .../src/packages/core/imaging/types.ts | 8 +++ .../src/packages/core/manifests.ts | 2 + .../collection/media-collection.context.ts | 26 +++++++++ .../packages/media/media/collection/types.ts | 2 +- .../media-grid-collection-view.element.ts | 10 +++- .../media-table-collection-view.element.ts | 1 - .../media-picker-folder-path.element.ts | 55 +++++++++---------- .../media-picker-modal.element.ts | 51 ++++++++++++----- .../media/media/modals/media-picker/types.ts | 2 +- .../packages/media/media/repository/index.ts | 1 + src/Umbraco.Web.UI.Client/tsconfig.json | 1 + 19 files changed, 327 insertions(+), 49 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/imaging/imaging.repository.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/imaging/imaging.server.data.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/imaging/imaging.store.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/imaging/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/imaging/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/imaging/types.ts 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/external/backend-api/src/models.ts b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models.ts index da4e84c5d8..1eb1880137 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 @@ -991,6 +991,15 @@ url?: string | null type?: string | null }; +export enum ImageCropModeModel { + CROP = 'Crop', + MAX = 'Max', + STRETCH = 'Stretch', + PAD = 'Pad', + BOX_PAD = 'BoxPad', + MIN = 'Min' +} + export type ImportDictionaryRequestModel = { temporaryFile: ReferenceByIdModel parent?: ReferenceByIdModel | null @@ -2574,6 +2583,12 @@ value: string key: string }; +export type UserExternalLoginProviderModel = { + providerSchemeName: string +isLinkedOnUser: boolean +hasManualLinkingEnabled: boolean + }; + export type UserGroupItemResponseModel = { id: string name: string @@ -3510,6 +3525,26 @@ tree?: string } +export type ImagingData = { + + payloads: { + GetImagingResizeUrls: { + height?: number +id?: Array +mode?: ImageCropModeModel +width?: number + + }; + } + + + responses: { + GetImagingResizeUrls: Array + + } + + } + export type IndexerData = { payloads: { @@ -5205,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 84d88c42c7..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 @@ -1,7 +1,7 @@ import type { CancelablePromise } from './core/CancelablePromise'; import { OpenAPI } from './core/OpenAPI'; import { request as __request } from './core/request'; -import type { CultureData, DataTypeData, DictionaryData, DocumentBlueprintData, DocumentTypeData, DocumentVersionData, DocumentData, DynamicRootData, HealthCheckData, HelpData, IndexerData, InstallData, LanguageData, LogViewerData, ManifestData, MediaTypeData, MediaData, MemberGroupData, MemberTypeData, MemberData, ModelsBuilderData, ObjectTypesData, OembedData, PackageData, PartialViewData, PreviewData, ProfilingData, PropertyTypeData, PublishedCacheData, RedirectManagementData, RelationTypeData, RelationData, ScriptData, SearcherData, SecurityData, SegmentData, ServerData, StaticFileData, StylesheetData, TagData, TelemetryData, TemplateData, TemporaryFileData, UpgradeData, UserDataData, UserGroupData, UserData, WebhookData } from './models'; +import type { CultureData, DataTypeData, DictionaryData, DocumentBlueprintData, DocumentTypeData, DocumentVersionData, DocumentData, DynamicRootData, HealthCheckData, HelpData, ImagingData, IndexerData, InstallData, LanguageData, LogViewerData, ManifestData, MediaTypeData, MediaData, MemberGroupData, MemberTypeData, MemberData, ModelsBuilderData, ObjectTypesData, OembedData, PackageData, PartialViewData, PreviewData, ProfilingData, PropertyTypeData, PublishedCacheData, RedirectManagementData, RelationTypeData, RelationData, ScriptData, SearcherData, SecurityData, SegmentData, ServerData, StaticFileData, StylesheetData, TagData, TelemetryData, TemplateData, TemporaryFileData, UpgradeData, UserDataData, UserGroupData, UserData, WebhookData } from './models'; export class CultureService { @@ -2917,6 +2917,35 @@ baseUrl } +export class ImagingService { + + /** + * @returns unknown Success + * @throws ApiError + */ + public static getImagingResizeUrls(data: ImagingData['payloads']['GetImagingResizeUrls'] = {}): CancelablePromise { + const { + + id, +height, +width, +mode + } = data; + return __request(OpenAPI, { + method: 'GET', + url: '/umbraco/management/api/v1/imaging/resize/urls', + query: { + id, height, width, mode + }, + errors: { + 401: `The resource is protected and requires an authentication token`, + 403: `The authenticated user do not have access to this resource`, + }, + }); + } + +} + export class IndexerService { /** @@ -8657,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/core/imaging/imaging.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/core/imaging/imaging.repository.ts new file mode 100644 index 0000000000..aae7b477f0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/imaging/imaging.repository.ts @@ -0,0 +1,40 @@ +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 { + //protected _init: Promise; + //protected _itemStore?: UmbImagingStore; + #itemSource: UmbImagingServerDataSource; + + constructor(host: UmbControllerHost) { + super(host); + this.#itemSource = new UmbImagingServerDataSource(host); + + /*this._init = this.consumeContext(UMB_IMAGING_STORE_CONTEXT, (instance) => { + this._itemStore = instance as UmbImagingStore; + }).asPromise();*/ + } + + /** + * Requests the items for the given uniques + * @param {Array} uniques + * @return {*} + * @memberof UmbImagingRepository + */ + async requestResizedItems({ uniques, height, width, mode }: UmbImagingModel) { + if (!uniques.length) throw new Error('Uniques are missing'); + //await this._init; + + const { data, error: _error } = await this.#itemSource.getItems({ uniques, height, width, mode }); + const error: any = _error; + /*if (data) { + this._itemStore!.appendItems(data); + } + return { data, error, asObservable: () => this._itemStore!.items(uniques) };*/ + + 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..ab297cff99 --- /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, width, height, mode }: UmbImagingModel) { + if (!uniques.length) throw new Error('Uniques are missing'); + + const { data, error } = await tryExecuteAndNotify( + this.#host, + ImagingService.getImagingResizeUrls({ id: uniques, width, height, mode }), + ); + + 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/imaging.store.ts b/src/Umbraco.Web.UI.Client/src/packages/core/imaging/imaging.store.ts new file mode 100644 index 0000000000..acbb9b5f6e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/imaging/imaging.store.ts @@ -0,0 +1,26 @@ +import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { UmbItemStoreBase } from '@umbraco-cms/backoffice/store'; +import type { UmbMediaUrlModel } from '@umbraco-cms/backoffice/media'; + +/** + * @export + * @class UmbImagingStore + * @extends {UmbStoreBase} + * @description - Data Store for Imaging Items + */ + +export class UmbImagingStore extends UmbItemStoreBase { + /** + * Creates an instance of UmbImagingStore. + * @param {UmbControllerHost} host + * @memberof UmbImagingStore + */ + constructor(host: UmbControllerHost) { + super(host, UMB_IMAGING_STORE_CONTEXT.toString()); + } +} + +export default UmbImagingStore; + +export const UMB_IMAGING_STORE_CONTEXT = new UmbContextToken('UmbImagingStore'); 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..cc3b637a97 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/imaging/types.ts @@ -0,0 +1,8 @@ +import type { ImageCropModeModel } from '@umbraco-cms/backoffice/external/backend-api'; + +export interface UmbImagingModel { + uniques: Array; + 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/media/media/collection/media-collection.context.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/media-collection.context.ts index bcf2f7ffe2..5a9078f6ea 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,13 +1,39 @@ +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'; +import { ImageCropModeModel } from '@umbraco-cms/backoffice/external/backend-api'; 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({ + uniques: items.map((m) => m.unique), + width: 250, + mode: ImageCropModeModel.MIN, + }); + + 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 +31,8 @@ export class UmbMediaPickerModalElement extends UmbModalBaseElement { super.connectedCallback(); @@ -35,7 +40,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({ + uniques: items.map((item) => item.unique), + width: 250, + mode: ImageCropModeModel.MIN, + }); 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 +198,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 +209,7 @@ export class UmbMediaPickerModalElement extends UmbModalBaseElement`; } @@ -227,10 +237,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/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"],