Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V15: Show server configuration when configuring the Upload Field #18185

Open
wants to merge 46 commits into
base: v15/dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
d5a021d
feat: shows notification when no suitable media type is found
iOvergaard Jan 28, 2025
455992a
chore: rearrange imports
iOvergaard Jan 28, 2025
4f6b4b7
feat: use a forward ref to find the dropzone
iOvergaard Jan 28, 2025
343e771
chore: rearrange imports
iOvergaard Jan 28, 2025
11976a0
chore(mock): send back correct header
iOvergaard Jan 28, 2025
37b3c94
feat: avoid using the context consumer to get a token, but instead mi…
iOvergaard Jan 28, 2025
badf18f
chore(mock): allow more file types
iOvergaard Jan 28, 2025
a79aa2c
chore(mock): create more upload fields
iOvergaard Jan 28, 2025
61abee4
chore(mock): also look for mediaPicker fields
iOvergaard Jan 28, 2025
4990a84
chore(mock): improve media mock db
iOvergaard Jan 28, 2025
9f821e5
chore(mock): add missing endpoints
iOvergaard Jan 28, 2025
38720c0
chore(mock): update media data
iOvergaard Jan 28, 2025
9addb3e
chore(mock): fix aliases for media grid and table
iOvergaard Jan 28, 2025
7cf4d59
chore(mock): add urls to media
iOvergaard Jan 28, 2025
dda7b79
chore(mock): adds missing endpoint for imaging
iOvergaard Jan 28, 2025
a12ec27
fix: reverse order of properties to overwrite existing status
iOvergaard Jan 28, 2025
ddbe415
feat: listen to progress updates on upload and update the `progress` …
iOvergaard Jan 28, 2025
900f57c
feat: adds tracking of upload progress to placeholders
iOvergaard Jan 28, 2025
647232a
feat: bind the progress number up on the temporary file badge to indi…
iOvergaard Jan 28, 2025
c0d67e5
feat: optimises progress calculation and makes the badge bigger to be…
iOvergaard Jan 28, 2025
f57d1ae
feat: allow text to be normal
iOvergaard Jan 28, 2025
e1e5408
chore: use correct localization
iOvergaard Jan 28, 2025
f8a9791
feat: shows error status for anything that isn't waiting or complete
iOvergaard Jan 28, 2025
ea3ea33
feat: makes `progress` optional
iOvergaard Jan 28, 2025
fa75e67
feat: adds repository+store for temporary file configuration
iOvergaard Jan 29, 2025
207d681
chore(mock): adds mock endpoint for temporary file configuration
iOvergaard Jan 29, 2025
b1a6125
feat: set progress for createTemporaryFiles
iOvergaard Jan 29, 2025
867da12
Merge branch 'v15/feature/media-library-drop-progress' into v15/featu…
iOvergaard Jan 29, 2025
966669b
Merge remote-tracking branch 'origin/v15/dev' into v15/feature/tempor…
iOvergaard Jan 29, 2025
66cf11d
feat: allows a `whitespace` option to notifications
iOvergaard Jan 29, 2025
bc58d72
feat: validates uploads before trying to query the server
iOvergaard Jan 29, 2025
4ba5aa5
feat: adds `formatBytes` function to format numbers
iOvergaard Jan 29, 2025
5fa5e09
chore: export all consts
iOvergaard Jan 29, 2025
a60dbc0
feat: exports bytes function
iOvergaard Jan 29, 2025
b1b655d
feat: set decimals to default to 2, which works nicely with the Intl …
iOvergaard Jan 29, 2025
d43127a
feat: use `formatBytes` to format the error message
iOvergaard Jan 29, 2025
72a0490
chore(mock): set max file size for mock to 1.4 GB
iOvergaard Jan 29, 2025
660334d
feat: adds localization
iOvergaard Jan 29, 2025
60c344e
Update src/Umbraco.Web.UI.Client/src/packages/core/utils/bytes/bytes.…
iOvergaard Jan 30, 2025
2c2206c
chore: add end character to comment
iOvergaard Jan 30, 2025
b6379fb
feat: binds multiple text string to validation
iOvergaard Jan 30, 2025
fce920f
chore: fixes event type
iOvergaard Jan 30, 2025
bcca6ec
feat: adds new property editor ui for accepted file types
iOvergaard Jan 30, 2025
9299cb3
feat: changes the upload field to use the property editor ui for acce…
iOvergaard Jan 30, 2025
efd9f3f
adds localization
iOvergaard Jan 31, 2025
72557cc
Merge remote-tracking branch 'origin/v15/dev' into v15/feature/dropzo…
iOvergaard Jan 31, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/Umbraco.Web.UI.Client/src/assets/lang/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,8 @@ export default {
fileSecurityValidationFailure: 'One or more file security validations have failed',
moveToSameFolderFailed: 'Parent and destination folders cannot be the same',
uploadNotAllowed: 'Upload is not allowed in this location.',
noticeExtensionsServerOverride:
'Regardless of the allowed file types, the following limitations apply system-wide due to the server configuration:',
},
member: {
'2fa': 'Two-Factor Authentication',
Expand Down Expand Up @@ -885,6 +887,7 @@ export default {
retrieve: 'Retrieve',
retry: 'Retry',
rights: 'Permissions',
serverConfiguration: 'Server Configuration',
scheduledPublishing: 'Scheduled Publishing',
umbracoInfo: 'Umbraco info',
search: 'Search',
Expand Down Expand Up @@ -2138,6 +2141,9 @@ export default {
numberMinimum: "Value must be greater than or equal to '%0%'.",
numberMaximum: "Value must be less than or equal to '%0%'.",
numberMisconfigured: "Minimum value '%0%' must be less than the maximum value '%1%'.",
invalidExtensions: 'One or more of the extensions are invalid.',
allowedExtensions: 'Allowed extensions are:',
disallowedExtensions: 'Disallowed extensions are:',
},
healthcheck: {
checkSuccessMessage: "Value is set to the recommended value: '%0%'.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,12 @@ export class UmbInputMultipleTextStringItemElement extends UUIFormControlMixin(U
}

// Prevent valid events from bubbling outside the message element
#onValid(event: any) {
#onValid(event: Event) {
event.stopPropagation();
}

// Prevent invalid events from bubbling outside the message element
#onInvalid(event: any) {
#onInvalid(event: Event) {
event.stopPropagation();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export class UmbTemporaryFileManager<
headline: 'Upload',
message: `
${this.#localization.term('media_invalidFileSize')}: ${item.file.name} (${formatBytes(item.file.size)}).

${this.#localization.term('media_maxFileSize')} ${formatBytes(maxFileSize)}.
`,
whitespace: 'pre-line',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const manifest: ManifestPropertyEditorSchema = {
{
alias: 'fileExtensions',
label: 'Accepted file extensions',
propertyEditorUiAlias: 'Umb.PropertyEditorUi.MultipleTextString',
propertyEditorUiAlias: 'Umb.PropertyEditorUi.AcceptedTypes',
},
],
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './property-editor-ui-accepted-types.element.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { ManifestPropertyEditorUi } from '@umbraco-cms/backoffice/property-editor';

export const manifest: ManifestPropertyEditorUi = {
type: 'propertyEditorUi',
alias: 'Umb.PropertyEditorUi.AcceptedTypes',
name: 'Accepted Types Property Editor UI',
element: () => import('./property-editor-ui-accepted-types.element.js'),
meta: {
label: 'Accepted Upload Types',
propertyEditorSchemaAlias: 'Umbraco.MultipleTextstring',
icon: 'icon-ordered-list',
group: 'lists',
supportsReadOnly: true,
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { UmbPropertyEditorUIMultipleTextStringElement } from '../multiple-text-string/property-editor-ui-multiple-text-string.element.js';
import { css, customElement, html, nothing, state } from '@umbraco-cms/backoffice/external/lit';
import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/property-editor';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import {
UmbTemporaryFileConfigRepository,
type UmbTemporaryFileConfigurationModel,
} from '@umbraco-cms/backoffice/temporary-file';
import { formatBytes } from '@umbraco-cms/backoffice/utils';

/**
* @element umb-property-editor-ui-accepted-types
*/
@customElement('umb-property-editor-ui-accepted-types')
export class UmbPropertyEditorUIAcceptedTypesElement
extends UmbPropertyEditorUIMultipleTextStringElement
implements UmbPropertyEditorUiElement
{
@state()
protected _acceptedTypes: string[] = [];

@state()
protected _disallowedTypes: string[] = [];

@state()
protected _maxFileSize?: number | null;

#temporaryFileConfigRepository = new UmbTemporaryFileConfigRepository(this);

override async connectedCallback() {
super.connectedCallback();

await this.#temporaryFileConfigRepository.initialized;
this.observe(this.#temporaryFileConfigRepository.all(), (config) => {
if (!config) return;

this.#addValidators(config);

this._acceptedTypes = config.allowedUploadedFileExtensions;
this._disallowedTypes = config.disallowedUploadedFilesExtensions;
this._maxFileSize = config.maxFileSize ? config.maxFileSize * 1024 : null;
});
}

#addValidators(config: UmbTemporaryFileConfigurationModel) {
this._inputElement?.addValidator(
'badInput',
() => {
let message = this.localize.term('validation_invalidExtensions');
if (config.allowedUploadedFileExtensions.length) {
message += ` ${this.localize.term('validation_allowedExtensions')} ${config.allowedUploadedFileExtensions.join(', ')}`;
}
if (config.disallowedUploadedFilesExtensions.length) {
message += ` ${this.localize.term('validation_disallowedExtensions')} ${config.disallowedUploadedFilesExtensions.join(', ')}`;
}
return message;
},
() => {
const extensions = this._inputElement?.items;
if (!extensions) return false;
if (
config.allowedUploadedFileExtensions.length &&
!config.allowedUploadedFileExtensions.some((ext) => extensions.includes(ext))
) {
return true;
}
if (config.disallowedUploadedFilesExtensions.some((ext) => extensions.includes(ext))) {
return true;
}
return false;
},
);
}

Check warning on line 73 in src/Umbraco.Web.UI.Client/src/packages/property-editors/accepted-types/property-editor-ui-accepted-types.element.ts

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v15/dev)

