Merge branch 'main' into bugfix/reload-collection-on-entity-structure-reload-request

This commit is contained in:
Mads Rasmussen
2024-05-17 13:48:46 +02:00
committed by GitHub
15 changed files with 207 additions and 85 deletions

View File

@@ -25,7 +25,7 @@ export class UmbInputUploadFieldElement extends UmbLitElement {
this._src = value.src;
}
get value(): MediaValueType {
return !this.temporaryFile ? { src: this._src } : { temporaryFileId: this.temporaryFile.unique };
return !this.temporaryFile ? { src: this._src } : { temporaryFileId: this.temporaryFile.temporaryUnique };
}
/**
@@ -67,7 +67,7 @@ export class UmbInputUploadFieldElement extends UmbLitElement {
async #onUpload(e: UUIFileDropzoneEvent) {
//Property Editor for Upload field will always only have one file.
const item: UmbTemporaryFileModel = {
unique: UmbId.new(),
temporaryUnique: UmbId.new(),
file: e.detail.files[0],
};
const upload = this.#manager.uploadOne(item);
@@ -80,7 +80,7 @@ export class UmbInputUploadFieldElement extends UmbLitElement {
const uploaded = await upload;
if (uploaded.status === TemporaryFileStatus.SUCCESS) {
this.temporaryFile = { unique: item.unique, file: item.file };
this.temporaryFile = { temporaryUnique: item.temporaryUnique, file: item.file };
this.dispatchEvent(new UmbChangeEvent());
}
}
@@ -172,6 +172,9 @@ export class UmbInputUploadFieldElement extends UmbLitElement {
static styles = [
css`
:host {
position: relative;
}
uui-icon {
vertical-align: sub;
margin-right: var(--uui-size-space-4);

View File

@@ -2,7 +2,6 @@ import { UmbTemporaryFileRepository } from './temporary-file.repository.js';
import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
import { UmbId } from '@umbraco-cms/backoffice/id';
///export type TemporaryFileStatus = 'success' | 'waiting' | 'error';
@@ -14,7 +13,7 @@ export enum TemporaryFileStatus {
export interface UmbTemporaryFileModel {
file: File;
unique: string;
temporaryUnique: string;
status?: TemporaryFileStatus;
}
@@ -23,7 +22,7 @@ export class UmbTemporaryFileManager<
> extends UmbControllerBase {
#temporaryFileRepository;
#queue = new UmbArrayState<UploadableItem>([], (item) => item.unique);
#queue = new UmbArrayState<UploadableItem>([], (item) => item.temporaryUnique);
public readonly queue = this.#queue.asObservable();
constructor(host: UmbControllerHost) {
@@ -66,18 +65,18 @@ export class UmbTemporaryFileManager<
if (!queue.length) return filesCompleted;
for (const item of queue) {
if (!item.unique) throw new Error(`Unique is missing for item ${item}`);
if (!item.temporaryUnique) throw new Error(`Unique is missing for item ${item}`);
const { error } = await this.#temporaryFileRepository.upload(item.unique, item.file);
const { error } = await this.#temporaryFileRepository.upload(item.temporaryUnique, item.file);
//await new Promise((resolve) => setTimeout(resolve, (Math.random() + 0.5) * 1000)); // simulate small delay so that the upload badge is properly shown
let status: TemporaryFileStatus;
if (error) {
status = TemporaryFileStatus.ERROR;
this.#queue.updateOne(item.unique, { ...item, status });
this.#queue.updateOne(item.temporaryUnique, { ...item, status });
} else {
status = TemporaryFileStatus.SUCCESS;
this.#queue.updateOne(item.unique, { ...item, status });
this.#queue.updateOne(item.temporaryUnique, { ...item, status });
}
filesCompleted.push({ ...item, status });

View File

@@ -1,9 +1,10 @@
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 { UmbImagingRepository } from '@umbraco-cms/backoffice/imaging';
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,
@@ -21,7 +22,10 @@ export class UmbMediaCollectionContext extends UmbDefaultCollectionContext<
this.observe(this.items, async (items) => {
if (!items?.length) return;
const { data } = await this.#imagingRepository.requestResizedItems(items.map((m) => m.unique));
const { data } = await this.#imagingRepository.requestResizedItems(
items.map((m) => m.unique),
{ height: 400, width: 400, mode: ImageCropModeModel.MIN },
);
this.#thumbnailItems.setValue(
items.map((item) => {

View File

@@ -116,7 +116,6 @@ export class UmbMediaGridCollectionViewElement extends UmbLitElement {
}
#renderItem(item: UmbMediaCollectionItemModel) {
// TODO: Fix the file extension when media items have a file extension. [?]
return html`
<uui-card-media
.name=${item.name ?? 'Unnamed Media'}
@@ -126,8 +125,7 @@ export class UmbMediaGridCollectionViewElement extends UmbLitElement {
@open=${(event: Event) => this.#onOpen(event, item.unique)}
@selected=${() => this.#onSelect(item)}
@deselected=${() => this.#onDeselect(item)}
class="media-item"
file-ext="${item.icon}">
class="media-item">
${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> -->
@@ -156,7 +154,7 @@ export class UmbMediaGridCollectionViewElement extends UmbLitElement {
gap: var(--uui-size-space-5);
}
umb-icon {
font-size: var(--uui-size-24);
font-size: var(--uui-size-8);
}
`,
];

View File

@@ -5,20 +5,15 @@ import { LitElement, css, html, nothing, customElement, property, query } from '
@customElement('umb-image-cropper-focus-setter')
export class UmbImageCropperFocusSetterElement extends LitElement {
@query('#image') imageElement?: HTMLImageElement;
@query('#image') imageElement!: HTMLImageElement;
@query('#wrapper') wrapperElement?: HTMLImageElement;
@query('#focal-point') focalPointElement?: HTMLImageElement;
@query('#focal-point') focalPointElement!: HTMLImageElement;
@property({ type: String }) src?: string;
@property({ attribute: false }) focalPoint: UmbImageCropperFocalPoint = { left: 0.5, top: 0.5 };
#DOT_RADIUS = 6 as const;
connectedCallback() {
super.connectedCallback();
this.#addEventListeners();
}
disconnectedCallback() {
super.disconnectedCallback();
this.#removeEventListeners();
@@ -33,33 +28,46 @@ export class UmbImageCropperFocusSetterElement extends LitElement {
}
}
protected update(changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void {
super.update(changedProperties);
if (changedProperties.has('src')) {
if (this.src) {
this.#initializeImage();
}
}
}
protected firstUpdated(_changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void {
super.firstUpdated(_changedProperties);
this.style.setProperty('--dot-radius', `${this.#DOT_RADIUS}px`);
}
if (this.focalPointElement) {
this.focalPointElement.style.left = `calc(${this.focalPoint.left * 100}% - ${this.#DOT_RADIUS}px)`;
this.focalPointElement.style.top = `calc(${this.focalPoint.top * 100}% - ${this.#DOT_RADIUS}px)`;
}
if (this.imageElement) {
this.imageElement.onload = () => {
if (!this.imageElement || !this.wrapperElement) return;
const imageAspectRatio = this.imageElement.naturalWidth / this.imageElement.naturalHeight;
const hostRect = this.getBoundingClientRect();
const image = this.imageElement.getBoundingClientRect();
async #initializeImage() {
await this.updateComplete; // Wait for the @query to be resolved
if (image.width > hostRect.width) {
this.imageElement.style.width = '100%';
}
if (image.height > hostRect.height) {
this.imageElement.style.height = '100%';
}
this.focalPointElement.style.left = `calc(${this.focalPoint.left * 100}% - ${this.#DOT_RADIUS}px)`;
this.focalPointElement.style.top = `calc(${this.focalPoint.top * 100}% - ${this.#DOT_RADIUS}px)`;
this.imageElement.style.aspectRatio = `${imageAspectRatio}`;
this.wrapperElement.style.aspectRatio = `${imageAspectRatio}`;
};
}
this.imageElement.onload = () => {
if (!this.imageElement || !this.wrapperElement) return;
const imageAspectRatio = this.imageElement.naturalWidth / this.imageElement.naturalHeight;
const hostRect = this.getBoundingClientRect();
const image = this.imageElement.getBoundingClientRect();
if (image.width > hostRect.width) {
this.imageElement.style.width = '100%';
}
if (image.height > hostRect.height) {
this.imageElement.style.height = '100%';
}
this.imageElement.style.aspectRatio = `${imageAspectRatio}`;
this.wrapperElement.style.aspectRatio = `${imageAspectRatio}`;
};
this.#addEventListeners();
}
async #addEventListeners() {
@@ -134,6 +142,7 @@ export class UmbImageCropperFocusSetterElement extends LitElement {
}
/* Wrapper is used to make the focal point position responsive to the image size */
#wrapper {
overflow: hidden;
position: relative;
display: flex;
margin: auto;

