Skip to content
This repository was archived by the owner on Nov 6, 2025. It is now read-only.

Commit 8c2fdc7

Browse files
Merge branch 'main' into v15/feature/select-all-option-in-publishing-dialogs
2 parents dda7d8e + afc1c4d commit 8c2fdc7

File tree

13 files changed

+240
-84
lines changed

13 files changed

+240
-84
lines changed

.storybook/preview.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,12 @@ const documentTreeStoreProvider = (story) => html`
8888
export const decorators = [documentStoreProvider, documentTreeStoreProvider, dataTypeStoreProvider, storybookProvider];
8989

9090
export const parameters = {
91+
docs: {
92+
source: {
93+
excludeDecorators: true,
94+
format: 'html', // see storybook docs for more info on this format https://storybook.js.org/docs/api/doc-blocks/doc-block-source#format
95+
},
96+
},
9197
options: {
9298
storySort: {
9399
method: 'alphabetical',

src/packages/core/collection/default/collection-default.context.ts

Lines changed: 38 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -37,21 +37,21 @@ export class UmbDefaultCollectionContext<
3737
implements UmbCollectionContext, UmbApi
3838
{
3939
#config?: UmbCollectionConfiguration = { pageSize: 50 };
40-
#manifest?: ManifestCollection;
41-
#repository?: UmbCollectionRepository;
40+
protected _manifest?: ManifestCollection;
41+
protected _repository?: UmbCollectionRepository;
4242

4343
// TODO: replace with a state manager
44-
#loading = new UmbObjectState<boolean>(false);
45-
public readonly loading = this.#loading.asObservable();
44+
protected _loading = new UmbObjectState<boolean>(false);
45+
public readonly loading = this._loading.asObservable();
4646

47-
#items = new UmbArrayState<CollectionItemType>([], (x) => x.unique);
48-
public readonly items = this.#items.asObservable();
47+
protected _items = new UmbArrayState<CollectionItemType>([], (x) => x.unique);
48+
public readonly items = this._items.asObservable();
4949

50-
#totalItems = new UmbNumberState(0);
51-
public readonly totalItems = this.#totalItems.asObservable();
50+
protected _totalItems = new UmbNumberState(0);
51+
public readonly totalItems = this._totalItems.asObservable();
5252

53-
#filter = new UmbObjectState<FilterModelType | object>({});
54-
public readonly filter = this.#filter.asObservable();
53+
protected _filter = new UmbObjectState<FilterModelType | object>({});
54+
public readonly filter = this._filter.asObservable();
5555

5656
#userDefinedProperties = new UmbArrayState<UmbCollectionColumnConfiguration>([], (x) => x.alias);
5757
public readonly userDefinedProperties = this.#userDefinedProperties.asObservable();
@@ -69,7 +69,7 @@ export class UmbDefaultCollectionContext<
6969
#initResolver?: () => void;
7070
#initialized = false;
7171

72-
#init = new Promise<void>((resolve) => {
72+
protected _init = new Promise<void>((resolve) => {
7373
if (this.#initialized) {
7474
resolve();
7575
} else {
@@ -115,9 +115,9 @@ export class UmbDefaultCollectionContext<
115115
});
116116
}
117117

118-
#configured = false;
118+
protected _configured = false;
119119

120-
#configure() {
120+
protected _configure() {
121121
if (!this.#config) return;
122122

123123
this.selection.setMultiple(true);
@@ -126,9 +126,9 @@ export class UmbDefaultCollectionContext<
126126
this.pagination.setPageSize(this.#config.pageSize);
127127
}
128128

129-
const filterValue = this.#filter.getValue() as FilterModelType;
129+
const filterValue = this._filter.getValue() as FilterModelType;
130130

131-
this.#filter.setValue({
131+
this._filter.setValue({
132132
...this.#defaultFilter,
133133
...this.#config,
134134
...filterValue,
@@ -148,11 +148,11 @@ export class UmbDefaultCollectionContext<
148148

149149
this.view.setConfig(viewManagerConfig);
150150

151-
this.#configured = true;
151+
this._configured = true;
152152
}
153153

154154
#checkIfInitialized() {
155-
if (this.#repository) {
155+
if (this._repository) {
156156
this.#initialized = true;
157157
this.#initResolver?.();
158158
}
@@ -167,7 +167,7 @@ export class UmbDefaultCollectionContext<
167167
repositoryAlias,
168168
[this._host],
169169
(permitted, ctrl) => {
170-
this.#repository = permitted ? ctrl.api : undefined;
170+
this._repository = permitted ? ctrl.api : undefined;
171171
this.#checkIfInitialized();
172172
},
173173
);
@@ -193,12 +193,12 @@ export class UmbDefaultCollectionContext<
193193
}
194194

195195
public set manifest(manifest: ManifestCollection | undefined) {
196-
if (this.#manifest === manifest) return;
197-
this.#manifest = manifest;
198-
this.#observeRepository(this.#manifest?.meta.repositoryAlias);
196+
if (this._manifest === manifest) return;
197+
this._manifest = manifest;
198+
this.#observeRepository(this._manifest?.meta.repositoryAlias);
199199
}
200200
public get manifest() {
201-
return this.#manifest;
201+
return this._manifest;
202202
}
203203

204204
/**
@@ -207,24 +207,24 @@ export class UmbDefaultCollectionContext<
207207
* @memberof UmbCollectionContext
208208
*/
209209
public async requestCollection() {
210-
await this.#init;
210+
await this._init;
211211

212-
if (!this.#configured) this.#configure();
212+
if (!this._configured) this._configure();
213213

214-
if (!this.#repository) throw new Error(`Missing repository for ${this.#manifest}`);
214+
if (!this._repository) throw new Error(`Missing repository for ${this._manifest}`);
215215

216-
this.#loading.setValue(true);
216+
this._loading.setValue(true);
217217

218-
const filter = this.#filter.getValue();
219-
const { data } = await this.#repository.requestCollection(filter);
218+
const filter = this._filter.getValue();
219+
const { data } = await this._repository.requestCollection(filter);
220220

221221
if (data) {
222-
this.#items.setValue(data.items);
223-
this.#totalItems.setValue(data.total);
222+
this._items.setValue(data.items);
223+
this._totalItems.setValue(data.total);
224224
this.pagination.setTotalItems(data.total);
225225
}
226226

227-
this.#loading.setValue(false);
227+
this._loading.setValue(false);
228228
}
229229