❌ New issue: Complex Method

addValidators has a cyclomatic complexity of 9, threshold = 9. This function has many conditional statements (e.g. if, for, while), leading to lower code health. Avoid adding more conditionals and code to it without refactoring.

#renderAcceptedTypes() {
if (!this._acceptedTypes.length && !this._disallowedTypes.length && !this._maxFileSize) {

Check warning on line 76 in src/Umbraco.Web.UI.Client/src/packages/property-editors/accepted-types/property-editor-ui-accepted-types.element.ts

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (v15/dev)

❌ New issue: Complex Conditional

renderAcceptedTypes has 1 complex conditionals with 2 branches, threshold = 2. A complex conditional is an expression inside a branch (e.g. if, for, while) which consists of multiple, logical operators such as AND/OR. The more logical operators in an expression, the more severe the code smell.
return nothing;
}
return html`
<uui-box id="notice" headline=${this.localize.term('general_serverConfiguration')}>
<p>${this.localize.term('media_noticeExtensionsServerOverride')}</p>
${this._acceptedTypes.length
? html`<p>
${this.localize.term('validation_allowedExtensions')} <strong>${this._acceptedTypes.join(', ')}</strong>
</p>`
: nothing}
${this._disallowedTypes.length
? html`<p>
${this.localize.term('validation_disallowedExtensions')}
<strong>${this._disallowedTypes.join(', ')}</strong>
</p>`
: nothing}
${this._maxFileSize
? html`
<p>
${this.localize.term('media_maxFileSize')}
<strong title="${this.localize.number(this._maxFileSize)} bytes">
${formatBytes(this._maxFileSize, { decimals: 2 })} </strong
>.
</p>
`
: nothing}
</uui-box>
`;
}

