Skip to content

Commit f1cdf50

Browse files
V15: Show server configuration when configuring the Upload Field (#18185)
* 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 * Update src/Umbraco.Web.UI.Client/src/packages/core/utils/bytes/bytes.function.ts Co-authored-by: Lee Kelleher <[email protected]> * chore: add end character to comment * feat: binds multiple text string to validation * chore: fixes event type * feat: adds new property editor ui for accepted file types * feat: changes the upload field to use the property editor ui for accepted file types * adds localization * Markup/style refactoring/streamlining * Renamed "Accepted Types" to "Accepted Upload Types" --------- Co-authored-by: Lee Kelleher <[email protected]> Co-authored-by: leekelleher <[email protected]>
1 parent ee231c7 commit f1cdf50

File tree

10 files changed

+251
-15
lines changed

10 files changed

+251
-15
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,8 @@ export default {
372372
fileSecurityValidationFailure: 'One or more file security validations have failed',
373373
moveToSameFolderFailed: 'Parent and destination folders cannot be the same',
374374
uploadNotAllowed: 'Upload is not allowed in this location.',
375+
noticeExtensionsServerOverride:
376+
'Regardless of the allowed file types, the following limitations apply system-wide due to the server configuration:',
375377
},
376378
member: {
377379
'2fa': 'Two-Factor Authentication',
@@ -885,6 +887,7 @@ export default {
885887
retrieve: 'Retrieve',
886888
retry: 'Retry',
887889
rights: 'Permissions',
890+
serverConfiguration: 'Server Configuration',
888891
scheduledPublishing: 'Scheduled Publishing',
889892
umbracoInfo: 'Umbraco info',
890893
search: 'Search',
@@ -2138,6 +2141,9 @@ export default {
21382141
numberMinimum: "Value must be greater than or equal to '%0%'.",
21392142
numberMaximum: "Value must be less than or equal to '%0%'.",
21402143
numberMisconfigured: "Minimum value '%0%' must be less than the maximum value '%1%'.",
2144+
invalidExtensions: 'One or more of the extensions are invalid.',
2145+
allowedExtensions: 'Allowed extensions are:',
2146+
disallowedExtensions: 'Disallowed extensions are:',
21412147
},
21422148
healthcheck: {
21432149
checkSuccessMessage: "Value is set to the recommended value: '%0%'.",

src/Umbraco.Web.UI.Client/src/packages/core/components/multiple-text-string-input/input-multiple-text-string-item.element.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,12 @@ export class UmbInputMultipleTextStringItemElement extends UUIFormControlMixin(U
6565
}
6666

6767
// Prevent valid events from bubbling outside the message element
68-
#onValid(event: any) {
68+
#onValid(event: Event) {
6969
event.stopPropagation();
7070
}
7171

7272
// Prevent invalid events from bubbling outside the message element
73-
#onInvalid(event: any) {
73+
#onInvalid(event: Event) {
7474
event.stopPropagation();
7575
}
7676

src/Umbraco.Web.UI.Client/src/packages/media/media/property-editors/upload-field/Umbraco.UploadField.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export const manifest: ManifestPropertyEditorSchema = {
1111
{
1212
alias: 'fileExtensions',
1313
label: 'Accepted file extensions',
14-
propertyEditorUiAlias: 'Umb.PropertyEditorUi.MultipleTextString',
14+
propertyEditorUiAlias: 'Umb.PropertyEditorUi.AcceptedUploadTypes',
1515
},
1616
],
1717
},
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './property-editor-ui-accepted-upload-types.element.js';
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import type { ManifestPropertyEditorUi } from '@umbraco-cms/backoffice/property-editor';
2+
3+
export const manifest: ManifestPropertyEditorUi = {
4+
type: 'propertyEditorUi',
5+
alias: 'Umb.PropertyEditorUi.AcceptedUploadTypes',
6+
name: 'Accepted Upload Types Property Editor UI',
7+
element: () => import('./property-editor-ui-accepted-upload-types.element.js'),
8+
meta: {
9+
label: 'Accepted Upload Types',
10+
propertyEditorSchemaAlias: 'Umbraco.MultipleTextstring',
11+
icon: 'icon-ordered-list',
12+
group: 'lists',
13+
supportsReadOnly: true,
14+
},
15+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import { UmbPropertyEditorUIMultipleTextStringElement } from '../multiple-text-string/property-editor-ui-multiple-text-string.element.js';
2+
import { css, customElement, html, nothing, state, when } from '@umbraco-cms/backoffice/external/lit';
3+
import { formatBytes } from '@umbraco-cms/backoffice/utils';
4+
import { UmbTemporaryFileConfigRepository } from '@umbraco-cms/backoffice/temporary-file';
5+
import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/property-editor';
6+
import type { UmbTemporaryFileConfigurationModel } from '@umbraco-cms/backoffice/temporary-file';
7+
8+
/**
9+
* @element umb-property-editor-ui-accepted-upload-types
10+
*/
11+
@customElement('umb-property-editor-ui-accepted-upload-types')
12+
export class UmbPropertyEditorUIAcceptedUploadTypesElement
13+
extends UmbPropertyEditorUIMultipleTextStringElement
14+
implements UmbPropertyEditorUiElement
15+
{
16+
#temporaryFileConfigRepository = new UmbTemporaryFileConfigRepository(this);
17+
18+
@state()
19+
protected _acceptedTypes: string[] = [];
20+
21+
@state()
22+
protected _disallowedTypes: string[] = [];
23+
24+
@state()
25+
protected _maxFileSize?: number | null;
26+
27+
override async connectedCallback() {
28+
super.connectedCallback();
29+
30+
await this.#temporaryFileConfigRepository.initialized;
31+
this.observe(this.#temporaryFileConfigRepository.all(), (config) => {
32+
if (!config) return;
33+
34+
this.#addValidators(config);
35+
36+
this._acceptedTypes = config.allowedUploadedFileExtensions;
37+
this._disallowedTypes = config.disallowedUploadedFilesExtensions;
38+
this._maxFileSize = config.maxFileSize ? config.maxFileSize * 1024 : null;
39+
});
40+
}
41+
42+
#addValidators(config: UmbTemporaryFileConfigurationModel) {
43+
this._inputElement?.addValidator(
44+
'badInput',
45+
() => {
46+
let message = this.localize.term('validation_invalidExtensions');
47+
if (config.allowedUploadedFileExtensions.length) {
48+
message += `<br>${this.localize.term('validation_allowedExtensions')} ${config.allowedUploadedFileExtensions.join(', ')}`;
49+
}
50+
if (config.disallowedUploadedFilesExtensions.length) {
51+
message += `<br>${this.localize.term('validation_disallowedExtensions')} ${config.disallowedUploadedFilesExtensions.join(', ')}`;
52+
}
53+
return message;
54+
},
55+
() => {
56+
const extensions = this._inputElement?.items;
57+
if (!extensions) return false;
58+
if (
59+
config.allowedUploadedFileExtensions.length &&
60+
!config.allowedUploadedFileExtensions.some((ext) => extensions.includes(ext))
61+
) {
62+
return true;
63+
}
64+
if (config.disallowedUploadedFilesExtensions.some((ext) => extensions.includes(ext))) {
65+
return true;
66+
}
67+
return false;
68+
},
69+
);
70+
}
71+
72+
#renderAcceptedTypes() {
73+
if (!this._acceptedTypes.length && !this._disallowedTypes.length && !this._maxFileSize) {
74+
return nothing;
75+
}
76+
77+
return html`
78+
<uui-box id="notice" headline=${this.localize.term('general_serverConfiguration')}>
79+
<p><umb-localize key="media_noticeExtensionsServerOverride"></umb-localize></p>
80+
${when(
81+
this._acceptedTypes.length,
82+
() => html`
83+
<p>
84+
<umb-localize key="validation_allowedExtensions"></umb-localize>
85+
<strong>${this._acceptedTypes.join(', ')}</strong>
86+
</p>
87+
`,
88+
)}
89+
${when(
90+
this._disallowedTypes.length,
91+
() => html`
92+
<p>
93+
<umb-localize key="validation_disallowedExtensions"></umb-localize>
94+
<strong>${this._disallowedTypes.join(', ')}</strong>
95+
</p>
96+
`,
97+
)}
98+
${when(
99+
this._maxFileSize,
100+
() => html`
101+
<p>
102+
${this.localize.term('media_maxFileSize')}
103+
<strong title="${this.localize.number(this._maxFileSize!)} bytes"
104+
>${formatBytes(this._maxFileSize!, { decimals: 2 })}</strong
105+
>.
106+
</p>
107+
`,
108+
)}
109+
</uui-box>
110+
`;
111+
}
112+
113+
override render() {
114+
return html`${this.#renderAcceptedTypes()} ${super.render()}`;
115+
}
116+
117+
static override readonly styles = [
118+
css`
119+
#notice {
120+
--uui-box-default-padding: var(--uui-size-space-4);
121+
--uui-box-header-padding: var(--uui-size-space-4);
122+
--uui-color-divider-standalone: var(--uui-color-warning-standalone);
123+
124+
border: 1px solid var(--uui-color-divider-standalone);
125+
background-color: var(--uui-color-warning);
126+
color: var(--uui-color-warning-contrast);
127+
margin-bottom: var(--uui-size-layout-1);
128+
129+
p {
130+
margin: 0.5rem 0;
131+
}
132+
}
133+
`,
134+
];
135+
}
136+
137+
export default UmbPropertyEditorUIAcceptedUploadTypesElement;
138+
139+
declare global {
140+
interface HTMLElementTagNameMap {
141+
'umb-property-editor-ui-accepted-upload-types': UmbPropertyEditorUIAcceptedUploadTypesElement;
142+
}
143+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import type { UmbPropertyEditorUIAcceptedUploadTypesElement } from './property-editor-ui-accepted-upload-types.element.js';
2+
import type { Meta, StoryFn } from '@storybook/web-components';
3+
import { html } from '@umbraco-cms/backoffice/external/lit';
4+
5+
import './property-editor-ui-accepted-types.element.js';
6+
7+
export default {
8+
title: 'Property Editor UIs/Accepted Types',
9+
component: 'umb-property-editor-ui-accepted-types',
10+
id: 'umb-property-editor-ui-accepted-types',
11+
} as Meta;
12+
13+
export const AAAOverview: StoryFn<UmbPropertyEditorUIAcceptedUploadTypesElement> = () =>
14+
html`<umb-property-editor-ui-accepted-upload-types></umb-property-editor-ui-accepted-upload-types>`;
15+
AAAOverview.storyName = 'Overview';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { UmbPropertyEditorUIAcceptedUploadTypesElement } from './property-editor-ui-accepted-upload-types.element.js';
2+
import { expect, fixture, html } from '@open-wc/testing';
3+
import { type UmbTestRunnerWindow, defaultA11yConfig } from '@umbraco-cms/internal/test-utils';
4+
5+
describe('UmbPropertyEditorUIUploadFieldElement', () => {
6+
let element: UmbPropertyEditorUIAcceptedUploadTypesElement;
7+
8+
beforeEach(async () => {
9+
element = await fixture(html`
10+
<umb-property-editor-ui-accepted-upload-types></umb-property-editor-ui-accepted-upload-types>
11+
`);
12+
});
13+
14+
it('is defined with its own instance', () => {
15+
expect(element).to.be.instanceOf(UmbPropertyEditorUIAcceptedUploadTypesElement);
16+
});
17+
18+
if ((window as UmbTestRunnerWindow).__UMBRACO_TEST_RUN_A11Y_TEST) {
19+
it('passes the a11y audit', async () => {
20+
await expect(element).shadowDom.to.be.accessible(defaultA11yConfig);
21+
});
22+
}
23+
});

src/Umbraco.Web.UI.Client/src/packages/property-editors/manifests.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { manifest as acceptedType } from './accepted-types/manifests.js';
12
import { manifest as colorEditor } from './color-swatches-editor/manifests.js';
23
import { manifest as numberRange } from './number-range/manifests.js';
34
import { manifest as orderDirection } from './order-direction/manifests.js';
@@ -38,6 +39,7 @@ export const manifests: Array<UmbExtensionManifest> = [
3839
...textBoxManifests,
3940
...toggleManifests,
4041
...contentPickerManifests,
42+
acceptedType,
4143
colorEditor,
4244
numberRange,
4345
orderDirection,

src/Umbraco.Web.UI.Client/src/packages/property-editors/multiple-text-string/property-editor-ui-multiple-text-string.element.ts

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
1-
import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor';
2-
import { customElement, html, property, state } from '@umbraco-cms/backoffice/external/lit';
1+
import { customElement, html, property, query, state } from '@umbraco-cms/backoffice/external/lit';
2+
import { umbBindToValidation, UmbValidationContext } from '@umbraco-cms/backoffice/validation';
33
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
4+
import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor';
5+
import { UMB_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/property';
6+
import {
7+
UMB_SUBMITTABLE_WORKSPACE_CONTEXT,
8+
UmbSubmittableWorkspaceContextBase,
9+
} from '@umbraco-cms/backoffice/workspace';
410
import type { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
511
import type { UmbInputMultipleTextStringElement } from '@umbraco-cms/backoffice/components';
612
import type {
713
UmbPropertyEditorConfigCollection,
814
UmbPropertyEditorUiElement,
915
} from '@umbraco-cms/backoffice/property-editor';
10-
import { UMB_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/property';
1116

1217
/**
1318
* @element umb-property-editor-ui-multiple-text-string
@@ -60,11 +65,23 @@ export class UmbPropertyEditorUIMultipleTextStringElement extends UmbLitElement
6065
@state()
6166
private _max = Infinity;
6267

68+
@query('#input', true)
69+
protected _inputElement?: UmbInputMultipleTextStringElement;
70+
71+
protected _validationContext = new UmbValidationContext(this);
72+
6373
constructor() {
6474
super();
75+
6576
this.consumeContext(UMB_PROPERTY_CONTEXT, (context) => {
6677
this._label = context.getLabel();
6778
});
79+
80+
this.consumeContext(UMB_SUBMITTABLE_WORKSPACE_CONTEXT, (context) => {
81+
if (context instanceof UmbSubmittableWorkspaceContextBase) {
82+
context.addValidationContext(this._validationContext);
83+
}
84+
});
6885
}
6986

7087
protected override firstUpdated() {
@@ -83,17 +100,31 @@ export class UmbPropertyEditorUIMultipleTextStringElement extends UmbLitElement
83100
this.dispatchEvent(new UmbPropertyValueChangeEvent());
84101
}
85102

103+
// Prevent valid events from bubbling outside the message element
104+
#onValid(event: Event) {
105+
event.stopPropagation();
106+
}
107+
108+
// Prevent invalid events from bubbling outside the message element
109+
#onInvalid(event: Event) {
110+
event.stopPropagation();
111+
}
112+
86113
override render() {
87114
return html`
88-
<umb-input-multiple-text-string
89-
max=${this._max}
90-
min=${this._min}
91-
.items=${this.value ?? []}
92-
?disabled=${this.disabled}
93-
?readonly=${this.readonly}
94-
?required=${this.required}
95-
@change=${this.#onChange}>
96-
</umb-input-multiple-text-string>
115+
<umb-form-validation-message id="validation-message" @invalid=${this.#onInvalid} @valid=${this.#onValid}>
116+
<umb-input-multiple-text-string
117+
id="input"
118+
max=${this._max}
119+
min=${this._min}
120+
.items=${this.value ?? []}
121+
?disabled=${this.disabled}
122+
?readonly=${this.readonly}
123+
?required=${this.required}
124+
@change=${this.#onChange}
125+
${umbBindToValidation(this)}>
126+
</umb-input-multiple-text-string>
127+
</umb-form-validation-message>
97128
`;
98129
}
99130
}

0 commit comments

Comments
 (0)