View File

@@ -33,11 +33,7 @@ export class UmbImageCropperPreviewElement extends LitElement {
if (!this.crop) return;
await this.updateComplete; // Wait for the @query to be resolved
if (!this.imageElement.complete) {
// Wait for the image to load
await new Promise((resolve) => (this.imageElement.onload = () => resolve(this.imageElement)));
}
await new Promise((resolve) => (this.imageElement.onload = () => resolve(this.imageElement)));
const container = this.imageContainerElement.getBoundingClientRect();
const cropAspectRatio = this.crop.width / this.crop.height;

View File

@@ -376,6 +376,7 @@ export class UmbImageCropperElement extends LitElement {
#image {
display: block;
position: absolute;
user-select: none;
}
#slider {

View File

@@ -56,7 +56,7 @@ export class UmbInputImageCropperElement extends UmbLitElement {
this.value = assignToFrozenObject(this.value, { temporaryFileId: unique });
this.#manager?.uploadOne({ unique, file });
this.#manager?.uploadOne({ temporaryUnique: unique, file });
this.dispatchEvent(new UmbChangeEvent());
}
@@ -68,8 +68,9 @@ export class UmbInputImageCropperElement extends UmbLitElement {
#onRemove = () => {
this.value = assignToFrozenObject(this.value, { src: '', temporaryFileId: null });
if (!this.fileUnique) return;
this.#manager?.removeOne(this.fileUnique);
if (this.fileUnique) {
this.#manager?.removeOne(this.fileUnique);
}
this.fileUnique = undefined;
this.file = undefined;
@@ -114,7 +115,7 @@ export class UmbInputImageCropperElement extends UmbLitElement {
const value = (e.target as UmbInputImageCropperFieldElement).value;
if (!value) {
this.value = { src: '', crops: [], focalPoint: { left: 0.5, top: 0.5 } };
this.value = { src: '', crops: [], focalPoint: { left: 0.5, top: 0.5 }, temporaryFileId: null };
this.dispatchEvent(new UmbChangeEvent());
return;
}

View File

@@ -1,13 +1,16 @@
import { UMB_MEDIA_PICKER_MODAL, type UmbMediaCardItemModel } from '../../modals/index.js';
import { UMB_MEDIA_ITEM_REPOSITORY_ALIAS } from '../../repository/index.js';
import type { UmbMediaItemModel } from '../../repository/item/types.js';
import type { UmbMediaTreeItemModel } from '../../tree/index.js';
import { UMB_MEDIA_TREE_PICKER_MODAL } from '../../tree/index.js';
import type {
UmbMediaTreePickerModalData,
UmbMediaTreePickerModalValue,
} from '../../tree/media-tree-picker-modal.token.js';
import { UmbPickerInputContext } from '@umbraco-cms/backoffice/picker-input';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api';
import { UmbImagingRepository } from '@umbraco-cms/backoffice/imaging';
import { ImageCropModeModel } from '@umbraco-cms/backoffice/external/backend-api';
export class UmbMediaPickerContext extends UmbPickerInputContext<
UmbMediaItemModel,
@@ -15,7 +18,38 @@ export class UmbMediaPickerContext extends UmbPickerInputContext<
UmbMediaTreePickerModalData,
UmbMediaTreePickerModalValue
> {
#imagingRepository: UmbImagingRepository;
#cardItems = new UmbArrayState<UmbMediaCardItemModel>([], (x) => x.unique);
readonly cardItems = this.#cardItems.asObservable();
constructor(host: UmbControllerHost) {
super(host, UMB_MEDIA_ITEM_REPOSITORY_ALIAS, UMB_MEDIA_TREE_PICKER_MODAL);
super(host, UMB_MEDIA_ITEM_REPOSITORY_ALIAS, UMB_MEDIA_PICKER_MODAL);
this.#imagingRepository = new UmbImagingRepository(host);
this.observe(this.selectedItems, async (selectedItems) => {
if (!selectedItems?.length) {
this.#cardItems.setValue([]);
return;
}
const { data } = await this.#imagingRepository.requestResizedItems(
selectedItems.map((x) => x.unique),
{ height: 400, width: 400, mode: ImageCropModeModel.MIN },
);
this.#cardItems.setValue(
selectedItems.map((item) => {
const url = data?.find((x) => x.unique === item.unique)?.url;
return {
icon: item.mediaType.icon,
name: item.name,
unique: item.unique,
isTrashed: item.isTrashed,
entityType: item.entityType,
url,
};
}),
);
});
}
}

View File

@@ -1,3 +1,4 @@
import type { UmbMediaCardItemModel } from '../../modals/index.js';
import type { UmbMediaItemModel } from '../../repository/index.js';
import { UmbMediaPickerContext } from './input-media.context.js';
import { css, html, customElement, property, state, ifDefined, repeat } from '@umbraco-cms/backoffice/external/lit';
@@ -7,6 +8,7 @@ import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UmbModalRouteRegistrationController, UMB_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/modal';
import { UmbSorterController } from '@umbraco-cms/backoffice/sorter';
import { UUIFormControlMixin } from '@umbraco-cms/backoffice/external/uui';
import type { UmbUploadableFileModel } from '@umbraco-cms/backoffice/media';
@customElement('umb-input-media')
export class UmbInputMediaElement extends UUIFormControlMixin(UmbLitElement, '') {
@@ -100,7 +102,7 @@ export class UmbInputMediaElement extends UUIFormControlMixin(UmbLitElement, '')
private _editMediaPath = '';
@state()
private _items?: Array<UmbMediaItemModel>;
private _items?: Array<UmbMediaCardItemModel>;
#pickerContext = new UmbMediaPickerContext(this);
@@ -117,7 +119,9 @@ export class UmbInputMediaElement extends UUIFormControlMixin(UmbLitElement, '')
});
this.observe(this.#pickerContext.selection, (selection) => (this.value = selection.join(',')));
this.observe(this.#pickerContext.selectedItems, (selectedItems) => (this._items = selectedItems));
this.observe(this.#pickerContext.cardItems, (cardItems) => {
this._items = cardItems;
});
this.addValidator(
'rangeUnderflow',
@@ -150,12 +154,29 @@ export class UmbInputMediaElement extends UUIFormControlMixin(UmbLitElement, '')
});
}
async #onUploadCompleted(e: CustomEvent) {
const completed = e.detail?.completed as Array<UmbUploadableFileModel>;
const uploaded = completed.map((file) => file.unique);
this.selection = [...this.selection, ...uploaded];
this.dispatchEvent(new UmbChangeEvent());
}
render() {
return html`<div class="container">${this.#renderItems()} ${this.#renderAddButton()}</div>`;
return html`${this.#renderDropzone()}
<div class="container">${this.#renderItems()} ${this.#renderAddButton()}</div>`;
}
#renderDropzone() {
if (this._items && this._items.length >= this.max) return;
return html`<umb-dropzone
id="dropzone"
?multiple=${this.max === 1}
@change=${this.#onUploadCompleted}></umb-dropzone>`;
}
#renderItems() {
if (!this._items) return;
if (!this._items?.length) return;
return html`${repeat(
this._items,
(item) => item.unique,
@@ -177,20 +198,20 @@ export class UmbInputMediaElement extends UUIFormControlMixin(UmbLitElement, '')
`;
}
#renderItem(item: UmbMediaItemModel) {
// TODO: `file-ext` value has been hardcoded here. Find out if API model has value for it. [LK]
#renderItem(item: UmbMediaCardItemModel) {
return html`
<uui-card-media
name=${ifDefined(item.name === null ? undefined : item.name)}
detail=${ifDefined(item.unique)}
file-ext="jpg">
<uui-card-media name=${ifDefined(item.name === null ? undefined : item.name)} detail=${ifDefined(item.unique)}>
${item.url
? html`<img src=${item.url} alt=${item.name} />`
: html`<umb-icon name=${ifDefined(item.icon)}></umb-icon>`}
${this.#renderIsTrashed(item)}
<uui-action-bar slot="actions">
${this.#renderOpenButton(item)}
<uui-button label="Copy media">
<uui-button label="Copy media" look="secondary">
<uui-icon name="icon-documents"></uui-icon>
</uui-button>
<uui-button
look="secondary"
@click=${() => this.#pickerContext.requestRemoveItem(item.unique)}
label="Remove media ${item.name}">
<uui-icon name="icon-trash"></uui-icon>
@@ -200,7 +221,7 @@ export class UmbInputMediaElement extends UUIFormControlMixin(UmbLitElement, '')
`;
}
#renderIsTrashed(item: UmbMediaItemModel) {
#renderIsTrashed(item: UmbMediaCardItemModel) {
if (!item.isTrashed) return;
return html`
<uui-tag size="s" slot="tag" color="danger">
@@ -209,7 +230,7 @@ export class UmbInputMediaElement extends UUIFormControlMixin(UmbLitElement, '')
`;
}
#renderOpenButton(item: UmbMediaItemModel) {
#renderOpenButton(item: UmbMediaCardItemModel) {
if (!this.showOpenButton) return;
return html`
<uui-button
@@ -223,6 +244,9 @@ export class UmbInputMediaElement extends UUIFormControlMixin(UmbLitElement, '')
static styles = [
css`
:host {
position: relative;
}
.container {
display: grid;
gap: var(--uui-size-space-3);
@@ -240,6 +264,10 @@ export class UmbInputMediaElement extends UUIFormControlMixin(UmbLitElement, '')
margin: 0 auto;
}
uui-card-media umb-icon {
font-size: var(--uui-size-8);
}
uui-card-media[drag-placeholder] {
opacity: 0.2;
}

