Merge remote-tracking branch 'origin/main' into feature/easy-way-to-produce-a-set-of-properties
This commit is contained in:
@@ -52,7 +52,7 @@ jobs:
|
||||
registry-url: https://registry.npmjs.org/
|
||||
scope: '@umbraco-cms'
|
||||
- run: npm ci
|
||||
- run: npm run build:for:npm
|
||||
- run: npm run build
|
||||
- name: Calculate version
|
||||
run: |
|
||||
if [ -z "${{inputs.version}}" ]; then
|
||||
|
||||
1282
src/Umbraco.Web.UI.Client/package-lock.json
generated
1282
src/Umbraco.Web.UI.Client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -97,7 +97,6 @@
|
||||
"backoffice:test:e2e": "npx playwright test",
|
||||
"build-storybook": "npm run wc-analyze && storybook build",
|
||||
"build:for:cms": "npm run build && node ./devops/build/copy-to-cms.js",
|
||||
"build:for:npm": "npm run build && tsc-alias -f -p src/tsconfig.build.json && npm run generate:jsonschema:dist && npm run wc-analyze && npm run wc-analyze:vscode",
|
||||
"build:for:static": "vite build",
|
||||
"build:vite": "tsc && vite build --mode staging",
|
||||
"build": "tsc --project ./src/tsconfig.build.json && rollup -c ./src/rollup.config.js",
|
||||
@@ -116,7 +115,7 @@
|
||||
"lint:fix": "npm run lint -- --fix",
|
||||
"lint": "eslint src",
|
||||
"new-extension": "plop --plopfile ./devops/plop/plop.js",
|
||||
"prepublishOnly": "node ./devops/publish/cleanse-pkg.js",
|
||||
"prepack": "tsc-alias -f -p src/tsconfig.build.json && npm run generate:jsonschema:dist && npm run wc-analyze && npm run wc-analyze:vscode && node ./devops/publish/cleanse-pkg.js",
|
||||
"preview": "vite preview --open",
|
||||
"storybook:build": "npm run wc-analyze && storybook build",
|
||||
"storybook": "npm run wc-analyze && storybook dev -p 6006",
|
||||
@@ -141,7 +140,7 @@
|
||||
"lit": "^2.8.0",
|
||||
"lodash-es": "4.17.21",
|
||||
"marked": "^9.1.0",
|
||||
"monaco-editor": "^0.41.0",
|
||||
"monaco-editor": "^0.44.0",
|
||||
"rxjs": "^7.8.1",
|
||||
"tinymce-i18n": "^23.8.7",
|
||||
"tinymce": "^6.7.3",
|
||||
@@ -155,13 +154,13 @@
|
||||
"@rollup/plugin-commonjs": "^25.0.4",
|
||||
"@rollup/plugin-json": "^6.0.1",
|
||||
"@rollup/plugin-node-resolve": "^15.2.1",
|
||||
"@storybook/addon-a11y": "7.5.3",
|
||||
"@storybook/addon-actions": "7.5.3",
|
||||
"@storybook/addon-essentials": "7.5.3",
|
||||
"@storybook/addon-links": "7.5.3",
|
||||
"@storybook/addon-a11y": "7.6.3",
|
||||
"@storybook/addon-actions": "7.6.3",
|
||||
"@storybook/addon-essentials": "7.6.3",
|
||||
"@storybook/addon-links": "7.6.3",
|
||||
"@storybook/mdx2-csf": "^1.1.0",
|
||||
"@storybook/web-components-vite": "7.5.3",
|
||||
"@storybook/web-components": "7.5.3",
|
||||
"@storybook/web-components-vite": "7.6.3",
|
||||
"@storybook/web-components": "7.6.3",
|
||||
"@types/chai": "^4.3.5",
|
||||
"@types/lodash-es": "^4.17.8",
|
||||
"@types/mocha": "^10.0.1",
|
||||
@@ -180,7 +179,7 @@
|
||||
"eslint-plugin-lit": "^1.10.1",
|
||||
"eslint-plugin-local-rules": "^1.3.2",
|
||||
"eslint-plugin-storybook": "^0.6.15",
|
||||
"eslint-plugin-wc": "^1.5.0",
|
||||
"eslint-plugin-wc": "^2.0.4",
|
||||
"eslint": "^8.46.0",
|
||||
"lucide-static": "^0.290.0",
|
||||
"msw": "^1.2.3",
|
||||
@@ -196,7 +195,7 @@
|
||||
"rollup-plugin-import-css": "^3.3.4",
|
||||
"rollup-plugin-web-worker-loader": "^1.6.1",
|
||||
"rollup": "^3.27.2",
|
||||
"storybook": "7.5.3",
|
||||
"storybook": "7.6.3",
|
||||
"tiny-glob": "^0.2.9",
|
||||
"tsc-alias": "^1.8.8",
|
||||
"typescript-json-schema": "^0.62.0",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { UmbTextStyles } from "@umbraco-cms/backoffice/style";
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import {
|
||||
css,
|
||||
html,
|
||||
@@ -54,14 +54,11 @@ export class UmbCodeBlockElement extends LitElement {
|
||||
: ''}
|
||||
</div>`
|
||||
: ''}
|
||||
<pre style="${this.language ? 'border-top: 1px solid var(--uui-color-divider-emphasis);' : ''}">
|
||||
<uui-scroll-container>
|
||||
<code>
|
||||
<slot></slot>
|
||||
</code>
|
||||
</uui-scroll-container>
|
||||
</pre>
|
||||
`;
|
||||
<pre
|
||||
style="${this.language
|
||||
? 'border-top: 1px solid var(--uui-color-divider-emphasis);'
|
||||
: ''}"><uui-scroll-container><code><slot></slot></code></uui-scroll-container></pre>
|
||||
`; // Avoid breaks between elements of <pre></pre>
|
||||
}
|
||||
|
||||
static styles = [
|
||||
@@ -84,7 +81,12 @@ export class UmbCodeBlockElement extends LitElement {
|
||||
background-color: var(--uui-color-surface-alt);
|
||||
color: #303033;
|
||||
display: block;
|
||||
font-family: Lato, Helvetica Neue, Helvetica, Arial, sans-serif;
|
||||
font-family:
|
||||
Lato,
|
||||
Helvetica Neue,
|
||||
Helvetica,
|
||||
Arial,
|
||||
sans-serif;
|
||||
margin: 0;
|
||||
overflow-x: auto;
|
||||
padding: 9.5px;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { css, html, nothing, until, customElement, property } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UMB_APP_CONTEXT } from '@umbraco-cms/backoffice/app';
|
||||
import { html, until, customElement, property } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
|
||||
|
||||
type FileItem = {
|
||||
@@ -8,12 +9,15 @@ type FileItem = {
|
||||
|
||||
@customElement('umb-input-upload-field-file')
|
||||
export class UmbInputUploadFieldFileElement extends UmbLitElement {
|
||||
@property({ type: String })
|
||||
path = '';
|
||||
|
||||
/**
|
||||
* @description The file to be rendered.
|
||||
* @type {File}
|
||||
* @required
|
||||
*/
|
||||
@property({ type: File, attribute: false })
|
||||
@property({ attribute: false })
|
||||
set file(value: File) {
|
||||
this.#fileItem = new Promise((resolve) => {
|
||||
/**
|
||||
@@ -40,15 +44,35 @@ export class UmbInputUploadFieldFileElement extends UmbLitElement {
|
||||
}
|
||||
|
||||
#fileItem!: Promise<FileItem>;
|
||||
#serverUrl = '';
|
||||
|
||||
render = () => until(this.#renderFileItem(), html`<uui-loader></uui-loader>`);
|
||||
constructor() {
|
||||
super();
|
||||
this.consumeContext(UMB_APP_CONTEXT, (instance) => {
|
||||
this.#serverUrl = instance.getServerUrl();
|
||||
});
|
||||
}
|
||||
|
||||
// TODO Better way to do this....
|
||||
render = () => {
|
||||
if (this.path) {
|
||||
return html`<uui-symbol-file-thumbnail
|
||||
src=${this.#serverUrl + this.path}
|
||||
title=${this.path}
|
||||
alt=${this.path}></uui-symbol-file-thumbnail>`;
|
||||
} else {
|
||||
return until(this.#renderFileItem(), html`<uui-loader></uui-loader>`);
|
||||
}
|
||||
};
|
||||
|
||||
// render = () => until(this.#renderFileItem(), html`<uui-loader></uui-loader>`);
|
||||
|
||||
async #renderFileItem() {
|
||||
const fileItem = await this.#fileItem;
|
||||
return html`<uui-symbol-file-thumbnail
|
||||
src=${fileItem.src}
|
||||
title=${fileItem.name}
|
||||
alt=${fileItem.name}></uui-symbol-file-thumbnail>`;
|
||||
alt=${fileItem.name}></uui-symbol-file-thumbnail> `;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
import { TemporaryFileQueueItem, UmbTemporaryFileManager } from '../../temporary-file/temporary-file-manager.class.js';
|
||||
import { UmbId } from '@umbraco-cms/backoffice/id';
|
||||
import {
|
||||
css,
|
||||
html,
|
||||
nothing,
|
||||
map,
|
||||
ifDefined,
|
||||
customElement,
|
||||
property,
|
||||
query,
|
||||
state,
|
||||
repeat,
|
||||
} from '@umbraco-cms/backoffice/external/lit';
|
||||
import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui';
|
||||
import type { UUIFileDropzoneElement, UUIFileDropzoneEvent } from '@umbraco-cms/backoffice/external/uui';
|
||||
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
|
||||
|
||||
import './input-upload-field-file.element.js';
|
||||
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
|
||||
|
||||
@customElement('umb-input-upload-field')
|
||||
export class UmbInputUploadFieldElement extends FormControlMixin(UmbLitElement) {
|
||||
@@ -23,10 +26,13 @@ export class UmbInputUploadFieldElement extends FormControlMixin(UmbLitElement)
|
||||
* @type {Array<String>}
|
||||
* @default []
|
||||
*/
|
||||
@property({ type: Array<string> })
|
||||
@property({ type: Array })
|
||||
public set keys(fileKeys: Array<string>) {
|
||||
this._keys = fileKeys;
|
||||
super.value = this._keys.join(',');
|
||||
fileKeys.forEach((key) => {
|
||||
if (!UmbId.validate(key) && key.startsWith('/')) this._filePaths.push(key);
|
||||
});
|
||||
}
|
||||
public get keys(): Array<string> {
|
||||
return this._keys;
|
||||
@@ -37,7 +43,7 @@ export class UmbInputUploadFieldElement extends FormControlMixin(UmbLitElement)
|
||||
* @type {Array<String>}
|
||||
* @default undefined
|
||||
*/
|
||||
@property({ type: Array<string> })
|
||||
@property({ type: Array })
|
||||
fileExtensions?: Array<string>;
|
||||
|
||||
/**
|
||||
@@ -50,7 +56,10 @@ export class UmbInputUploadFieldElement extends FormControlMixin(UmbLitElement)
|
||||
multiple = false;
|
||||
|
||||
@state()
|
||||
_currentFiles: File[] = [];
|
||||
_currentFiles: Array<TemporaryFileQueueItem> = [];
|
||||
|
||||
@state()
|
||||
_filePaths: Array<string> = [];
|
||||
|
||||
@state()
|
||||
extensions?: string[];
|
||||
@@ -58,10 +67,20 @@ export class UmbInputUploadFieldElement extends FormControlMixin(UmbLitElement)
|
||||
@query('#dropzone')
|
||||
private _dropzone?: UUIFileDropzoneElement;
|
||||
|
||||
#manager;
|
||||
|
||||
protected getFormElement() {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.#manager = new UmbTemporaryFileManager(this);
|
||||
|
||||
this.observe(this.#manager.isReady, (value) => (this.error = !value));
|
||||
this.observe(this.#manager.queue, (value) => (this._currentFiles = value));
|
||||
}
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this.#setExtensions();
|
||||
@@ -85,12 +104,19 @@ export class UmbInputUploadFieldElement extends FormControlMixin(UmbLitElement)
|
||||
}
|
||||
|
||||
#setFiles(files: File[]) {
|
||||
this._currentFiles = [...this._currentFiles, ...files];
|
||||
const items = files.map(
|
||||
(file): TemporaryFileQueueItem => ({
|
||||
unique: UmbId.new(),
|
||||
file,
|
||||
status: 'waiting',
|
||||
}),
|
||||
);
|
||||
this.#manager.upload(items);
|
||||
|
||||
//TODO: set keys when possible, not names
|
||||
this.keys = this._currentFiles.map((file) => file.name);
|
||||
this.dispatchEvent(new CustomEvent('change', { bubbles: true }));
|
||||
this.keys = items.map((item) => item.unique);
|
||||
this.value = this.keys.join(',');
|
||||
|
||||
this.dispatchEvent(new UmbChangeEvent());
|
||||
}
|
||||
|
||||
#handleBrowse() {
|
||||
@@ -99,11 +125,14 @@ export class UmbInputUploadFieldElement extends FormControlMixin(UmbLitElement)
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`${this.#renderFiles()} ${this.#renderDropzone()}`;
|
||||
return html`<div id="wrapper">${this.#renderFilesWithPath()} ${this.#renderFilesUploaded()}</div>
|
||||
${this.#renderDropzone()}${this.#renderButtonRemove()}`;
|
||||
}
|
||||
|
||||
//TODO When the property editor gets saved, it seems that the property editor gets the file path from the server rather than key/id.
|
||||
// This however does not work when there is multiple files. Can the server not handle multiple files uploaded into one property editor?
|
||||
#renderDropzone() {
|
||||
if (!this.multiple && this._currentFiles.length) return nothing;
|
||||
if (!this.multiple && (this._currentFiles.length || this._filePaths.length)) return nothing;
|
||||
return html`
|
||||
<uui-file-dropzone
|
||||
id="dropzone"
|
||||
@@ -116,21 +145,41 @@ export class UmbInputUploadFieldElement extends FormControlMixin(UmbLitElement)
|
||||
`;
|
||||
}
|
||||
|
||||
#renderFiles() {
|
||||
#renderFilesWithPath() {
|
||||
if (!this._filePaths.length) return nothing;
|
||||
return html`${this._filePaths.map(
|
||||
(path) => html`<umb-input-upload-field-file .path=${path}></umb-input-upload-field-file>`,
|
||||
)}`;
|
||||
}
|
||||
|
||||
#renderFilesUploaded() {
|
||||
if (!this._currentFiles.length) return nothing;
|
||||
return html` <div id="wrapper">
|
||||
${map(this._currentFiles, (file) => {
|
||||
return html`<umb-input-upload-field-file .file=${file}></umb-input-upload-field-file>`;
|
||||
})}
|
||||
</div>
|
||||
<uui-button compact @click=${this.#handleRemove} label="Remove files">
|
||||
<uui-icon name="icon-trash"></uui-icon> Remove file(s)
|
||||
</uui-button>`;
|
||||
return html`
|
||||
${repeat(
|
||||
this._currentFiles,
|
||||
(item) => item.unique + item.status,
|
||||
(item) =>
|
||||
html`<div style="position:relative;">
|
||||
<umb-input-upload-field-file .file=${item.file as any}></umb-input-upload-field-file>
|
||||
${item.status === 'waiting' ? html`<umb-temporary-file-badge></umb-temporary-file-badge>` : nothing}
|
||||
</div> `,
|
||||
)}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
#renderButtonRemove() {
|
||||
if (!this._currentFiles.length && !this._filePaths.length) return;
|
||||
return html`<uui-button compact @click=${this.#handleRemove} label="Remove files">
|
||||
<uui-icon name="icon-trash"></uui-icon> Remove file(s)
|
||||
</uui-button>`;
|
||||
}
|
||||
|
||||
#handleRemove() {
|
||||
// Remove via endpoint?
|
||||
this._currentFiles = [];
|
||||
this._filePaths = [];
|
||||
const uniques = this._currentFiles.map((item) => item.unique) as string[];
|
||||
this.#manager.remove(uniques);
|
||||
|
||||
this.dispatchEvent(new UmbChangeEvent());
|
||||
}
|
||||
|
||||
static styles = [
|
||||
@@ -156,6 +205,10 @@ export class UmbInputUploadFieldElement extends FormControlMixin(UmbLitElement)
|
||||
grid-template-columns: repeat(auto-fit, 200px);
|
||||
gap: var(--uui-size-space-4);
|
||||
}
|
||||
|
||||
uui-file-dropzone {
|
||||
padding: 3px; /** Dropzone background is blurry and covers slightly into other elements. Hack to avoid this */
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -53,10 +53,10 @@ export class UmbDataTypeCreateOptionsModalElement extends UmbLitElement {
|
||||
href=${`section/settings/workspace/data-type/create/${this.data?.parentKey || null}`}
|
||||
label="New Data Type..."
|
||||
@click=${this.#onNavigate}>
|
||||
<uui-icon slot="icon" name="icon-autofill"></uui-icon>}
|
||||
<uui-icon slot="icon" name="icon-autofill"></uui-icon>
|
||||
</uui-menu-item>
|
||||
<uui-menu-item @click=${this.#onClick} label="New Folder...">
|
||||
<uui-icon slot="icon" name="icon-folder"></uui-icon>}
|
||||
<uui-icon slot="icon" name="icon-folder"></uui-icon>
|
||||
</uui-menu-item>
|
||||
</uui-box>
|
||||
<uui-button slot="actions" id="cancel" label="Cancel" @click="${this.#onCancel}">Cancel</uui-button>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { type UmbTreeElement } from '../../../tree/tree.element.js';
|
||||
import { html, customElement, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { html, customElement, state, ifDefined } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import { UmbTreePickerModalData, UmbPickerModalValue, UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';
|
||||
import { TreeItemPresentationModel } from '@umbraco-cms/backoffice/backend-api';
|
||||
@@ -43,7 +43,8 @@ export class UmbTreePickerModalElement<TreeItemType extends TreeItemPresentation
|
||||
<umb-body-layout headline="Select">
|
||||
<uui-box>
|
||||
<umb-tree
|
||||
alias=${this.data?.treeAlias}
|
||||
?hide-tree-root=${this.data?.hideTreeRoot}
|
||||
alias=${ifDefined(this.data?.treeAlias)}
|
||||
@selection-change=${this.#onSelectionChange}
|
||||
.selection=${this._selection}
|
||||
selectable
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export interface UmbPickerModalData<ItemType> {
|
||||
multiple?: boolean;
|
||||
selection?: Array<string | null>;
|
||||
hideTreeRoot?: boolean;
|
||||
filter?: (item: ItemType) => boolean;
|
||||
pickableFilter?: (item: ItemType) => boolean;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { EntityTreeItemResponseModel } from '@umbraco-cms/backoffice/backend-api';
|
||||
import { UmbModalToken } from '@umbraco-cms/backoffice/modal';
|
||||
|
||||
export interface UmbImportDictionaryModalData {
|
||||
@@ -5,7 +6,7 @@ export interface UmbImportDictionaryModalData {
|
||||
}
|
||||
|
||||
export interface UmbImportDictionaryModalValue {
|
||||
temporaryFileId: string;
|
||||
entityItems: Array<EntityTreeItemResponseModel>;
|
||||
parentId?: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { UmbInputUploadFieldElement } from '../../../components/input-upload-field/input-upload-field.element.js';
|
||||
import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbTextStyles } from "@umbraco-cms/backoffice/style";
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
|
||||
import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor';
|
||||
@@ -34,7 +34,8 @@ export class UmbPropertyEditorUIUploadFieldElement extends UmbLitElement impleme
|
||||
return html`<umb-input-upload-field
|
||||
@change="${this._onChange}"
|
||||
?multiple="${this._multiple}"
|
||||
.fileExtensions="${this._fileExtensions}"></umb-input-upload-field>`;
|
||||
.fileExtensions="${this._fileExtensions}"
|
||||
.keys=${(this.value as string)?.split(',') ?? []}></umb-input-upload-field>`;
|
||||
}
|
||||
|
||||
static styles = [UmbTextStyles];
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import { css, customElement, html, property } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
|
||||
import { clamp } from '@umbraco-cms/backoffice/external/uui';
|
||||
|
||||
@customElement('umb-temporary-file-badge')
|
||||
export class UmbTemporaryFileBadgeElement extends UmbLitElement {
|
||||
private _progress = 0;
|
||||
|
||||
@property({ type: Number })
|
||||
public set progress(v: number) {
|
||||
const oldVal = this._progress;
|
||||
|
||||
const p = clamp(v, 0, 100);
|
||||
this._progress = p;
|
||||
|
||||
this.requestUpdate('progress', oldVal);
|
||||
}
|
||||
public get progress(): number {
|
||||
return this._progress;
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<uui-badge>
|
||||
<div id="wrapper">
|
||||
<uui-loader-circle progress=${this.progress}></uui-loader-circle>
|
||||
<uui-icon name="icon-arrow-up"></uui-icon>
|
||||
</div>
|
||||
</uui-badge>`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#wrapper {
|
||||
box-sizing: border-box;
|
||||
box-shadow: inset 0px 0px 0px 6px var(--uui-color-surface);
|
||||
background-color: var(--uui-color-selected);
|
||||
position: relative;
|
||||
border-radius: 100%;
|
||||
font-size: var(--uui-size-6);
|
||||
}
|
||||
|
||||
uui-loader-circle {
|
||||
display: absolute;
|
||||
inset: 0;
|
||||
color: var(--uui-color-focus);
|
||||
font-size: var(--uui-size-12);
|
||||
}
|
||||
|
||||
uui-badge {
|
||||
padding: 0;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
uui-icon {
|
||||
font-size: var(--uui-size-6);
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-temporary-file-badge': UmbTemporaryFileBadgeElement;
|
||||
}
|
||||
}
|
||||
|
||||
export default UmbTemporaryFileBadgeElement;
|
||||
@@ -1 +1,2 @@
|
||||
export * from './temporary-file.repository.js';
|
||||
export * from './components/temporary-file-badge.element.js';
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import { UmbTemporaryFileRepository } from './temporary-file.repository.js';
|
||||
import { UmbArrayState, UmbBooleanState } from '@umbraco-cms/backoffice/observable-api';
|
||||
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { UmbBaseController } from '@umbraco-cms/backoffice/class-api';
|
||||
|
||||
export type TemporaryFileStatus = 'complete' | 'waiting' | 'error';
|
||||
|
||||
export interface TemporaryFileQueueItem {
|
||||
unique: string;
|
||||
file: File;
|
||||
status?: TemporaryFileStatus;
|
||||
}
|
||||
|
||||
export class UmbTemporaryFileManager extends UmbBaseController {
|
||||
#temporaryFileRepository;
|
||||
|
||||
#queue = new UmbArrayState<TemporaryFileQueueItem>([], (item) => item.unique);
|
||||
public readonly queue = this.#queue.asObservable();
|
||||
|
||||
#isReady = new UmbBooleanState(true);
|
||||
public readonly isReady = this.#isReady.asObservable();
|
||||
|
||||
constructor(host: UmbControllerHostElement) {
|
||||
super(host);
|
||||
this.#temporaryFileRepository = new UmbTemporaryFileRepository(host);
|
||||
}
|
||||
|
||||
uploadOne(unique: string, file: File, status: TemporaryFileStatus = 'waiting') {
|
||||
this.#queue.appendOne({ unique, file, status });
|
||||
this.handleQueue();
|
||||
}
|
||||
|
||||
upload(queueItems: Array<TemporaryFileQueueItem>) {
|
||||
this.#queue.append(queueItems);
|
||||
this.handleQueue();
|
||||
}
|
||||
|
||||
removeOne(unique: string) {
|
||||
this.#queue.removeOne(unique);
|
||||
}
|
||||
|
||||
remove(uniques: Array<string>) {
|
||||
this.#queue.remove(uniques);
|
||||
}
|
||||
|
||||
private async handleQueue() {
|
||||
const queue = this.#queue.getValue();
|
||||
|
||||
if (!queue.length && this.getIsReady()) return;
|
||||
|
||||
this.#isReady.next(false);
|
||||
|
||||
queue.forEach(async (item) => {
|
||||
if (item.status !== 'waiting') return;
|
||||
|
||||
const { error } = await this.#temporaryFileRepository.upload(item.unique, item.file);
|
||||
await new Promise((resolve) => setTimeout(resolve, (Math.random() + 0.5) * 1000)); // simulate small delay so that the upload badge is properly shown
|
||||
|
||||
if (error) {
|
||||
this.#queue.updateOne(item.unique, { ...item, status: 'error' });
|
||||
} else {
|
||||
this.#queue.updateOne(item.unique, { ...item, status: 'complete' });
|
||||
}
|
||||
});
|
||||
|
||||
if (!queue.find((item) => item.status === 'waiting') && !this.getIsReady()) {
|
||||
this.#isReady.next(true);
|
||||
}
|
||||
}
|
||||
|
||||
getIsReady() {
|
||||
return this.#queue.getValue();
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
import { UmbPickerInputContext } from '@umbraco-cms/backoffice/picker-input';
|
||||
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { UMB_DICTIONARY_ITEM_PICKER_MODAL } from '@umbraco-cms/backoffice/modal';
|
||||
import { DictionaryItemItemResponseModel } from '@umbraco-cms/backoffice/backend-api';
|
||||
|
||||
export class UmbDictionaryItemPickerContext extends UmbPickerInputContext<DictionaryItemItemResponseModel> {
|
||||
constructor(host: UmbControllerHostElement) {
|
||||
super(host, 'Umb.Repository.Dictionary', UMB_DICTIONARY_ITEM_PICKER_MODAL);
|
||||
}
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
import { UmbDictionaryItemPickerContext } from './dictionary-item-input.context.js';
|
||||
import { css, html, customElement, property, state, ifDefined, repeat } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui';
|
||||
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
|
||||
import type { DictionaryItemItemResponseModel } from '@umbraco-cms/backoffice/backend-api';
|
||||
|
||||
@customElement('umb-dictionary-item-input')
|
||||
export class UmbDictionaryItemInputElement extends FormControlMixin(UmbLitElement) {
|
||||
/**
|
||||
* This is a minimum amount of selected items in this input.
|
||||
* @type {number}
|
||||
* @attr
|
||||
* @default 0
|
||||
*/
|
||||
@property({ type: Number })
|
||||
public get min(): number {
|
||||
return this.#pickerContext.min;
|
||||
}
|
||||
public set min(value: number) {
|
||||
this.#pickerContext.min = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Min validation message.
|
||||
* @type {boolean}
|
||||
* @attr
|
||||
* @default
|
||||
*/
|
||||
@property({ type: String, attribute: 'min-message' })
|
||||
minMessage = 'This field need more items';
|
||||
|
||||
/**
|
||||
* This is a maximum amount of selected items in this input.
|
||||
* @type {number}
|
||||
* @attr
|
||||
* @default Infinity
|
||||
*/
|
||||
@property({ type: Number })
|
||||
public get max(): number {
|
||||
return this.#pickerContext.max;
|
||||
}
|
||||
public set max(value: number) {
|
||||
this.#pickerContext.max = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Max validation message.
|
||||
* @type {boolean}
|
||||
* @attr
|
||||
* @default
|
||||
*/
|
||||
@property({ type: String, attribute: 'min-message' })
|
||||
maxMessage = 'This field exceeds the allowed amount of items';
|
||||
|
||||
public get selectedIds(): Array<string> {
|
||||
return this.#pickerContext.getSelection();
|
||||
}
|
||||
public set selectedIds(ids: Array<string>) {
|
||||
this.#pickerContext.setSelection(ids);
|
||||
}
|
||||
|
||||
@property()
|
||||
public set value(idsString: string) {
|
||||
// Its with full purpose we don't call super.value, as thats being handled by the observation of the context selection.
|
||||
this.selectedIds = idsString.split(/[ ,]+/);
|
||||
}
|
||||
|
||||
@state()
|
||||
private _items?: Array<DictionaryItemItemResponseModel>;
|
||||
|
||||
#pickerContext = new UmbDictionaryItemPickerContext(this);
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.addValidator(
|
||||
'rangeUnderflow',
|
||||
() => this.minMessage,
|
||||
() => !!this.min && this.#pickerContext.getSelection().length < this.min,
|
||||
);
|
||||
|
||||
this.addValidator(
|
||||
'rangeOverflow',
|
||||
() => this.maxMessage,
|
||||
() => !!this.max && this.#pickerContext.getSelection().length > this.max,
|
||||
);
|
||||
|
||||
this.observe(this.#pickerContext.selection, (selection) => (super.value = selection.join(',')));
|
||||
this.observe(this.#pickerContext.selectedItems, (selectedItems) => (this._items = selectedItems));
|
||||
}
|
||||
|
||||
protected getFormElement() {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
${this._items
|
||||
? html` <uui-ref-list
|
||||
>${repeat(
|
||||
this._items,
|
||||
(item) => item.id,
|
||||
(item) => this._renderItem(item),
|
||||
)}
|
||||
</uui-ref-list>`
|
||||
: ''}
|
||||
${this.#renderAddButton()}
|
||||
`;
|
||||
}
|
||||
|
||||
#renderAddButton() {
|
||||
if (this.max > 0 && this.selectedIds.length >= this.max) return;
|
||||
return html`<uui-button
|
||||
id="add-button"
|
||||
look="placeholder"
|
||||
@click=${() => this.#pickerContext.openPicker()}
|
||||
label=${this.localize.term('general_add')}></uui-button>`;
|
||||
}
|
||||
|
||||
private _renderItem(item: DictionaryItemItemResponseModel) {
|
||||
if (!item.id) return;
|
||||
return html`
|
||||
<uui-ref-node name=${ifDefined(item.name)} detail=${ifDefined(item.id)}>
|
||||
<!-- TODO: implement is trashed <uui-tag size="s" slot="tag" color="danger">Trashed</uui-tag> -->
|
||||
<uui-action-bar slot="actions">
|
||||
<uui-button
|
||||
@click=${() => this.#pickerContext.requestRemoveItem(item.id!)}
|
||||
label=${this.localize.term('actions_remove')}></uui-button>
|
||||
</uui-action-bar>
|
||||
</uui-ref-node>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = [
|
||||
css`
|
||||
#add-button {
|
||||
width: 100%;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
export default UmbDictionaryItemInputElement;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-dictionary-item-input': UmbDictionaryItemInputElement;
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from './dictionary-item-input/dictionary-item-input.element.js';
|
||||
@@ -1,5 +1,4 @@
|
||||
import '../../components/dictionary-item-input/dictionary-item-input.element.js';
|
||||
import UmbDictionaryItemInputElement from '../../components/dictionary-item-input/dictionary-item-input.element.js';
|
||||
import { UMB_DICTIONARY_TREE_ALIAS } from '../../tree/manifests.js';
|
||||
import { UmbDictionaryRepository } from '../../repository/dictionary.repository.js';
|
||||
import { css, html, customElement, query, state, when } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
@@ -8,11 +7,13 @@ import {
|
||||
UmbImportDictionaryModalValue,
|
||||
UmbModalBaseElement,
|
||||
} from '@umbraco-cms/backoffice/modal';
|
||||
import { ImportDictionaryRequestModel } from '@umbraco-cms/backoffice/backend-api';
|
||||
import { UmbId } from '@umbraco-cms/backoffice/id';
|
||||
import { EntityTreeItemResponseModel } from '@umbraco-cms/backoffice/backend-api';
|
||||
import { UmbTreeElement } from '@umbraco-cms/backoffice/tree';
|
||||
|
||||
interface DictionaryItemPreview {
|
||||
name: string;
|
||||
id: string;
|
||||
children: Array<DictionaryItemPreview>;
|
||||
}
|
||||
|
||||
@@ -25,32 +26,64 @@ export class UmbImportDictionaryModalLayout extends UmbModalBaseElement<
|
||||
private _parentId?: string;
|
||||
|
||||
@state()
|
||||
private _temporaryFileId?: string;
|
||||
private _temporaryFileId = '';
|
||||
|
||||
@query('#form')
|
||||
private _form!: HTMLFormElement;
|
||||
|
||||
@query('umb-tree')
|
||||
private _treeElement?: UmbTreeElement;
|
||||
|
||||
#fileReader;
|
||||
|
||||
#fileNodes!: NodeListOf<ChildNode>;
|
||||
|
||||
#fileContent: Array<DictionaryItemPreview> = [];
|
||||
|
||||
#dictionaryRepository: UmbDictionaryRepository;
|
||||
|
||||
#handleClose() {
|
||||
this.modalContext?.reject();
|
||||
}
|
||||
|
||||
#submit() {
|
||||
// TODO: Gotta do a temp file upload before submitting, so that the server can use it
|
||||
console.log('submit:', this._temporaryFileId, this._parentId);
|
||||
//this.modalContext?.submit({ temporaryFileId: this._temporaryFileId, parentId: this._parentId });
|
||||
#createTreeEntitiesFromTempFile(): Array<EntityTreeItemResponseModel> {
|
||||
const data: Array<EntityTreeItemResponseModel> = [];
|
||||
|
||||
const list = this.#dictionaryPreviewItemBuilder(this.#fileNodes);
|
||||
const scaffold = (items: Array<DictionaryItemPreview>, parentId?: string) => {
|
||||
items.forEach((item) => {
|
||||
data.push({
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
type: 'dictionary-item',
|
||||
hasChildren: item.children.length ? true : false,
|
||||
parentId: parentId,
|
||||
});
|
||||
scaffold(item.children, item.id);
|
||||
});
|
||||
};
|
||||
|
||||
scaffold(list, this._parentId);
|
||||
return data;
|
||||
}
|
||||
|
||||
async #submit() {
|
||||
const { error } = await this.#dictionaryRepository.import(this._temporaryFileId, this._parentId);
|
||||
if (error) return;
|
||||
|
||||
this.modalContext?.submit({ entityItems: this.#createTreeEntitiesFromTempFile(), parentId: this._parentId });
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.#dictionaryRepository = new UmbDictionaryRepository(this);
|
||||
|
||||
this.#fileReader = new FileReader();
|
||||
|
||||
this.#fileReader.onload = (e) => {
|
||||
if (typeof e.target?.result === 'string') {
|
||||
const fileContent = e.target.result;
|
||||
this.#dictionaryItemBuilder(fileContent);
|
||||
this.#dictionaryPreviewBuilder(fileContent);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -60,16 +93,17 @@ export class UmbImportDictionaryModalLayout extends UmbModalBaseElement<
|
||||
this._parentId = this.data?.unique ?? undefined;
|
||||
}
|
||||
|
||||
#dictionaryItemBuilder(htmlString: string) {
|
||||
#dictionaryPreviewBuilder(htmlString: string) {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(htmlString, 'text/xml');
|
||||
const elements = doc.childNodes;
|
||||
this.#fileNodes = elements;
|
||||
|
||||
this.#fileContent = this.#makeDictionaryItems(elements);
|
||||
this.#fileContent = this.#dictionaryPreviewItemBuilder(elements);
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
#makeDictionaryItems(nodeList: NodeListOf<ChildNode>): Array<DictionaryItemPreview> {
|
||||
#dictionaryPreviewItemBuilder(nodeList: NodeListOf<ChildNode>): Array<DictionaryItemPreview> {
|
||||
const items: Array<DictionaryItemPreview> = [];
|
||||
const list: Array<Element> = [];
|
||||
nodeList.forEach((node) => {
|
||||
@@ -81,25 +115,29 @@ export class UmbImportDictionaryModalLayout extends UmbModalBaseElement<
|
||||
list.forEach((item) => {
|
||||
items.push({
|
||||
name: item.getAttribute('Name') ?? '',
|
||||
children: this.#makeDictionaryItems(item.childNodes) ?? undefined,
|
||||
id: item.getAttribute('Key') ?? '',
|
||||
children: this.#dictionaryPreviewItemBuilder(item.childNodes) ?? undefined,
|
||||
});
|
||||
});
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
#onUpload(e: Event) {
|
||||
async #onUpload(e: Event) {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(this._form);
|
||||
const file = formData.get('file') as Blob;
|
||||
const file = formData.get('file') as File;
|
||||
|
||||
if (!file) throw new Error('File is missing');
|
||||
|
||||
this.#fileReader.readAsText(file);
|
||||
this._temporaryFileId = file ? UmbId.new() : undefined;
|
||||
this._temporaryFileId = UmbId.new();
|
||||
|
||||
this.#dictionaryRepository.upload(this._temporaryFileId, file);
|
||||
}
|
||||
|
||||
#onParentChange(event: CustomEvent) {
|
||||
this._parentId = (event.target as UmbDictionaryItemInputElement).selectedIds[0] || undefined;
|
||||
//console.log((event.target as UmbDictionaryItemInputElement).selectedIds[0] || undefined);
|
||||
#onParentChange() {
|
||||
this._parentId = this._treeElement?.selection[0] ?? undefined;
|
||||
}
|
||||
|
||||
async #onFileInput() {
|
||||
@@ -145,16 +183,15 @@ export class UmbImportDictionaryModalLayout extends UmbModalBaseElement<
|
||||
</div>
|
||||
<div>
|
||||
<strong><umb-localize key="actions_chooseWhereToImport">Choose where to import</umb-localize>:</strong>
|
||||
Work in progress<br />
|
||||
${
|
||||
this._parentId
|
||||
// TODO
|
||||
// <umb-dictionary-item-input
|
||||
// @change=${this.#onParentChange}
|
||||
// .selectedIds=${this._parentId ? [this._parentId] : []}
|
||||
// max="1">
|
||||
// </umb-dictionary-item-input>
|
||||
}
|
||||
<br />parentId:
|
||||
|
||||
<umb-tree
|
||||
?hide-tree-root=${true}
|
||||
?multiple=${false}
|
||||
alias=${UMB_DICTIONARY_TREE_ALIAS}
|
||||
@selection-change=${this.#onParentChange}
|
||||
.selection=${[this._parentId ?? '']}
|
||||
selectable></umb-tree>
|
||||
</div>
|
||||
|
||||
${this.#renderNavigate()}
|
||||
|
||||
@@ -7,11 +7,13 @@ import {
|
||||
UMB_MODAL_MANAGER_CONTEXT_TOKEN,
|
||||
UMB_IMPORT_DICTIONARY_MODAL,
|
||||
} from '@umbraco-cms/backoffice/modal';
|
||||
import { UMB_DICTIONARY_TREE_STORE_CONTEXT, UmbDictionaryTreeStore } from '@umbraco-cms/backoffice/dictionary';
|
||||
|
||||
export default class UmbImportDictionaryEntityAction extends UmbEntityActionBase<UmbDictionaryRepository> {
|
||||
static styles = [UmbTextStyles];
|
||||
|
||||
#modalContext?: UmbModalManagerContext;
|
||||
#treeStore?: UmbDictionaryTreeStore;
|
||||
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string) {
|
||||
super(host, repositoryAlias, unique);
|
||||
@@ -19,6 +21,9 @@ export default class UmbImportDictionaryEntityAction extends UmbEntityActionBase
|
||||
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT_TOKEN, (instance) => {
|
||||
this.#modalContext = instance;
|
||||
});
|
||||
this.consumeContext(UMB_DICTIONARY_TREE_STORE_CONTEXT, (instance) => {
|
||||
this.#treeStore = instance;
|
||||
});
|
||||
}
|
||||
|
||||
async execute() {
|
||||
@@ -26,8 +31,12 @@ export default class UmbImportDictionaryEntityAction extends UmbEntityActionBase
|
||||
|
||||
const modalContext = this.#modalContext?.open(UMB_IMPORT_DICTIONARY_MODAL, { unique: this.unique });
|
||||
|
||||
const { parentId, temporaryFileId } = await modalContext.onSubmit();
|
||||
const { entityItems, parentId } = await modalContext.onSubmit();
|
||||
|
||||
await this.repository?.import(temporaryFileId, parentId);
|
||||
if (!entityItems?.length) return;
|
||||
|
||||
this.#treeStore?.appendItems(entityItems);
|
||||
|
||||
if (parentId) this.#treeStore?.updateItem(parentId, { hasChildren: true });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
export * from './repository/index.js';
|
||||
export * from './tree/index.js';
|
||||
export * from './components/index.js';
|
||||
|
||||
@@ -7,11 +7,11 @@ import { UmbDetailRepository } from '@umbraco-cms/backoffice/repository';
|
||||
import {
|
||||
CreateDictionaryItemRequestModel,
|
||||
DictionaryOverviewResponseModel,
|
||||
ImportDictionaryRequestModel,
|
||||
UpdateDictionaryItemRequestModel,
|
||||
} from '@umbraco-cms/backoffice/backend-api';
|
||||
import { UmbNotificationContext, UMB_NOTIFICATION_CONTEXT_TOKEN } from '@umbraco-cms/backoffice/notification';
|
||||
import type { UmbApi } from '@umbraco-cms/backoffice/extension-api';
|
||||
import { UmbTemporaryFileRepository } from '@umbraco-cms/backoffice/temporary-file';
|
||||
|
||||
export class UmbDictionaryRepository
|
||||
extends UmbBaseController
|
||||
@@ -31,6 +31,8 @@ export class UmbDictionaryRepository
|
||||
#detailSource: UmbDictionaryDetailServerDataSource;
|
||||
#detailStore?: UmbDictionaryStore;
|
||||
|
||||
#temporaryFileRepository: UmbTemporaryFileRepository;
|
||||
|
||||
#notificationContext?: UmbNotificationContext;
|
||||
|
||||
constructor(host: UmbControllerHostElement) {
|
||||
@@ -38,6 +40,7 @@ export class UmbDictionaryRepository
|
||||
|
||||
// TODO: figure out how spin up get the correct data source
|
||||
this.#detailSource = new UmbDictionaryDetailServerDataSource(this);
|
||||
this.#temporaryFileRepository = new UmbTemporaryFileRepository(host);
|
||||
|
||||
this.#init = Promise.all([
|
||||
this.consumeContext(UMB_DICTIONARY_STORE_CONTEXT_TOKEN, (instance) => {
|
||||
@@ -93,6 +96,7 @@ export class UmbDictionaryRepository
|
||||
|
||||
async delete(id: string) {
|
||||
await this.#init;
|
||||
await this.#treeStore?.removeItem(id);
|
||||
return this.#detailSource.delete(id);
|
||||
}
|
||||
|
||||
@@ -155,14 +159,12 @@ export class UmbDictionaryRepository
|
||||
return this.#detailSource.import(temporaryFileId, parentId);
|
||||
}
|
||||
|
||||
async upload(formData: ImportDictionaryRequestModel) {
|
||||
async upload(UmbId: string, file: File) {
|
||||
await this.#init;
|
||||
if (!UmbId) throw new Error('UmbId is missing');
|
||||
if (!file) throw new Error('File is missing');
|
||||
|
||||
if (!formData) {
|
||||
throw new Error('Form data is missing');
|
||||
}
|
||||
|
||||
return this.#detailSource.upload(formData);
|
||||
return this.#temporaryFileRepository.upload(UmbId, file);
|
||||
}
|
||||
|
||||
// TODO => temporary only, until languages data source exists, or might be
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { UMB_MEDIA_WORKSPACE_CONTEXT } from './media-workspace.context.js';
|
||||
import { UmbTextStyles } from "@umbraco-cms/backoffice/style";
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import { css, html, nothing, customElement, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
|
||||
@customElement('umb-media-workspace-editor')
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import { css, customElement, html, property } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
|
||||
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
|
||||
|
||||
@customElement('umb-template-alias-input')
|
||||
export class UmbTemplateAliasInputElement extends UmbLitElement {
|
||||
render() {
|
||||
return html`
|
||||
<uui-button compact @click=${this.#handleClick} label="unlock alias input">
|
||||
<uui-symbol-lock .open=${this.isOpen} ></uui-symbol-lock>
|
||||
</uui-button>
|
||||
<input placeholder="Enter alias..." .value=${this.value} ?disabled=${!this.isOpen} @input=${
|
||||
this.#setValue
|
||||
}></input>
|
||||
|
||||
`;
|
||||
}
|
||||
|
||||
@property({ type: String, attribute: 'value' })
|
||||
value = '';
|
||||
|
||||
@property({ type: Boolean })
|
||||
isOpen = false;
|
||||
|
||||
#setValue(event: Event) {
|
||||
event.stopPropagation();
|
||||
this.value = (event.target as HTMLInputElement).value;
|
||||
}
|
||||
|
||||
#handleClick() {
|
||||
this.isOpen = !this.isOpen;
|
||||
if (!this.isOpen) {
|
||||
this.dispatchEvent(new UmbChangeEvent());
|
||||
}
|
||||
}
|
||||
|
||||
static styles = [
|
||||
UmbTextStyles,
|
||||
css`
|
||||
:host {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
:host(:focus-within) {
|
||||
border-color: var(--uui-input-border-color-focus, var(--uui-color-border-emphasis, #a1a1a1));
|
||||
outline: calc(2px * var(--uui-show-focus-outline, 1)) solid var(--uui-color-focus, #3879ff);
|
||||
}
|
||||
|
||||
input {
|
||||
background: transparent;
|
||||
border-color: transparent;
|
||||
font-family: inherit;
|
||||
padding: var(--uui-size-1, 3px) var(--uui-size-space-3, 9px);
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
border-radius: 0px;
|
||||
box-sizing: border-box;
|
||||
border: none;
|
||||
background: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: inherit;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
input:disabled {
|
||||
color: #a2a1a6;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
export default UmbTemplateAliasInputElement;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-template-alias-input': UmbTemplateAliasInputElement;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,2 @@
|
||||
export * from './template-card/template-card.element.js';
|
||||
export * from './input-template/input-template.element.js';
|
||||
export * from './alias-input/alias-input.js';
|
||||
|
||||
@@ -93,13 +93,13 @@ export class UmbQueryBuilderFilterElement extends UmbLitElement {
|
||||
<uui-combobox-list slot="dropdown" @change=${this.#setOperator} class="options-list">
|
||||
${this.settings?.operators
|
||||
?.filter((operator) =>
|
||||
this.currentPropertyType ? operator.applicableTypes?.includes(this.currentPropertyType) : true
|
||||
this.currentPropertyType ? operator.applicableTypes?.includes(this.currentPropertyType) : true,
|
||||
)
|
||||
.map(
|
||||
(operator) =>
|
||||
html`<uui-combobox-list-option .value=${(operator.operator as string) ?? ''}
|
||||
>${operator.operator}</uui-combobox-list-option
|
||||
>`
|
||||
>`,
|
||||
)}</uui-combobox-list
|
||||
>
|
||||
</umb-button-with-dropdown>`;
|
||||
@@ -120,27 +120,27 @@ export class UmbQueryBuilderFilterElement extends UmbLitElement {
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<span>${this.unremovable ? 'where' : 'and'}</span>
|
||||
<span>${this.unremovable ? this.localize.term('template_where') : this.localize.term('template_and')}</span>
|
||||
<umb-button-with-dropdown look="outline" id="property-alias-dropdown" label="Property alias"
|
||||
>${this.filter?.propertyAlias ?? ''}
|
||||
<uui-combobox-list slot="dropdown" @change=${this.#setPropertyAlias} class="options-list">
|
||||
${this.settings?.properties?.map(
|
||||
(property) =>
|
||||
html`<uui-combobox-list-option tabindex="0" .value=${property.alias ?? ''}
|
||||
>${property.alias}</uui-combobox-list-option
|
||||
>`
|
||||
html`<uui-combobox-list-option tabindex="0" .value=${property.alias ?? ''}>
|
||||
${property.alias}
|
||||
</uui-combobox-list-option>`,
|
||||
)}
|
||||
</uui-combobox-list></umb-button-with-dropdown
|
||||
>
|
||||
${this.filter?.propertyAlias ? this._renderOperatorsDropdown() : ''}
|
||||
${this.filter?.operator ? this._renderConstraintValueInput() : ''}
|
||||
<uui-button-group>
|
||||
<uui-button title="Add filter" label="Add filter" compact @click=${this.#addFilter}
|
||||
><uui-icon name="add"></uui-icon
|
||||
></uui-button>
|
||||
<uui-button title="Remove filter" label="Remove filter" compact @click=${this.#removeOrReset}
|
||||
><uui-icon name="delete"></uui-icon
|
||||
></uui-button>
|
||||
<uui-button title="Add filter" label="Add filter" compact @click=${this.#addFilter}>
|
||||
<uui-icon name="icon-add"></uui-icon>
|
||||
</uui-button>
|
||||
<uui-button title="Remove filter" label="Remove filter" compact @click=${this.#removeOrReset}>
|
||||
<uui-icon name="delete"></uui-icon>
|
||||
</uui-button>
|
||||
</uui-button-group>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -57,10 +57,10 @@ export default class UmbChooseInsertTypeModalElement extends UmbModalBaseElement
|
||||
private _queryBuilderSettings?: TemplateQuerySettingsResponseModel;
|
||||
|
||||
@state()
|
||||
private _selectedRootContentName? = 'all pages';
|
||||
private _selectedRootContentName? = this.localize.term('template_websiteRoot');
|
||||
|
||||
@state()
|
||||
private _defaultSortDirection: SortOrder = SortOrder.Descending;
|
||||
private _defaultSortDirection: SortOrder = SortOrder.Ascending;
|
||||
|
||||
#documentRepository: UmbDocumentRepository;
|
||||
#modalManagerContext?: UmbModalManagerContext;
|
||||
@@ -111,7 +111,7 @@ export default class UmbChooseInsertTypeModalElement extends UmbModalBaseElement
|
||||
|
||||
#openDocumentPicker = () => {
|
||||
this.#modalManagerContext
|
||||
?.open(UMB_DOCUMENT_PICKER_MODAL)
|
||||
?.open(UMB_DOCUMENT_PICKER_MODAL, { hideTreeRoot: true })
|
||||
.onSubmit()
|
||||
.then((result) => {
|
||||
this.#updateQueryRequest({ rootContentId: result.selection[0] });
|
||||
@@ -252,7 +252,7 @@ export default class UmbChooseInsertTypeModalElement extends UmbModalBaseElement
|
||||
ms</span
|
||||
>
|
||||
</div>
|
||||
<umb-code-block language="C#" copy> ${this._templateQuery?.queryExpression ?? ''} </umb-code-block>
|
||||
<umb-code-block language="C#" copy>${this._templateQuery?.queryExpression ?? ''}</umb-code-block>
|
||||
</uui-box>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -10,6 +10,9 @@ import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources';
|
||||
export class UmbTemplateQueryBuilderServerDataSource {
|
||||
#host: UmbControllerHost;
|
||||
|
||||
// TODO: When we map the server models to our own models, we need to have a localization property.
|
||||
// For example, the OperatorModel.NOT_EQUALS need to use the localization key "template_doesNotEqual"
|
||||
|
||||
/**
|
||||
* Creates an instance of UmbTemplateQueryBuilderServerDataSource.
|
||||
* @param {UmbControllerHost} host
|
||||
|
||||
@@ -6,7 +6,7 @@ import { UMB_TEMPLATE_WORKSPACE_CONTEXT } from './template-workspace.context.js'
|
||||
import type { UmbCodeEditorElement } from '@umbraco-cms/backoffice/code-editor';
|
||||
import { camelCase } from '@umbraco-cms/backoffice/external/lodash';
|
||||
import { UUIInputElement } from '@umbraco-cms/backoffice/external/uui';
|
||||
import { css, html, customElement, query, state, nothing } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { css, html, customElement, query, state, nothing, ifDefined } from '@umbraco-cms/backoffice/external/lit';
|
||||
import {
|
||||
UMB_MODAL_MANAGER_CONTEXT_TOKEN,
|
||||
UMB_TEMPLATE_PICKER_MODAL,
|
||||
@@ -179,12 +179,10 @@ export class UmbTemplateWorkspaceEditorElement extends UmbLitElement {
|
||||
slot="header"
|
||||
.value=${this._name}
|
||||
@input=${this.#onNameInput}
|
||||
label="template name"
|
||||
><umb-template-alias-input
|
||||
slot="append"
|
||||
.value=${this._alias ?? ''}
|
||||
@change=${this.#onAliasInput}></umb-template-alias-input
|
||||
></uui-input>
|
||||
label="template name">
|
||||
<uui-input-lock slot="append" value=${ifDefined(this._alias!)} @change=${this.#onAliasInput}></uui-input-lock>
|
||||
</uui-input>
|
||||
|
||||
<uui-box>
|
||||
<div slot="header" id="code-editor-menu-container">
|
||||
${this.#renderMasterTemplatePicker()}
|
||||
|
||||
@@ -49,6 +49,9 @@ export class UmbUmbracoNewsDashboardElement extends UmbLitElement {
|
||||
display: block;
|
||||
padding: var(--uui-size-layout-1);
|
||||
}
|
||||
p {
|
||||
position: relative;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user