230230
/**
@@ -233,7 +233,7 @@ export class UmbDefaultCollectionContext<
233233
* @memberof UmbCollectionContext
234234
*/
235235
public setFilter(filter: Partial<FilterModelType>) {
236-
this.#filter.setValue({ ...this.#filter.getValue(), ...filter });
236+
this._filter.setValue({ ...this._filter.getValue(), ...filter });
237237
this.requestCollection();
238238
}
239239

@@ -258,7 +258,7 @@ export class UmbDefaultCollectionContext<
258258
}
259259

260260
#onReloadStructureRequest = (event: UmbRequestReloadStructureForEntityEvent) => {
261-
const items = this.#items.getValue();
261+
const items = this._items.getValue();
262262
const hasItem = items.some((item) => item.unique === event.getUnique());
263263
if (hasItem) {
264264
this.requestCollection();
@@ -297,11 +297,11 @@ export class UmbDefaultCollectionContext<
297297
* @deprecated Use set the `.manifest` property instead.
298298
*/
299299
public setManifest(manifest: ManifestCollection | undefined) {
300-
if (this.#manifest === manifest) return;
301-
this.#manifest = manifest;
300+
if (this._manifest === manifest) return;
301+
this._manifest = manifest;
302302

303-
if (!this.#manifest) return;
304-
this.#observeRepository(this.#manifest.meta.repositoryAlias);
303+
if (!this._manifest) return;
304+
this.#observeRepository(this._manifest.meta.repositoryAlias);
305305
}
306306

307307
/**
@@ -311,6 +311,6 @@ export class UmbDefaultCollectionContext<
311311
* @deprecated Use get the `.manifest` property instead.
312312
*/
313313
public getManifest() {
314-
return this.#manifest;
314+
return this._manifest;
315315
}
316316
}

src/packages/core/temporary-file/components/temporary-file-badge.element.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,16 @@ export class UmbTemporaryFileBadgeElement extends UmbLitElement {
1919
return this._progress;
2020
}
2121

22+
@property({ type: Boolean, reflect: true })
23+
public complete = false;
24+
2225
override render() {
2326
return html`<uui-badge>
2427
<div id="wrapper">
25-
<uui-loader-circle progress=${this.progress}></uui-loader-circle>
26-
<uui-icon name="icon-arrow-up"></uui-icon>
28+
<uui-loader-circle .progress=${this.complete ? 100 : this.progress}></uui-loader-circle>
29+
${this.complete
30+
? html`<uui-icon name="icon-check"></uui-icon>`
31+
: html`<uui-icon name="icon-arrow-up"></uui-icon>`}
2732
</div>
2833
</uui-badge>`;
2934
}
@@ -42,8 +47,16 @@ export class UmbTemporaryFileBadgeElement extends UmbLitElement {
4247
font-size: var(--uui-size-6);
4348
}
4449
50+
:host([complete]) #wrapper {
51+
background-color: var(--uui-color-positive);
52+
}
53+
:host([complete]) uui-loader-circle {
54+
color: var(--uui-color-positive);
55+
}
56+
4557
uui-loader-circle {
4658
display: absolute;
59+
z-index: 2;
4760
inset: 0;
4861
color: var(--uui-color-focus);
4962
font-size: var(--uui-size-12);

src/packages/media/media/collection/media-collection.context.ts

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
import type { UmbMediaCollectionFilterModel, UmbMediaCollectionItemModel } from './types.js';
1+
import { UMB_MEDIA_PLACEHOLDER_ENTITY_TYPE } from '../entity.js';
2+
import type { UmbFileDropzoneItemStatus } from '../dropzone/types.js';
23
import { UMB_MEDIA_GRID_COLLECTION_VIEW_ALIAS } from './views/index.js';
4+
import type { UmbMediaCollectionFilterModel, UmbMediaCollectionItemModel } from './types.js';
35
import { UmbDefaultCollectionContext } from '@umbraco-cms/backoffice/collection';
46
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
5-
7+
import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api';
68
export class UmbMediaCollectionContext extends UmbDefaultCollectionContext<
79
UmbMediaCollectionItemModel,
810
UmbMediaCollectionFilterModel
@@ -13,9 +15,77 @@ export class UmbMediaCollectionContext extends UmbDefaultCollectionContext<
1315
*/
1416
public readonly thumbnailItems = this.items;
1517

18+
#placeholders = new UmbArrayState<UmbMediaCollectionItemModel>([], (x) => x.unique);
19+
public readonly placeholders = this.#placeholders.asObservable();
20+
1621
constructor(host: UmbControllerHost) {
1722
super(host, UMB_MEDIA_GRID_COLLECTION_VIEW_ALIAS);
1823
}
24+
25+
setPlaceholders(partial: Array<{ unique: string; status: UmbFileDropzoneItemStatus; name?: string }>) {
26+
const items = this._items.getValue();
27+
28+
// We do not want to set a placeholder which unique already exists in the collection.
29+
const date = new Date();
30+
const placeholders: Array<UmbMediaCollectionItemModel> = partial
31+
.filter((placeholder) => !items.find((item) => item.unique === placeholder.unique))
32+
.map((placeholder) => ({
33+
updateDate: date,
34+
createDate: date,
35+
entityType: UMB_MEDIA_PLACEHOLDER_ENTITY_TYPE,
36+
...placeholder,
37+
}))
38+
.reverse();
39+
this.#placeholders.setValue(placeholders);
40+
41+
this._items.setValue([...placeholders, ...items]);
42+
this._totalItems.setValue(placeholders.length + items.length);
43+
this.pagination.setTotalItems(placeholders.length + items.length);
44+
}
45+
46+
updatePlaceholderStatus(unique: string, status?: UmbFileDropzoneItemStatus) {
47+
this._items.updateOne(unique, { status });
48+
this.#placeholders.updateOne(unique, { status });
49+
}
50+
51+
/**
52+
* Requests the collection from the repository.
53+
* @returns {*}
54+
* @memberof UmbCollectionContext
55+
*/
56+
public override async requestCollection() {
57+
await this._init;
58+
59+
if (!this._configured) this._configure();
60+
61+
if (!this._repository) throw new Error(`Missing repository for ${this._manifest}`);
62+
63+
this._loading.setValue(true);
64+
65+
const filter = this._filter.getValue();
66+
const { data } = await this._repository.requestCollection(filter);
67+
68+
if (data) {
69+
this.#cleanupPlaceholdersFromCollection(data.items);
70+
const placeholders = this.#placeholders.getValue();
71+
72+
this._items.setValue([...placeholders, ...data.items]);
73+
this._totalItems.setValue(placeholders.length + data.total);
74+
this.pagination.setTotalItems(placeholders.length + data.total);
75+
}
76+
77+
this._loading.setValue(false);
78+
}
79+
80+
#cleanupPlaceholdersFromCollection(collection: Array<UmbMediaCollectionItemModel>) {
81+
const placeholderItems = this.#placeholders.getValue();
82+
83+
const dataSet = new Set(collection.map((item) => item.unique));
84+
const completedPlaceholders = placeholderItems.filter((item) => dataSet.has(item.unique));
85+
completedPlaceholders.forEach((placeholder) => {
86+
this.#placeholders.removeOne(placeholder.unique);
87+
});
88+
}
1989
}
2090

