Skip to content

Commit 22d974e

Browse files
authored
V15: Client should validate maxFileSize and allowed/disallowed file types from server configuration (#18163)
* feat: shows notification when no suitable media type is found * chore: rearrange imports * feat: use a forward ref to find the dropzone * chore: rearrange imports * chore(mock): send back correct header * feat: avoid using the context consumer to get a token, but instead mimick the OpenAPI generator * chore(mock): allow more file types * chore(mock): create more upload fields * chore(mock): also look for mediaPicker fields * chore(mock): improve media mock db * chore(mock): add missing endpoints * chore(mock): update media data * chore(mock): fix aliases for media grid and table * chore(mock): add urls to media * chore(mock): adds missing endpoint for imaging * fix: reverse order of properties to overwrite existing status * feat: listen to progress updates on upload and update the `progress` property * feat: adds tracking of upload progress to placeholders * feat: bind the progress number up on the temporary file badge to indicate upload status * feat: optimises progress calculation and makes the badge bigger to be able to show the progress in percent * feat: allow text to be normal * chore: use correct localization * feat: shows error status for anything that isn't waiting or complete * feat: makes `progress` optional * feat: adds repository+store for temporary file configuration * chore(mock): adds mock endpoint for temporary file configuration * feat: set progress for createTemporaryFiles * feat: allows a `whitespace` option to notifications * feat: validates uploads before trying to query the server * feat: adds `formatBytes` function to format numbers * chore: export all consts * feat: exports bytes function * feat: set decimals to default to 2, which works nicely with the Intl numberformat * feat: use `formatBytes` to format the error message * chore(mock): set max file size for mock to 1.4 GB * feat: adds localization
1 parent 1752be9 commit 22d974e

File tree

22 files changed

+299
-8
lines changed

22 files changed

+299
-8
lines changed

src/Umbraco.Web.UI.Client/src/assets/lang/da-dk.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,9 @@ export default {
345345
clickToUpload: 'Klik for at uploade',
346346
orClickHereToUpload: 'eller klik her for at vælge filer',
347347
disallowedFileType: 'Kan ikke uploade denne fil, den har ikke en godkendt filtype',
348+
disallowedMediaType: "Kan ikke uploade denne fil, mediatypen med alias '%0%' er ikke tilladt her",
349+
invalidFileName: 'Kan ikke uploade denne fil, den har et ugyldigt filnavn',
350+
invalidFileSize: 'Kan ikke uploade denne fil, den er for stor',
348351
maxFileSize: 'Maks filstørrelse er',
349352
mediaRoot: 'Medie rod',
350353
moveToSameFolderFailed: 'Overordnet og destinations mappe kan ikke være den samme',
@@ -353,8 +356,6 @@ export default {
353356
dragAndDropYourFilesIntoTheArea:
354357
'Træk dine filer ind i dropzonen for, at uploade dem til\n mediebiblioteket.\n ',
355358
uploadNotAllowed: 'Upload er ikke tiladt på denne lokation',
356-
disallowedMediaType: "Cannot upload this file, the media type with alias '%0%' is not allowed here",
357-
invalidFileName: 'Cannot upload this file, it does not have a valid file name',
358359
},
359360
member: {
360361
createNewMember: 'Opret et nyt medlem',

src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,7 @@ export default {
376376
disallowedFileType: 'Cannot upload this file, it does not have an approved file type',
377377
disallowedMediaType: "Cannot upload this file, the media type with alias '%0%' is not allowed here",
378378
invalidFileName: 'Cannot upload this file, it does not have a valid file name',
379+
invalidFileSize: 'Cannot upload this file, it is too large',
379380
maxFileSize: 'Max file size is',
380381
mediaRoot: 'Media root',
381382
createFolderFailed: 'Failed to create a folder under parent id %0%',

src/Umbraco.Web.UI.Client/src/assets/lang/en.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,7 @@ export default {
363363
disallowedFileType: 'Cannot upload this file, it does not have an approved file type',
364364
disallowedMediaType: "Cannot upload this file, the media type with alias '%0%' is not allowed here",
365365
invalidFileName: 'Cannot upload this file, it does not have a valid file name',
366+
invalidFileSize: 'Cannot upload this file, it is too large',
366367
maxFileSize: 'Max file size is',
367368
mediaRoot: 'Media root',
368369
createFolderFailed: 'Failed to create a folder under parent id %0%',

src/Umbraco.Web.UI.Client/src/mocks/handlers/temporary-file/temporary-file.handlers.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
const { rest } = window.MockServiceWorker;
22
import { umbracoPath } from '@umbraco-cms/backoffice/utils';
3-
import type { PostTemporaryFileResponse } from '@umbraco-cms/backoffice/external/backend-api';
3+
import type {
4+
GetTemporaryFileConfigurationResponse,
5+
PostTemporaryFileResponse,
6+
} from '@umbraco-cms/backoffice/external/backend-api';
47
import { UmbId } from '@umbraco-cms/backoffice/id';
58

69
const UMB_SLUG = 'temporary-file';
@@ -15,4 +18,16 @@ export const handlers = [
1518
ctx.text<PostTemporaryFileResponse>(guid),
1619
);
1720
}),
21+
22+
rest.get(umbracoPath(`/${UMB_SLUG}/configuration`), async (_req, res, ctx) => {
23+
return res(
24+
ctx.delay(),
25+
ctx.json<GetTemporaryFileConfigurationResponse>({
26+
allowedUploadedFileExtensions: [],
27+
disallowedUploadedFilesExtensions: ['exe', 'dll', 'bat', 'msi'],
28+
maxFileSize: 1468007,
29+
imageFileTypes: ['jpg', 'png', 'gif', 'jpeg', 'svg'],
30+
}),
31+
);
32+
}),
1833
];

src/Umbraco.Web.UI.Client/src/packages/core/manifests.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { manifests as propertyTypeManifests } from './property-type/manifests.js
1919
import { manifests as recycleBinManifests } from './recycle-bin/manifests.js';
2020
import { manifests as sectionManifests } from './section/manifests.js';
2121
import { manifests as serverFileSystemManifests } from './server-file-system/manifests.js';
22+
import { manifests as temporaryFileManifests } from './temporary-file/manifests.js';
2223
import { manifests as themeManifests } from './themes/manifests.js';
2324
import { manifests as treeManifests } from './tree/manifests.js';
2425
import { manifests as workspaceManifests } from './workspace/manifests.js';
@@ -47,6 +48,7 @@ export const manifests: Array<UmbExtensionManifest | UmbExtensionManifestKind> =
4748
...recycleBinManifests,
4849
...sectionManifests,
4950
...serverFileSystemManifests,
51+
...temporaryFileManifests,
5052
...themeManifests,
5153
...treeManifests,
5254
...workspaceManifests,

src/Umbraco.Web.UI.Client/src/packages/core/notification/layouts/default/notification-layout-default.element.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
ifDefined,
77
nothing,
88
css,
9+
styleMap,
910
} from '@umbraco-cms/backoffice/external/lit';
1011
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
1112
import type { UmbNotificationDefaultData, UmbNotificationHandler } from '@umbraco-cms/backoffice/notification';
@@ -23,7 +24,7 @@ export class UmbNotificationLayoutDefaultElement extends LitElement {
2324
override render() {
2425
return html`
2526
<uui-toast-notification-layout id="layout" headline="${ifDefined(this.data.headline)}" class="uui-text">
26-
<div id="message">${this.data.message}</div>
27+
<div id="message" style=${styleMap({ whiteSpace: this.data.whitespace })}>${this.data.message}</div>
2728
${this.#renderStructuredList(this.data.structuredList)}
2829
</uui-toast-notification-layout>
2930
`;

src/Umbraco.Web.UI.Client/src/packages/core/notification/notification.context.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export interface UmbNotificationDefaultData {
1212
message: string;
1313
headline?: string;
1414
structuredList?: Record<string, Array<unknown>>;
15+
whitespace?: 'normal' | 'pre-line' | 'pre-wrap' | 'nowrap' | 'pre';
1516
}
1617

1718
/**
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import type { UmbTemporaryFileConfigurationModel } from '../types.js';
2+
import { UmbTemporaryFileConfigServerDataSource } from './config.server.data-source.js';
3+
import { UMB_TEMPORARY_FILE_CONFIG_STORE_CONTEXT } from './config.store.token.js';
4+
import { UMB_TEMPORARY_FILE_REPOSITORY_ALIAS } from './constants.js';
5+
import { UmbRepositoryBase } from '@umbraco-cms/backoffice/repository';
6+
import type { UmbApi } from '@umbraco-cms/backoffice/extension-api';
7+
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
8+
import type { Observable } from '@umbraco-cms/backoffice/observable-api';
9+
10+
export class UmbTemporaryFileConfigRepository extends UmbRepositoryBase implements UmbApi {
11+
/**
12+
* Promise that resolves when the repository has been initialized, i.e. when the configuration has been fetched from the server.
13+
*/
14+
initialized: Promise<void>;
15+
16+
#dataStore?: typeof UMB_TEMPORARY_FILE_CONFIG_STORE_CONTEXT.TYPE;
17+
#dataSource = new UmbTemporaryFileConfigServerDataSource(this);
18+
19+
constructor(host: UmbControllerHost) {
20+
super(host, UMB_TEMPORARY_FILE_REPOSITORY_ALIAS.toString());
21+
this.initialized = new Promise<void>((resolve) => {
22+
this.consumeContext(UMB_TEMPORARY_FILE_CONFIG_STORE_CONTEXT, async (store) => {
23+
this.#dataStore = store;
24+
await this.#init();
25+
resolve();
26+
});
27+
});
28+
}
29+
30+
async #init() {
31+
// Check if the store already has data
32+
if (this.#dataStore?.getState()) {
33+
return;
34+
}
35+
36+
const { data } = await this.#dataSource.getConfig();
37+
38+
if (data) {
39+
this.#dataStore?.update(data);
40+
}
41+
}
42+
43+
/**
44+
* Subscribe to the entire configuration.
45+
*/
46+
all() {
47+
if (!this.#dataStore) {
48+
throw new Error('Data store not initialized');
49+
}
50+
51+
return this.#dataStore.all();
52+
}
53+
54+
/**
55+
* Subscribe to a part of the configuration.
56+
* @param part
57+
*/
58+
part<Part extends keyof UmbTemporaryFileConfigurationModel>(
59+
part: Part,
60+
): Observable<UmbTemporaryFileConfigurationModel[Part]> {
61+
if (!this.#dataStore) {
62+
throw new Error('Data store not initialized');
63+
}
64+
65+
return this.#dataStore.part(part);
66+
}
67+
}
68+
69+
export default UmbTemporaryFileConfigRepository;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
2+
import { TemporaryFileService } from '@umbraco-cms/backoffice/external/backend-api';
3+
import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources';
4+
5+
export class UmbTemporaryFileConfigServerDataSource {
6+
#host;
7+
8+
constructor(host: UmbControllerHost) {
9+
this.#host = host;
10+
}
11+
12+
/**
13+
* Get the temporary file configuration.
14+
*/
15+
getConfig() {
16+
return tryExecuteAndNotify(this.#host, TemporaryFileService.getTemporaryFileConfiguration());
17+
}
18+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import type { UmbTemporaryFileConfigStore } from './config.store.js';
2+
import { UMB_TEMPORARY_FILE_CONFIG_STORE_ALIAS } from './constants.js';
3+
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
4+
5+
export const UMB_TEMPORARY_FILE_CONFIG_STORE_CONTEXT = new UmbContextToken<UmbTemporaryFileConfigStore>(
6+
UMB_TEMPORARY_FILE_CONFIG_STORE_ALIAS,
7+
);

0 commit comments

Comments
 (0)