override render() {
return html` ${this.#renderAcceptedTypes()} ${super.render()} `;
}

static override readonly styles = [
UmbTextStyles,
css`
#notice {
--uui-color-divider-standalone: var(--uui-color-warning-standalone);
border: 1px solid var(--uui-color-divider-standalone);
background-color: var(--uui-color-warning);
color: var(--uui-color-warning-contrast);
margin-bottom: var(--uui-size-layout-1);
`,
];
}

export default UmbPropertyEditorUIAcceptedTypesElement;

declare global {
interface HTMLElementTagNameMap {
'umb-property-editor-ui-accepted-types': UmbPropertyEditorUIAcceptedTypesElement;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { UmbPropertyEditorUIAcceptedTypesElement } from './property-editor-ui-accepted-types.element.js';
import type { Meta, StoryFn } from '@storybook/web-components';
import { html } from '@umbraco-cms/backoffice/external/lit';

import './property-editor-ui-accepted-types.element.js';

export default {
title: 'Property Editor UIs/Accepted Types',
component: 'umb-property-editor-ui-accepted-types',
id: 'umb-property-editor-ui-accepted-types',
} as Meta;

export const AAAOverview: StoryFn<UmbPropertyEditorUIAcceptedTypesElement> = () =>
html`<umb-property-editor-ui-accepted-types></umb-property-editor-ui-accepted-types>`;
AAAOverview.storyName = 'Overview';
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { UmbPropertyEditorUIAcceptedTypesElement } from './property-editor-ui-accepted-types.element.js';
import { expect, fixture, html } from '@open-wc/testing';
import { type UmbTestRunnerWindow, defaultA11yConfig } from '@umbraco-cms/internal/test-utils';

describe('UmbPropertyEditorUIUploadFieldElement', () => {
let element: UmbPropertyEditorUIAcceptedTypesElement;

beforeEach(async () => {
element = await fixture(html` <umb-property-editor-ui-accepted-types></umb-property-editor-ui-accepted-types> `);
});

it('is defined with its own instance', () => {
expect(element).to.be.instanceOf(UmbPropertyEditorUIAcceptedTypesElement);
});

if ((window as UmbTestRunnerWindow).__UMBRACO_TEST_RUN_A11Y_TEST) {
it('passes the a11y audit', async () => {
await expect(element).shadowDom.to.be.accessible(defaultA11yConfig);
});
}
});
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { manifest as acceptedType } from './accepted-types/manifests.js';
import { manifest as colorEditor } from './color-swatches-editor/manifests.js';
import { manifest as numberRange } from './number-range/manifests.js';
import { manifest as orderDirection } from './order-direction/manifests.js';
Expand Down Expand Up @@ -38,6 +39,7 @@ export const manifests: Array<UmbExtensionManifest> = [
...textBoxManifests,
...toggleManifests,
...contentPickerManifests,
acceptedType,
colorEditor,
numberRange,
orderDirection,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor';
import { customElement, html, property, state } from '@umbraco-cms/backoffice/external/lit';
import { customElement, html, property, query, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import type { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
import type { UmbInputMultipleTextStringElement } from '@umbraco-cms/backoffice/components';
import type {
UmbPropertyEditorConfigCollection,
UmbPropertyEditorUiElement,
} from '@umbraco-cms/backoffice/property-editor';
import { umbBindToValidation, UmbValidationContext } from '@umbraco-cms/backoffice/validation';
import {
UMB_SUBMITTABLE_WORKSPACE_CONTEXT,
UmbSubmittableWorkspaceContextBase,
} from '@umbraco-cms/backoffice/workspace';

/**
* @element umb-property-editor-ui-multiple-text-string
Expand Down Expand Up @@ -56,24 +61,53 @@ export class UmbPropertyEditorUIMultipleTextStringElement extends UmbLitElement
@state()
private _max = Infinity;

@query('#input', true)
protected _inputElement?: UmbInputMultipleTextStringElement;

protected _validationContext = new UmbValidationContext(this);

constructor() {
super();

this.consumeContext(UMB_SUBMITTABLE_WORKSPACE_CONTEXT, (context) => {
if (context instanceof UmbSubmittableWorkspaceContextBase) {
context.addValidationContext(this._validationContext);
}
});
}

#onChange(event: UmbChangeEvent) {
event.stopPropagation();
const target = event.currentTarget as UmbInputMultipleTextStringElement;
this.value = target.items;
this.dispatchEvent(new UmbPropertyValueChangeEvent());
}

// Prevent valid events from bubbling outside the message element
#onValid(event: Event) {
event.stopPropagation();
}

// Prevent invalid events from bubbling outside the message element
#onInvalid(event: Event) {
event.stopPropagation();
}

override render() {
return html`
<umb-input-multiple-text-string
max=${this._max}
min=${this._min}
.items=${this.value ?? []}
?disabled=${this.disabled}
?readonly=${this.readonly}
?required=${this.required}
@change=${this.#onChange}>
</umb-input-multiple-text-string>
<umb-form-validation-message id="validation-message" @invalid=${this.#onInvalid} @valid=${this.#onValid}>
<umb-input-multiple-text-string
id="input"
max=${this._max}
min=${this._min}
.items=${this.value ?? []}
?disabled=${this.disabled}
?readonly=${this.readonly}
?required=${this.required}
@change=${this.#onChange}
${umbBindToValidation(this)}>
</umb-input-multiple-text-string>
</umb-form-validation-message>
`;
}
}
Expand Down
Loading