2191
export { UmbMediaCollectionContext as api };

src/packages/media/media/collection/media-collection.element.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { UMB_MEDIA_ENTITY_TYPE, UMB_MEDIA_ROOT_ENTITY_TYPE } from '../entity.js';
22
import { UMB_MEDIA_WORKSPACE_CONTEXT } from '../workspace/media-workspace.context-token.js';
3+
import { UmbFileDropzoneItemStatus, type UmbUploadableItem } from '../dropzone/types.js';
4+
import type { UmbDropzoneElement } from '../dropzone/dropzone.element.js';
35
import type { UmbMediaCollectionContext } from './media-collection.context.js';
46
import { UMB_MEDIA_COLLECTION_CONTEXT } from './media-collection.context-token.js';
5-
import { customElement, html, state, when } from '@umbraco-cms/backoffice/external/lit';
7+
import { customElement, html, query, state, when } from '@umbraco-cms/backoffice/external/lit';
68
import { UmbCollectionDefaultElement } from '@umbraco-cms/backoffice/collection';
79
import './media-collection-toolbar.element.js';
810
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
@@ -18,6 +20,9 @@ export class UmbMediaCollectionElement extends UmbCollectionDefaultElement {
1820
@state()
1921
private _unique: string | null = null;
2022

23+
@query('#dropzone')
24+
private _dropzone!: UmbDropzoneElement;
25+
2126
constructor() {
2227
super();
2328
this.consumeContext(UMB_MEDIA_COLLECTION_CONTEXT, (instance) => {
@@ -30,6 +35,32 @@ export class UmbMediaCollectionElement extends UmbCollectionDefaultElement {
3035
});
3136
}
3237

38+
#observeProgressItems() {
39+
this.observe(
40+
this._dropzone.progressItems(),
41+
(progressItems) => {
42+
progressItems.forEach((item) => {
43+
if (item.status === UmbFileDropzoneItemStatus.COMPLETE && !item.folder?.name) {
44+
// We do not update folders as it may have children still being uploaded.
45+
this.#mediaCollection?.updatePlaceholderStatus(item.unique, UmbFileDropzoneItemStatus.COMPLETE);
46+
}
47+
});
48+
},
49+
'_observeProgressItems',
50+
);
51+
}
52+
53+
async #setupPlaceholders(event: CustomEvent) {
54+
event.preventDefault();
55+
const uploadable = event.detail as Array<UmbUploadableItem>;
56+
const placeholders = uploadable
57+
.filter((p) => p.parentUnique === this._unique)
58+
.map((p) => ({ unique: p.unique, status: p.status, name: p.temporaryFile?.file.name ?? p.folder?.name }));
59+
60+
this.#mediaCollection?.setPlaceholders(placeholders);
61+
this.#observeProgressItems();
62+
}
63+
3364
async #onComplete() {
3465
this._progress = -1;
3566
this.#mediaCollection?.requestCollection();
@@ -54,8 +85,10 @@ export class UmbMediaCollectionElement extends UmbCollectionDefaultElement {
5485
<umb-media-collection-toolbar slot="header"></umb-media-collection-toolbar>
5586
${when(this._progress >= 0, () => html`<uui-loader-bar progress=${this._progress}></uui-loader-bar>`)}
5687
<umb-dropzone
88+
id="dropzone"
5789
multiple
5890
.parentUnique=${this._unique}
91+
@submitted=${this.#setupPlaceholders}
5992
@complete=${this.#onComplete}
6093
@progress=${this.#onProgress}></umb-dropzone>
6194
`;

0 commit comments

Comments
 (0)