Feature: Imaging Endpoint

This commit is contained in:
Lone Iversen
2024-05-15 16:37:55 +02:00
parent e8729f32eb
commit 9891a05259
19 changed files with 327 additions and 49 deletions

View File

@@ -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",

View File

@@ -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<string>
mode?: ImageCropModeModel
width?: number
};
}
responses: {
GetImagingResizeUrls: Array<MediaUrlInfoResponseModel>
}
}
export type IndexerData = {
payloads: {
@@ -5205,6 +5240,7 @@ PostUserUnlock: {
,PostUserCurrentAvatar: string
,PostUserCurrentChangePassword: string
,GetUserCurrentConfiguration: CurrenUserConfigurationResponseModel
,GetUserCurrentLoginProviders: Array<UserExternalLoginProviderModel>
,GetUserCurrentLogins: LinkedLoginsRequestModel
,GetUserCurrentPermissions: UserPermissionsResponseModel
,GetUserCurrentPermissionsDocument: Array<UserPermissionsResponseModel>

View File

@@ -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<ImagingData['responses']['GetImagingResizeUrls']> {
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<UserData['responses']['GetUserCurrentLoginProviders']> {
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

View File

@@ -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<unknown>;
//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<string>} 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 };
}
}

View File

@@ -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,
};
}
}

View File

@@ -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<UmbMediaUrlModel> {
/**
* 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>('UmbImagingStore');

View File

@@ -0,0 +1,2 @@
export { UmbImagingRepository } from './imaging.repository.js';
export { UMB_IMAGING_REPOSITORY_ALIAS } from './manifests.js';

View File

@@ -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<ManifestTypes> = [repository];

View File

@@ -0,0 +1,8 @@
import type { ImageCropModeModel } from '@umbraco-cms/backoffice/external/backend-api';
export interface UmbImagingModel {
uniques: Array<string>;
height?: number;
width?: number;
mode?: ImageCropModeModel;
}

View File

@@ -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<ManifestTypes | UmbBackofficeManifestKind> = [
...authManifests,
...extensionManifests,
...iconRegistryManifests,
...imagingManifests,
...cultureManifests,
...localizationManifests,
...themeManifests,

View File

@@ -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<UmbMediaCollectionItemModel>([], (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 };
}),
);
});
}
}

View File

@@ -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;
}

View File

@@ -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`<img src=${item.url} alt=${item.name} />` : html`<umb-icon name=${item.icon}></umb-icon>`}
<!-- TODO: [LK] I'd like to indicate a busy state when bulk actions are triggered. -->
<!-- <div class="container"><uui-loader></uui-loader></div> -->
</uui-card-media>
@@ -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);
}
`,
];
}

View File

@@ -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 {

View File

@@ -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<UmbMediaPathModel> = [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`<uui-button
compact
.label=${path.name}
?disabled=${this.currentMedia?.unique === path.unique}
@click=${() => this.#goToFolder({ unique: path.unique, entityType: path.entityType })}></uui-button
?disabled=${this.currentMedia.unique === path.unique}
@click=${() => this.#goToFolder(path)}></uui-button
>/`,
)}${this._typingNewFolder
? html`<uui-input

View File

@@ -1,19 +1,23 @@
import { UmbImagingRepository } from '@umbraco-cms/backoffice/imaging';
import { type UmbMediaItemModel, UmbMediaItemRepository, UmbMediaUrlRepository } from '../../repository/index.js';
import { UmbMediaTreeRepository } from '../../tree/media-tree.repository.js';
import { UMB_MEDIA_ROOT_ENTITY_TYPE } from '../../entity.js';
import type { UmbMediaCardItemModel } from './types.js';
import type { UmbMediaCardItemModel, UmbMediaPathModel } from './types.js';
import type { UmbMediaPickerFolderPathElement } from './components/media-picker-folder-path.element.js';
import type { UmbMediaPickerModalData, UmbMediaPickerModalValue } from './media-picker-modal.token.js';
import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';
import { css, html, customElement, state, repeat, ifDefined } from '@umbraco-cms/backoffice/external/lit';
import type { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui';
import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity';
import { ImageCropModeModel } from '@umbraco-cms/backoffice/external/backend-api';
const root: UmbMediaPathModel = { name: 'Media', unique: null, entityType: UMB_MEDIA_ROOT_ENTITY_TYPE };
@customElement('umb-media-picker-modal')
export class UmbMediaPickerModalElement extends UmbModalBaseElement<UmbMediaPickerModalData, UmbMediaPickerModalValue> {
#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<UmbMediaCardItemModel> = [];
@@ -27,7 +31,8 @@ export class UmbMediaPickerModalElement extends UmbModalBaseElement<UmbMediaPick
private _searchQuery = '';
@state()
private _currentMediaEntity: UmbEntityModel = { unique: null, entityType: UMB_MEDIA_ROOT_ENTITY_TYPE };
private _currentMediaEntity: UmbMediaPathModel = root;
async connectedCallback(): Promise<void> {
super.connectedCallback();
@@ -35,7 +40,7 @@ export class UmbMediaPickerModalElement extends UmbModalBaseElement<UmbMediaPick
const { data } = await this.#mediaItemRepository.requestItems([this.data.startNode]);
if (data?.length) {
this._currentMediaEntity = { unique: data[0].unique, entityType: data[0].entityType };
this._currentMediaEntity = { name: data[0].name, unique: data[0].unique, entityType: data[0].entityType };
}
}
@@ -59,18 +64,22 @@ export class UmbMediaPickerModalElement extends UmbModalBaseElement<UmbMediaPick
async #mapMediaUrls(items: Array<UmbMediaItemModel>): Promise<Array<UmbMediaCardItemModel>> {
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<UmbMediaPick
@selected=${() => 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`<img src=${item.url} alt=${ifDefined(item.name)} />` : ''}
selectable>
${item.url
? html`<img src=${item.url} alt=${ifDefined(item.name)} />`
: html`<umb-icon .name=${item.icon}></umb-icon>`}
</uui-card-media>
`;
}
@@ -199,7 +209,7 @@ export class UmbMediaPickerModalElement extends UmbModalBaseElement<UmbMediaPick
#renderPath() {
return html`<umb-media-picker-folder-path
slot="footer-info"
.currentPath=${this._currentMediaEntity.unique}
.currentMedia=${this._currentMediaEntity}
@change=${this.#onPathChange}></umb-media-picker-folder-path>`;
}
@@ -227,10 +237,23 @@ export class UmbMediaPickerModalElement extends UmbModalBaseElement<UmbMediaPick
#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);
padding-bottom: 5px; /** The modal is a bit jumpy due to the img card focus/hover border. This fixes the issue. */
}
umb-icon {
font-size: var(--uui-size-24);
}
img {
background-image: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" fill-opacity=".1"><path d="M50 0h50v50H50zM0 50h50v50H0z"/></svg>');
background-size: 10px 10px;
background-repeat: repeat;
}
#actions {
max-width: 100%;
}
`,
];
}

View File

@@ -6,7 +6,7 @@ export interface UmbMediaCardItemModel {
unique: string;
entityType: UmbMediaEntityType;
url?: string;
extension?: string;
icon?: string;
}
export interface UmbMediaPathModel extends UmbEntityModel {

View File

@@ -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';

View File

@@ -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"],