View File

@@ -15,7 +15,6 @@ import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api';
export interface UmbUploadableFileModel extends UmbTemporaryFileModel {
unique: string;
file: File;
mediaTypeUnique: string;
}
@@ -38,7 +37,10 @@ export class UmbDropzoneManager extends UmbControllerBase {
#mediaTypeStructure = new UmbMediaTypeStructureRepository(this);
#mediaDetailRepository = new UmbMediaDetailRepository(this);
#completed = new UmbArrayState<UmbUploadableFileModel | UmbTemporaryFileModel>([], (upload) => upload.unique);
#completed = new UmbArrayState<UmbUploadableFileModel | UmbTemporaryFileModel>(
[],
(upload) => upload.temporaryUnique,
);
public readonly completed = this.#completed.asObservable();
constructor(host: UmbControllerHost) {
@@ -56,7 +58,7 @@ export class UmbDropzoneManager extends UmbControllerBase {
const temporaryFiles: Array<UmbTemporaryFileModel> = [];
for (const file of files) {
const uploaded = await this.#tempFileManager.uploadOne({ unique: UmbId.new(), file });
const uploaded = await this.#tempFileManager.uploadOne({ temporaryUnique: UmbId.new(), file });
this.#completed.setValue([...this.#completed.getValue(), uploaded]);
temporaryFiles.push(uploaded);
}
@@ -107,7 +109,12 @@ export class UmbDropzoneManager extends UmbControllerBase {
// Since we are uploading multiple files, we will pick first allowed option.
// Consider a way we can handle this differently in the future to let the user choose. Maybe a list of all files with an allowed media type dropdown?
const mediaType = options[0];
uploadableFiles.push({ unique: UmbId.new(), file, mediaTypeUnique: mediaType.unique });
uploadableFiles.push({
temporaryUnique: UmbId.new(),
file,
mediaTypeUnique: mediaType.unique,
unique: UmbId.new(),
});
}
notAllowedFiles.forEach((file) => {
@@ -142,6 +149,7 @@ export class UmbDropzoneManager extends UmbControllerBase {
// Only one allowed option, upload file using that option.
const uploadableFile: UmbUploadableFileModel = {
unique: UmbId.new(),
temporaryUnique: UmbId.new(),
file,
mediaTypeUnique: mediaTypes[0].unique,
};
@@ -156,6 +164,7 @@ export class UmbDropzoneManager extends UmbControllerBase {
const uploadableFile: UmbUploadableFileModel = {
unique: UmbId.new(),
temporaryUnique: UmbId.new(),
file,
mediaTypeUnique: mediaType.unique,
};
@@ -211,6 +220,7 @@ export class UmbDropzoneManager extends UmbControllerBase {
if (upload.status === TemporaryFileStatus.SUCCESS) {
// Upload successful. Create media item.
const preset: Partial<UmbMediaDetailModel> = {
unique: file.unique,
mediaType: {
unique: upload.mediaTypeUnique,
collection: null,
@@ -227,7 +237,7 @@ export class UmbDropzoneManager extends UmbControllerBase {
values: [
{
alias: 'umbracoFile',
value: { temporaryFileId: upload.unique },
value: { temporaryFileId: upload.temporaryUnique },
culture: null,
segment: null,
},

View File

@@ -1,14 +1,32 @@
import { UmbDropzoneManager } from './dropzone-manager.class.js';
import { UmbChangeEvent, UmbProgressEvent } from '@umbraco-cms/backoffice/event';
import { UmbDropzoneManager, type UmbUploadableFileModel } from './dropzone-manager.class.js';
import { UmbProgressEvent } from '@umbraco-cms/backoffice/event';
import { css, html, customElement, property } from '@umbraco-cms/backoffice/external/lit';
import type { UUIFileDropzoneElement, UUIFileDropzoneEvent } from '@umbraco-cms/backoffice/external/uui';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import type { UmbTemporaryFileModel } from '@umbraco-cms/backoffice/temporary-file';
@customElement('umb-dropzone')
export class UmbDropzoneElement extends UmbLitElement {
@property({ attribute: false })
parentUnique: string | null = null;
@property({ type: Boolean })
multiple: boolean = true;
@property({ type: Boolean })
createAsTemporary: boolean = false;
@property({ type: Array, attribute: false })
accept: Array<string> = [];
//TODO: logic to disable the dropzone?
#files: Array<UmbUploadableFileModel | UmbTemporaryFileModel> = [];
public getFiles() {
return this.#files;
}
public browse() {
const element = this.shadowRoot?.querySelector('#dropzone') as UUIFileDropzoneElement;
return element.browse();
@@ -28,7 +46,9 @@ export class UmbDropzoneElement extends UmbLitElement {
document.removeEventListener('drop', this.#handleDrop.bind(this));
}
#handleDragEnter() {
#handleDragEnter(e: DragEvent) {
// Avoid collision with UmbSorterController
if (!e.dataTransfer?.types?.length) return;
this.toggleAttribute('dragging', true);
}
@@ -57,23 +77,28 @@ export class UmbDropzoneElement extends UmbLitElement {
this.dispatchEvent(new UmbProgressEvent(progress));
if (completed.length === files.length) {
this.dispatchEvent(new UmbChangeEvent());
this.#files = completed;
this.dispatchEvent(new CustomEvent('change', { detail: { completed } }));
dropzoneManager.destroy();
}
},
'_observeCompleted',
);
//TODO Create some placeholder items while files are being uploaded? Could update them as they get completed.
await dropzoneManager.createFilesAsMedia(files, this.parentUnique);
if (this.createAsTemporary) {
await dropzoneManager.createFilesAsTemporary(files);
} else {
await dropzoneManager.createFilesAsMedia(files, this.parentUnique);
}
}
render() {
return html`<uui-file-dropzone
id="dropzone"
multiple
.accept=${this.accept?.join(',')}
?multiple=${this.multiple}
@change=${this.#onDropFiles}
label="${this.localize.term('media_dragAndDropYourFilesIntoTheArea')}"
accept=""></uui-file-dropzone>`;
label="${this.localize.term('media_dragAndDropYourFilesIntoTheArea')}"></uui-file-dropzone>`;
}
static styles = [

View File

@@ -1,4 +1,3 @@
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';
@@ -6,8 +5,10 @@ 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 { UmbImagingRepository } from '@umbraco-cms/backoffice/imaging';
import { css, html, customElement, state, repeat, ifDefined } from '@umbraco-cms/backoffice/external/lit';
import type { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui';
import { ImageCropModeModel } from '@umbraco-cms/backoffice/external/backend-api';
const root: UmbMediaPathModel = { name: 'Media', unique: null, entityType: UMB_MEDIA_ROOT_ENTITY_TYPE };
@@ -63,11 +64,21 @@ export class UmbMediaPickerModalElement extends UmbModalBaseElement<UmbMediaPick
async #mapMediaUrls(items: Array<UmbMediaItemModel>): Promise<Array<UmbMediaCardItemModel>> {
if (!items.length) return [];
const { data } = await this.#imagingRepository.requestResizedItems(items.map((item) => item.unique));
const { data } = await this.#imagingRepository.requestResizedItems(
items.map((item) => item.unique),
{ height: 400, width: 400, mode: ImageCropModeModel.MIN },
);
return items.map((item): UmbMediaCardItemModel => {
const url = data?.find((media) => media.unique === item.unique)?.url;
return { name: item.name, unique: item.unique, url, icon: item.mediaType.icon, entityType: item.entityType };
return {
name: item.name,
unique: item.unique,
url,
icon: item.mediaType.icon,
entityType: item.entityType,
isTrashed: item.isTrashed,
};
});
}
@@ -236,7 +247,7 @@ export class UmbMediaPickerModalElement extends UmbModalBaseElement<UmbMediaPick
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);
font-size: var(--uui-size-8);
}
img {

View File

@@ -5,8 +5,9 @@ export interface UmbMediaCardItemModel {
name: string;
unique: string;
entityType: UmbMediaEntityType;
isTrashed: boolean;
icon: string;
url?: string;
icon?: string;
}
export interface UmbMediaPathModel extends UmbEntityModel {

View File

@@ -15,6 +15,7 @@ import {
export class UmbPropertyEditorUIImageCropperElement extends UmbLitElement implements UmbPropertyEditorUiElement {
@property({ attribute: false })
value: UmbImageCropperPropertyEditorValue = {
temporaryFileId: null,
src: '',
crops: [],
focalPoint: { left: 0.5, top: 0.5 },
@@ -28,6 +29,7 @@ export class UmbPropertyEditorUIImageCropperElement extends UmbLitElement implem
if (changedProperties.has('value')) {
if (!this.value) {
this.value = {
temporaryFileId: null,
src: '',
crops: [],
focalPoint: { left: 0.5, top: 0.5 },