Merge branch 'main' into feature/log-viewer
This commit is contained in:
@@ -34,7 +34,7 @@ jobs:
|
||||
- run: sudo npx playwright install-deps
|
||||
- run: npm test
|
||||
- name: Upload Code Coverage reports
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: code-coverage
|
||||
|
||||
@@ -29,6 +29,7 @@ import '../src/backoffice/shared/components/backoffice-frame/backoffice-notifica
|
||||
import '../libs/element/context-provider.element';
|
||||
import '../src/backoffice/shared/components/backoffice-frame/backoffice-modal-container.element';
|
||||
import '../src/backoffice/shared/components/code-block/code-block.element';
|
||||
import '../src/backoffice/shared/components/workspace/workspace-layout/workspace-layout.element';
|
||||
|
||||
class UmbStoryBookElement extends LitElement {
|
||||
_umbIconStore = new UmbIconStore();
|
||||
|
||||
@@ -80,4 +80,19 @@ export class UmbExtensionRegistry {
|
||||
)
|
||||
) as Observable<Array<ExtensionType>>;
|
||||
}
|
||||
|
||||
extensionsSortedByTypeAndWeight<ExtensionType = ManifestBase>(): Observable<Array<ExtensionType>> {
|
||||
return this.extensions.pipe(
|
||||
map((exts) => exts
|
||||
.sort((a, b) => {
|
||||
// If type is the same, sort by weight
|
||||
if (a.type === b.type) {
|
||||
return (a.weight || 0) - (b.weight || 0);
|
||||
}
|
||||
|
||||
// Otherwise sort by type
|
||||
return a.type.localeCompare(b.type);
|
||||
}))
|
||||
) as Observable<Array<ExtensionType>>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'router-slot';
|
||||
import { LitElement, PropertyValueMap } from 'lit';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { IRoute, RouterSlot } from 'router-slot';
|
||||
@@ -7,6 +8,8 @@ import { UmbRouterSlotChangeEvent, UmbRouterSlotInitEvent } from '@umbraco-cms/r
|
||||
* @element umb-router-slot-element
|
||||
* @description - Component for wrapping Router Slot element, providing some local events for implementation.
|
||||
* @extends UmbRouterSlotElement
|
||||
* @fires {UmbRouterSlotInitEvent} init - fires when the media card is selected
|
||||
* @fires {UmbRouterSlotChangeEvent} change - fires when the media card is unselected
|
||||
*/
|
||||
@customElement('umb-router-slot')
|
||||
export class UmbRouterSlotElement extends LitElement {
|
||||
@@ -38,8 +41,11 @@ export class UmbRouterSlotElement extends LitElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.#router = document.createElement('router-slot');
|
||||
// Note: I decided not to use the local changestate event, because it is not fired when the route is changed from any router-slot. And for now I wanted to keep it local.
|
||||
//this.#router.addEventListener('changestate', this._onNavigationChanged);
|
||||
}
|
||||
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
if (this.#listening === false) {
|
||||
@@ -54,6 +60,7 @@ export class UmbRouterSlotElement extends LitElement {
|
||||
this.#listening = false;
|
||||
}
|
||||
|
||||
|
||||
protected firstUpdated(_changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void {
|
||||
super.firstUpdated(_changedProperties);
|
||||
this._routerPath = this.#router.constructAbsolutePath('') || '';
|
||||
|
||||
2376
src/Umbraco.Web.UI.Client/package-lock.json
generated
2376
src/Umbraco.Web.UI.Client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -57,10 +57,8 @@
|
||||
"npm": ">=8.0.0 < 9"
|
||||
},
|
||||
"dependencies": {
|
||||
"@umbraco-ui/uui": "^1.1.0",
|
||||
"@umbraco-ui/uui-color-swatch": "file:umbraco-ui-uui-color-swatch-0.0.0.tgz",
|
||||
"@umbraco-ui/uui-color-swatches": "file:umbraco-ui-uui-color-swatches-2.0.0.tgz",
|
||||
"@umbraco-ui/uui-css": "^1.0.0",
|
||||
"@umbraco-ui/uui": "^1.2.0-rc.0",
|
||||
"@umbraco-ui/uui-css": "^1.2.0-rc.0",
|
||||
"@umbraco-ui/uui-modal": "file:umbraco-ui-uui-modal-0.0.0.tgz",
|
||||
"@umbraco-ui/uui-modal-container": "file:umbraco-ui-uui-modal-container-0.0.0.tgz",
|
||||
"@umbraco-ui/uui-modal-dialog": "file:umbraco-ui-uui-modal-dialog-0.0.0.tgz",
|
||||
@@ -81,7 +79,7 @@
|
||||
"@storybook/addon-actions": "^6.5.14",
|
||||
"@storybook/addon-essentials": "^6.5.15",
|
||||
"@storybook/addon-links": "^6.5.15",
|
||||
"@storybook/builder-vite": "^0.2.7",
|
||||
"@storybook/builder-vite": "^0.3.0",
|
||||
"@storybook/mdx2-csf": "^0.0.3",
|
||||
"@storybook/web-components": "^6.5.15",
|
||||
"@types/chai": "^4.3.4",
|
||||
@@ -89,7 +87,7 @@
|
||||
"@types/mocha": "^10.0.0",
|
||||
"@types/uuid": "^9.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.50.0",
|
||||
"@typescript-eslint/parser": "^5.48.1",
|
||||
"@typescript-eslint/parser": "^5.50.0",
|
||||
"@web/dev-server-esbuild": "^0.3.3",
|
||||
"@web/dev-server-import-maps": "^0.0.7",
|
||||
"@web/test-runner": "^0.15.0",
|
||||
|
||||
@@ -2,14 +2,12 @@ import '@umbraco-ui/uui-css/dist/uui-css.css';
|
||||
import '@umbraco-cms/css';
|
||||
|
||||
// TODO: remove these imports when they are part of UUI
|
||||
import '@umbraco-ui/uui-color-swatch';
|
||||
import '@umbraco-ui/uui-color-swatches';
|
||||
import '@umbraco-ui/uui-modal';
|
||||
import '@umbraco-ui/uui-modal-container';
|
||||
import '@umbraco-ui/uui-modal-dialog';
|
||||
import '@umbraco-ui/uui-modal-sidebar';
|
||||
import 'element-internals-polyfill';
|
||||
import 'router-slot';
|
||||
import '@umbraco-cms/router';
|
||||
|
||||
import type { Guard, IRoute } from 'router-slot/model';
|
||||
|
||||
@@ -151,7 +149,7 @@ export class UmbApp extends UmbLitElement {
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<router-slot id="router-slot" .routes=${this._routes}></router-slot>`;
|
||||
return html`<umb-router-slot id="router-slot" .routes=${this._routes}></umb-router-slot>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ export class UmbCreatedPackagesSectionViewElement extends UmbLitElement {
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<router-slot .routes=${this._routes}></router-slot>`;
|
||||
return html`<umb-router-slot .routes=${this._routes}></umb-router-slot>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ export class UmbInstalledPackagesSectionViewElement extends UmbLitElement {
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<router-slot .routes=${this._routes}></router-slot>`;
|
||||
return html`<umb-router-slot .routes=${this._routes}></umb-router-slot>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import { UmbDashboardExamineIndexElement } from './views/section-view-examine-in
|
||||
import { UmbDashboardExamineSearcherElement } from './views/section-view-examine-searchers';
|
||||
|
||||
import { UmbLitElement } from '@umbraco-cms/element';
|
||||
import { UmbRouterSlotChangeEvent, UmbRouterSlotInitEvent } from '@umbraco-cms/router';
|
||||
|
||||
@customElement('umb-dashboard-examine-management')
|
||||
export class UmbDashboardExamineManagementElement extends UmbLitElement {
|
||||
@@ -46,28 +47,24 @@ export class UmbDashboardExamineManagementElement extends UmbLitElement {
|
||||
];
|
||||
|
||||
@state()
|
||||
private _currentPath?: string;
|
||||
private _routerPath?: string;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
@state()
|
||||
private _activePath = '';
|
||||
|
||||
private _onRouteChange() {
|
||||
this._currentPath = path();
|
||||
}
|
||||
|
||||
private get backbutton(): boolean {
|
||||
return !(this._currentPath?.endsWith('examine-management/'));
|
||||
}
|
||||
|
||||
render() {
|
||||
return html` ${this.backbutton
|
||||
? html` <a href="section/settings/dashboard/examine-management"> ← Back to overview </a> `
|
||||
return html` ${this._routerPath && this._activePath !== ''
|
||||
? html` <a href=${this._routerPath}> ← Back to overview </a> `
|
||||
: nothing}
|
||||
<router-slot @changestate="${this._onRouteChange}" .routes=${this._routes}></router-slot>`;
|
||||
<umb-router-slot
|
||||
.routes=${this._routes}
|
||||
@init=${(event: UmbRouterSlotInitEvent) => {
|
||||
this._routerPath = event.target.absoluteRouterPath;
|
||||
}}
|
||||
@change=${(event: UmbRouterSlotChangeEvent) => {
|
||||
this._activePath = event.target.localActiveViewPath || '';
|
||||
}}></umb-router-slot>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ export class UmbDashboardHealthCheckElement extends UmbLitElement {
|
||||
}
|
||||
|
||||
render() {
|
||||
return html` <router-slot .routes=${this._routes}></router-slot>`;
|
||||
return html` <umb-router-slot .routes=${this._routes}></umb-router-slot>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,33 +1,55 @@
|
||||
import { html } from 'lit';
|
||||
import { customElement, state } from 'lit/decorators.js';
|
||||
import { isManifestElementNameType , umbExtensionsRegistry } from '@umbraco-cms/extensions-api';
|
||||
import { isManifestElementNameType, umbExtensionsRegistry } from '@umbraco-cms/extensions-api';
|
||||
import type { ManifestBase } from '@umbraco-cms/models';
|
||||
import { UmbLitElement } from '@umbraco-cms/element';
|
||||
import { UmbModalService, UMB_MODAL_SERVICE_CONTEXT_TOKEN } from '@umbraco-cms/modal';
|
||||
|
||||
@customElement('umb-extension-root-workspace')
|
||||
export class UmbExtensionRootWorkspaceElement extends UmbLitElement {
|
||||
@state()
|
||||
private _extensions?: Array<ManifestBase> = undefined;
|
||||
|
||||
private _modalService?: UmbModalService;
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this._observeExtensions();
|
||||
|
||||
this.consumeContext(UMB_MODAL_SERVICE_CONTEXT_TOKEN, (modalService) => {
|
||||
this._modalService = modalService;
|
||||
});
|
||||
}
|
||||
|
||||
private _observeExtensions() {
|
||||
this.observe(umbExtensionsRegistry.extensions, (extensions) => {
|
||||
this.observe(umbExtensionsRegistry.extensionsSortedByTypeAndWeight(), (extensions) => {
|
||||
this._extensions = extensions || undefined;
|
||||
});
|
||||
}
|
||||
|
||||
#removeExtension(extension: ManifestBase) {
|
||||
const modalHandler = this._modalService?.confirm({
|
||||
headline: 'Unload extension',
|
||||
confirmLabel: 'Unload',
|
||||
content: html`<p>Are you sure you want to unload the extension <strong>${extension.alias}</strong>?</p>`,
|
||||
color: 'danger',
|
||||
});
|
||||
|
||||
modalHandler?.onClose().then(({ confirmed }: any) => {
|
||||
if (confirmed) {
|
||||
umbExtensionsRegistry.unregister(extension.alias);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<umb-workspace-layout headline="Extensions" alias="Umb.Workspace.ExtensionRoot">
|
||||
<uui-box>
|
||||
<p>List of currently loaded extensions</p>
|
||||
<uui-table>
|
||||
<uui-table-head>
|
||||
<uui-table-head-cell>Type</uui-table-head-cell>
|
||||
<uui-table-head-cell>Weight</uui-table-head-cell>
|
||||
<uui-table-head-cell>Name</uui-table-head-cell>
|
||||
<uui-table-head-cell>Alias</uui-table-head-cell>
|
||||
<uui-table-head-cell>Actions</uui-table-head-cell>
|
||||
@@ -37,14 +59,19 @@ export class UmbExtensionRootWorkspaceElement extends UmbLitElement {
|
||||
(extension) => html`
|
||||
<uui-table-row>
|
||||
<uui-table-cell>${extension.type}</uui-table-cell>
|
||||
<uui-table-cell>${extension.weight ? extension.weight : 'Not Set'} </uui-table-cell>
|
||||
<uui-table-cell>
|
||||
${isManifestElementNameType(extension) ? extension.name : 'Custom extension'}
|
||||
${isManifestElementNameType(extension) ? extension.name : `[Custom extension] ${extension.name}`}
|
||||
</uui-table-cell>
|
||||
<uui-table-cell>${extension.alias}</uui-table-cell>
|
||||
<uui-table-cell>
|
||||
<uui-button
|
||||
label="unload"
|
||||
@click=${() => umbExtensionsRegistry.unregister(extension.alias)}></uui-button>
|
||||
label="Unload"
|
||||
color="danger"
|
||||
look="primary"
|
||||
@click=${() => this.#removeExtension(extension)}>
|
||||
<uui-icon name="umb:trash"></uui-icon>
|
||||
</uui-button>
|
||||
</uui-table-cell>
|
||||
</uui-table-row>
|
||||
`
|
||||
|
||||
@@ -103,7 +103,7 @@ export class UmbCollectionElement extends UmbLitElement {
|
||||
return html`
|
||||
<umb-body-layout no-header-background>
|
||||
<umb-collection-toolbar slot="header"></umb-collection-toolbar>
|
||||
<router-slot id="router-slot" .routes="${this._routes}"></router-slot>
|
||||
<umb-router-slot id="router-slot" .routes="${this._routes}"></umb-router-slot>
|
||||
${this._selection && this._selection.length > 0
|
||||
? html`<umb-collection-selection-actions slot="footer"></umb-collection-selection-actions>`
|
||||
: nothing}
|
||||
|
||||
@@ -2,12 +2,12 @@ import { defineElement } from '@umbraco-ui/uui-base/lib/registration';
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { css, html } from 'lit';
|
||||
import { state } from 'lit/decorators.js';
|
||||
import { IRoutingInfo } from 'router-slot';
|
||||
import { UmbSectionContext, UMB_SECTION_CONTEXT_TOKEN } from '../section/section.context';
|
||||
import { UmbBackofficeContext, UMB_BACKOFFICE_CONTEXT_TOKEN } from './backoffice.context';
|
||||
import type { ManifestSection } from '@umbraco-cms/models';
|
||||
import { UmbLitElement } from '@umbraco-cms/element';
|
||||
import { createExtensionElementOrFallback } from '@umbraco-cms/extensions-api';
|
||||
import { UmbRouterSlotChangeEvent } from '@umbraco-cms/router';
|
||||
|
||||
@defineElement('umb-backoffice-main')
|
||||
export class UmbBackofficeMain extends UmbLitElement {
|
||||
@@ -67,9 +67,6 @@ export class UmbBackofficeMain extends UmbLitElement {
|
||||
return {
|
||||
path: this._routePrefix + section.meta.pathname,
|
||||
component: () => createExtensionElementOrFallback(section, 'umb-section'),
|
||||
setup: this._onRouteSetup,
|
||||
// TODO: sometimes we can end up in a state where this callback doesn't get called. It could look like a bug in the router-slot.
|
||||
// Niels: Could this be because _backofficeContext is not available at that state?
|
||||
};
|
||||
});
|
||||
|
||||
@@ -79,8 +76,8 @@ export class UmbBackofficeMain extends UmbLitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _onRouteSetup = (_component: HTMLElement, info: IRoutingInfo) => {
|
||||
const currentPath = info.match.route.path;
|
||||
private _onRouteChange = (event: UmbRouterSlotChangeEvent) => {
|
||||
const currentPath = event.target.localActiveViewPath || ''
|
||||
const section = this._sections.find((s) => this._routePrefix + s.meta.pathname === currentPath);
|
||||
if (!section) return;
|
||||
this._backofficeContext?.setActiveSectionAlias(section.alias);
|
||||
@@ -97,7 +94,11 @@ export class UmbBackofficeMain extends UmbLitElement {
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<umb-router-slot .routes=${this._routes}></umb-router-slot>`;
|
||||
return html`
|
||||
<umb-router-slot
|
||||
.routes=${this._routes}
|
||||
@change=${this._onRouteChange}
|
||||
></umb-router-slot>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css';
|
||||
import { css, html, LitElement } from 'lit';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
|
||||
@customElement('umb-empty-state')
|
||||
export class UmbEmptyStateElement extends LitElement {
|
||||
static styles = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
text-align: center;
|
||||
padding: var(--uui-size-space-4);
|
||||
}
|
||||
|
||||
:host([position='center']) {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
max-width: 400px;
|
||||
width: 80%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
:host(:not([position='center'])) {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
:host(:not([size='small'])) {
|
||||
font-size: var(--uui-size-6);
|
||||
}
|
||||
|
||||
:host([size='small']) {
|
||||
font-size: var(--uui-size-5);
|
||||
}
|
||||
|
||||
slot {
|
||||
margin: auto;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
/**
|
||||
* Set the text size
|
||||
*/
|
||||
@property({ type: String })
|
||||
size: 'small' | 'large' = 'large';
|
||||
|
||||
/**
|
||||
* Set the element position
|
||||
* 'center' => element is absolutely centered
|
||||
* undefined => element has auto margin, to center in parent
|
||||
*/
|
||||
@property({ type: String })
|
||||
position: 'center' | undefined;
|
||||
|
||||
render() {
|
||||
return html`<slot></slot>`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-empty-state': UmbEmptyStateElement;
|
||||
}
|
||||
}
|
||||
@@ -14,3 +14,7 @@ import './section/section-sidebar/section-sidebar.element';
|
||||
import './section/section.element';
|
||||
import './tree/tree.element';
|
||||
import './workspace/workspace-content/workspace-content.element';
|
||||
import './input-media-picker/input-media-picker.element';
|
||||
import './input-document-picker/input-document-picker.element';
|
||||
import './empty-state/empty-state.element';
|
||||
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import { css, html, nothing } from 'lit';
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { customElement, property, state } from 'lit/decorators.js';
|
||||
import { FormControlMixin } from '@umbraco-ui/uui-base/lib/mixins';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
import { UUIBooleanInputEvent } from '@umbraco-ui/uui';
|
||||
import { UmbLitElement } from '@umbraco-cms/element';
|
||||
|
||||
@customElement('umb-input-checkbox-list')
|
||||
export class UmbInputCheckboxListElement extends FormControlMixin(UmbLitElement) {
|
||||
static styles = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
uui-checkbox {
|
||||
width: 100%;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
/**
|
||||
* List of items.
|
||||
*/
|
||||
@property()
|
||||
list?: [];
|
||||
|
||||
private _selectedKeys: Array<string> = [];
|
||||
public get selectedKeys(): Array<string> {
|
||||
return this._selectedKeys;
|
||||
}
|
||||
public set selectedKeys(keys: Array<string>) {
|
||||
this._selectedKeys = keys;
|
||||
super.value = keys.join(',');
|
||||
}
|
||||
|
||||
@property()
|
||||
public set value(keysString: string) {
|
||||
if (keysString !== this._value) {
|
||||
this.selectedKeys = keysString.split(/[ ,]+/);
|
||||
}
|
||||
}
|
||||
|
||||
protected getFormElement() {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private _setSelection(e: UUIBooleanInputEvent) {
|
||||
e.stopPropagation();
|
||||
if (e.target.checked) this.selectedKeys = [...this.selectedKeys, e.target.value];
|
||||
else this._removeFromSelection(this.selectedKeys.findIndex((key) => e.target.value === key));
|
||||
|
||||
this.dispatchEvent(new CustomEvent('change', { bubbles: true, composed: true }));
|
||||
}
|
||||
|
||||
private _removeFromSelection(index: number) {
|
||||
if (index == -1) return;
|
||||
const keys = [...this.selectedKeys];
|
||||
keys.splice(index, 1);
|
||||
this.selectedKeys = keys;
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.list) return nothing;
|
||||
return html`<form>
|
||||
<uui-form @change="${this._setSelection}">
|
||||
${repeat(this.list, (item) => item.key, this.renderCheckbox)}
|
||||
</uui-form>
|
||||
</form>`;
|
||||
}
|
||||
|
||||
renderCheckbox(item: any) {
|
||||
return html`<uui-checkbox value="${item.key}" label="${item.label}"></uui-checkbox>`;
|
||||
}
|
||||
}
|
||||
|
||||
export default UmbInputCheckboxListElement;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-input-checkbox-list': UmbInputCheckboxListElement;
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,6 @@ export class UmbInputDocumentPickerElement extends FormControlMixin(UmbLitElemen
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
/**
|
||||
* This is a minimum amount of selected items in this input.
|
||||
* @type {number}
|
||||
@@ -122,7 +121,10 @@ export class UmbInputDocumentPickerElement extends FormControlMixin(UmbLitElemen
|
||||
|
||||
private _openPicker() {
|
||||
// We send a shallow copy(good enough as its just an array of keys) of our this._selectedKeys, as we don't want the modal to manipulate our data:
|
||||
const modalHandler = this._modalService?.contentPicker({ multiple: true, selection: [...this._selectedKeys] });
|
||||
const modalHandler = this._modalService?.contentPicker({
|
||||
multiple: this.max === 1 ? false : true,
|
||||
selection: [...this._selectedKeys],
|
||||
});
|
||||
modalHandler?.onClose().then(({ selection }: any) => {
|
||||
this._setSelection(selection);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
import { UmbInputDocumentPickerElement } from './input-document-picker.element';
|
||||
import { defaultA11yConfig } from '@umbraco-cms/test-utils';
|
||||
describe('UmbPropertyEditorUINumberRangeElement', () => {
|
||||
let element: UmbInputDocumentPickerElement;
|
||||
|
||||
beforeEach(async () => {
|
||||
element = await fixture(html` <umb-input-document-picker></umb-input-document-picker> `);
|
||||
});
|
||||
|
||||
it('is defined with its own instance', () => {
|
||||
expect(element).to.be.instanceOf(UmbInputDocumentPickerElement);
|
||||
});
|
||||
|
||||
it('passes the a11y audit', async () => {
|
||||
await expect(element).shadowDom.to.be.accessible(defaultA11yConfig);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,210 @@
|
||||
import { css, html, nothing } from 'lit';
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { customElement, property, state } from 'lit/decorators.js';
|
||||
import { ifDefined } from 'lit-html/directives/if-defined.js';
|
||||
import { FormControlMixin } from '@umbraco-ui/uui-base/lib/mixins';
|
||||
import { UmbModalService, UMB_MODAL_SERVICE_CONTEXT_TOKEN } from '../../../../core/modal';
|
||||
import {
|
||||
MediaTreeItem,
|
||||
UmbMediaTreeStore,
|
||||
UMB_MEDIA_TREE_STORE_CONTEXT_TOKEN,
|
||||
} from '../../../../backoffice/media/media/media.tree.store';
|
||||
import { UmbLitElement } from '@umbraco-cms/element';
|
||||
import type { FolderTreeItem } from '@umbraco-cms/backend-api';
|
||||
import type { UmbObserverController } from '@umbraco-cms/observable-api';
|
||||
|
||||
@customElement('umb-input-media-picker')
|
||||
export class UmbInputMediaPickerElement extends FormControlMixin(UmbLitElement) {
|
||||
static styles = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
:host {
|
||||
display: grid;
|
||||
gap: var(--uui-size-space-3);
|
||||
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
|
||||
}
|
||||
#add-button {
|
||||
text-align: center;
|
||||
min-height: 160px;
|
||||
}
|
||||
uui-icon {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
/**
|
||||
* This is a minimum amount of selected items in this input.
|
||||
* @type {number}
|
||||
* @attr
|
||||
* @default undefined
|
||||
*/
|
||||
@property({ type: Number })
|
||||
min?: number;
|
||||
|
||||
/**
|
||||
* 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 undefined
|
||||
*/
|
||||
@property({ type: Number })
|
||||
max?: number;
|
||||
|
||||
/**
|
||||
* Max validation message.
|
||||
* @type {boolean}
|
||||
* @attr
|
||||
* @default
|
||||
*/
|
||||
@property({ type: String, attribute: 'min-message' })
|
||||
maxMessage = 'This field exceeds the allowed amount of items';
|
||||
|
||||
// TODO: do we need both selectedKeys and value? If we just use value we follow the same pattern as native form controls.
|
||||
private _selectedKeys: Array<string> = [];
|
||||
public get selectedKeys(): Array<string> {
|
||||
return this._selectedKeys;
|
||||
}
|
||||
public set selectedKeys(keys: Array<string>) {
|
||||
this._selectedKeys = keys;
|
||||
super.value = keys.join(',');
|
||||
this._observePickedMedias();
|
||||
}
|
||||
|
||||
@property()
|
||||
public set value(keysString: string) {
|
||||
if (keysString !== this._value) {
|
||||
this.selectedKeys = keysString.split(/[ ,]+/);
|
||||
}
|
||||
}
|
||||
|
||||
@state()
|
||||
private _items?: Array<MediaTreeItem>;
|
||||
|
||||
private _modalService?: UmbModalService;
|
||||
private _mediaStore?: UmbMediaTreeStore;
|
||||
private _pickedItemsObserver?: UmbObserverController<FolderTreeItem>;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.addValidator(
|
||||
'rangeUnderflow',
|
||||
() => this.minMessage,
|
||||
() => !!this.min && this._selectedKeys.length < this.min
|
||||
);
|
||||
this.addValidator(
|
||||
'rangeOverflow',
|
||||
() => this.maxMessage,
|
||||
() => !!this.max && this._selectedKeys.length > this.max
|
||||
);
|
||||
|
||||
this.consumeContext(UMB_MEDIA_TREE_STORE_CONTEXT_TOKEN, (instance) => {
|
||||
this._mediaStore = instance;
|
||||
this._observePickedMedias();
|
||||
});
|
||||
this.consumeContext(UMB_MODAL_SERVICE_CONTEXT_TOKEN, (instance) => {
|
||||
this._modalService = instance;
|
||||
});
|
||||
}
|
||||
|
||||
protected getFormElement() {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private _observePickedMedias() {
|
||||
this._pickedItemsObserver?.destroy();
|
||||
|
||||
if (!this._mediaStore) return;
|
||||
|
||||
// TODO: consider changing this to the list data endpoint when it is available
|
||||
this._pickedItemsObserver = this.observe(this._mediaStore.getTreeItems(this._selectedKeys), (items) => {
|
||||
this._items = items;
|
||||
});
|
||||
}
|
||||
|
||||
private _openPicker() {
|
||||
// We send a shallow copy(good enough as its just an array of keys) of our this._selectedKeys, as we don't want the modal to manipulate our data:
|
||||
const modalHandler = this._modalService?.mediaPicker({
|
||||
multiple: this.max === 1 ? false : true,
|
||||
selection: [...this._selectedKeys],
|
||||
});
|
||||
modalHandler?.onClose().then(({ selection }: any) => {
|
||||
this._setSelection(selection);
|
||||
});
|
||||
}
|
||||
|
||||
private _removeItem(item: FolderTreeItem) {
|
||||
const modalHandler = this._modalService?.confirm({
|
||||
color: 'danger',
|
||||
headline: `Remove ${item.name}?`,
|
||||
content: 'Are you sure you want to remove this item',
|
||||
confirmLabel: 'Remove',
|
||||
});
|
||||
|
||||
modalHandler?.onClose().then(({ confirmed }) => {
|
||||
if (confirmed) {
|
||||
const newSelection = this._selectedKeys.filter((value) => value !== item.key);
|
||||
this._setSelection(newSelection);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _setSelection(newSelection: Array<string>) {
|
||||
this.selectedKeys = newSelection;
|
||||
this.dispatchEvent(new CustomEvent('change', { bubbles: true, composed: true }));
|
||||
console.log(this._items);
|
||||
}
|
||||
|
||||
render() {
|
||||
return html` ${this._items?.map((item) => this._renderItem(item))} ${this._renderButton()} `;
|
||||
}
|
||||
private _renderButton() {
|
||||
if (this.max == 1 && this._items && this._items.length > 0) return;
|
||||
return html`<uui-button id="add-button" look="placeholder" @click=${this._openPicker} label="open">
|
||||
<uui-icon name="umb:add"></uui-icon>
|
||||
Add
|
||||
</uui-button>`;
|
||||
}
|
||||
|
||||
private _renderItem(item: FolderTreeItem) {
|
||||
// TODO: remove when we have a way to handle trashed items
|
||||
const tempItem = item as FolderTreeItem & { isTrashed: boolean };
|
||||
|
||||
return html`
|
||||
<uui-card-media
|
||||
name=${ifDefined(item.name === null ? undefined : item.name)}
|
||||
detail=${ifDefined(item.key)}
|
||||
file-ext="jpg">
|
||||
${tempItem.isTrashed ? html` <uui-tag size="s" slot="tag" color="danger">Trashed</uui-tag> ` : nothing}
|
||||
<uui-action-bar slot="actions">
|
||||
<uui-button label="Copy media">
|
||||
<uui-icon name="umb:documents"></uui-icon>
|
||||
</uui-button>
|
||||
<uui-button @click=${() => this._removeItem(item)} label="Remove media ${item.name}">
|
||||
<uui-icon name="umb:trash"></uui-icon>
|
||||
</uui-button>
|
||||
</uui-action-bar>
|
||||
</uui-card-media>
|
||||
`;
|
||||
//TODO: <uui-button-inline-create vertical></uui-button-inline-create>
|
||||
}
|
||||
}
|
||||
|
||||
export default UmbInputMediaPickerElement;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-input-media-picker': UmbInputMediaPickerElement;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
import { UmbInputMediaPickerElement } from './input-media-picker.element';
|
||||
import { defaultA11yConfig } from '@umbraco-cms/test-utils';
|
||||
describe('UmbPropertyEditorUINumberRangeElement', () => {
|
||||
let element: UmbInputMediaPickerElement;
|
||||
|
||||
beforeEach(async () => {
|
||||
element = await fixture(html` <umb-input-media-picker></umb-input-media-picker> `);
|
||||
});
|
||||
|
||||
it('is defined with its own instance', () => {
|
||||
expect(element).to.be.instanceOf(UmbInputMediaPickerElement);
|
||||
});
|
||||
|
||||
it('passes the a11y audit', async () => {
|
||||
await expect(element).shadowDom.to.be.accessible(defaultA11yConfig);
|
||||
});
|
||||
});
|
||||
@@ -8,6 +8,7 @@ import { createExtensionElement, umbExtensionsRegistry } from '@umbraco-cms/exte
|
||||
import type { ManifestDashboard, ManifestDashboardCollection, ManifestWithMeta } from '@umbraco-cms/models';
|
||||
|
||||
import { UmbLitElement } from '@umbraco-cms/element';
|
||||
import { UmbRouterSlotChangeEvent, UmbRouterSlotInitEvent } from '@umbraco-cms/router';
|
||||
|
||||
@customElement('umb-section-dashboards')
|
||||
export class UmbSectionDashboardsElement extends UmbLitElement {
|
||||
@@ -27,7 +28,8 @@ export class UmbSectionDashboardsElement extends UmbLitElement {
|
||||
}
|
||||
|
||||
#scroll-container {
|
||||
flex: 1;
|
||||
flex:1;
|
||||
position:relative;
|
||||
}
|
||||
|
||||
#router-slot {
|
||||
@@ -41,14 +43,14 @@ export class UmbSectionDashboardsElement extends UmbLitElement {
|
||||
@state()
|
||||
private _dashboards?: Array<ManifestDashboard | ManifestDashboardCollection>;
|
||||
|
||||
@state()
|
||||
private _currentDashboardPathname = '';
|
||||
|
||||
@state()
|
||||
private _routes: Array<any> = [];
|
||||
|
||||
@state()
|
||||
private _currentSectionPathname = '';
|
||||
private _routerPath?: string;
|
||||
|
||||
@state()
|
||||
private _activePath?: string;
|
||||
|
||||
private _currentSectionAlias?: string;
|
||||
private _sectionContext?: UmbSectionContext;
|
||||
@@ -69,9 +71,6 @@ export class UmbSectionDashboardsElement extends UmbLitElement {
|
||||
this._currentSectionAlias = alias;
|
||||
this._observeDashboards();
|
||||
});
|
||||
this.observe(this._sectionContext.pathname.pipe(first()), (pathname) => {
|
||||
this._currentSectionPathname = pathname || '';
|
||||
});
|
||||
}
|
||||
|
||||
private _observeDashboards() {
|
||||
@@ -108,7 +107,6 @@ export class UmbSectionDashboardsElement extends UmbLitElement {
|
||||
return createExtensionElement(dashboard);
|
||||
},
|
||||
setup: (component: Promise<HTMLElement> | HTMLElement, info: IRoutingInfo) => {
|
||||
this._currentDashboardPathname = info.match.route.path;
|
||||
// When its using import, we get an element, when using createExtensionElement we get a Promise.
|
||||
// TODO: this is a bit hacky, can we do it in a more appropriate way:
|
||||
if ((component as any).then) {
|
||||
@@ -135,9 +133,9 @@ export class UmbSectionDashboardsElement extends UmbLitElement {
|
||||
${this._dashboards.map(
|
||||
(dashboard) => html`
|
||||
<uui-tab
|
||||
href="${`section/${this._currentSectionPathname}/dashboard/${dashboard.meta.pathname}`}"
|
||||
href="${this._routerPath}/${dashboard.meta.pathname}"
|
||||
label=${dashboard.meta.label || dashboard.name}
|
||||
?active="${dashboard.meta.pathname === this._currentDashboardPathname}"></uui-tab>
|
||||
?active="${dashboard.meta.pathname === this._activePath}"></uui-tab>
|
||||
`
|
||||
)}
|
||||
</uui-tab-group>
|
||||
@@ -150,7 +148,16 @@ export class UmbSectionDashboardsElement extends UmbLitElement {
|
||||
return html`
|
||||
${this._renderNavigation()}
|
||||
<uui-scroll-container id="scroll-container">
|
||||
<router-slot id="router-slot" .routes="${this._routes}"></router-slot>
|
||||
<umb-router-slot
|
||||
id="router-slot"
|
||||
.routes="${this._routes}"
|
||||
@init=${(event: UmbRouterSlotInitEvent) => {
|
||||
this._routerPath = event.target.absoluteRouterPath;
|
||||
}}
|
||||
@change=${(event: UmbRouterSlotChangeEvent) => {
|
||||
this._activePath = event.target.localActiveViewPath;
|
||||
}}
|
||||
></umb-router-slot>
|
||||
</uui-scroll-container>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import { UmbLitElement } from '@umbraco-cms/element';
|
||||
|
||||
import './section-sidebar-menu/section-sidebar-menu.element.ts';
|
||||
import './section-views/section-views.element.ts';
|
||||
import { UmbRouterSlotChangeEvent } from '@umbraco-cms/router';
|
||||
|
||||
@customElement('umb-section')
|
||||
export class UmbSectionElement extends UmbLitElement {
|
||||
@@ -173,9 +174,6 @@ export class UmbSectionElement extends UmbLitElement {
|
||||
return {
|
||||
path: 'view/' + view.meta.pathname,
|
||||
component: () => createExtensionElement(view),
|
||||
setup: () => {
|
||||
this._sectionContext?.setActiveView(view);
|
||||
},
|
||||
};
|
||||
}) ?? [];
|
||||
|
||||
@@ -187,6 +185,13 @@ export class UmbSectionElement extends UmbLitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _onRouteChange = (event: UmbRouterSlotChangeEvent) => {
|
||||
const currentPath = event.target.localActiveViewPath;
|
||||
const view = this._views?.find((view) => 'view/' + view.meta.pathname === currentPath);
|
||||
if (!view) return;
|
||||
this._sectionContext?.setActiveView(view);
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
${this._menuItems && this._menuItems.length > 0
|
||||
@@ -199,7 +204,7 @@ export class UmbSectionElement extends UmbLitElement {
|
||||
<umb-section-main>
|
||||
${this._views && this._views.length > 0 ? html`<umb-section-views></umb-section-views>` : nothing}
|
||||
${this._routes && this._routes.length > 0
|
||||
? html`<router-slot id="router-slot" .routes="${this._routes}"></router-slot>`
|
||||
? html`<umb-router-slot id="router-slot" .routes="${this._routes}" @change=${this._onRouteChange}></umb-router-slot>`
|
||||
: nothing}
|
||||
<slot></slot>
|
||||
</umb-section-main>
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { html } from 'lit';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { customElement, property, state } from 'lit/decorators.js';
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import '../../../components/input-checkbox-list/input-checkbox-list.element';
|
||||
import { UmbInputCheckboxListElement } from '../../../components/input-checkbox-list/input-checkbox-list.element';
|
||||
import { UmbLitElement } from '@umbraco-cms/element';
|
||||
import type { DataTypePropertyData } from '@umbraco-cms/models';
|
||||
|
||||
/**
|
||||
* @element umb-property-editor-ui-checkbox-list
|
||||
@@ -10,14 +13,37 @@ import { UmbLitElement } from '@umbraco-cms/element';
|
||||
export class UmbPropertyEditorUICheckboxListElement extends UmbLitElement {
|
||||
static styles = [UUITextStyles];
|
||||
|
||||
@property()
|
||||
value = '';
|
||||
private _value: Array<string> = [];
|
||||
@property({ type: Array })
|
||||
public get value(): Array<string> {
|
||||
return this._value;
|
||||
}
|
||||
public set value(value: Array<string>) {
|
||||
this._value = value || [];
|
||||
}
|
||||
|
||||
@property({ type: Array, attribute: false })
|
||||
public config = [];
|
||||
public set config(config: Array<DataTypePropertyData>) {
|
||||
const listData = config.find((x) => x.alias === 'itemList');
|
||||
|
||||
if (!listData) return;
|
||||
this._list = listData.value;
|
||||
}
|
||||
|
||||
@state()
|
||||
private _list: [] = [];
|
||||
|
||||
private _onChange(event: CustomEvent) {
|
||||
this.value = (event.target as UmbInputCheckboxListElement).selectedKeys;
|
||||
this.dispatchEvent(new CustomEvent('property-value-change'));
|
||||
console.log(this._value);
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<div>umb-property-editor-ui-checkbox-list</div>`;
|
||||
return html`<umb-input-checkbox-list
|
||||
@change="${this._onChange}"
|
||||
.selectedKeys="${this._value}"
|
||||
.list="${this._list}"></umb-input-checkbox-list>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { html } from 'lit';
|
||||
import { customElement, property, state } from 'lit/decorators.js';
|
||||
import { UmbInputDocumentPickerElement } from '../../../components/input-document-picker/input-document-picker.element';
|
||||
import { UmbLitElement } from '@umbraco-cms/element';
|
||||
import type { UmbInputDocumentPickerElement } from 'src/backoffice/shared/components/input-document-picker/input-document-picker.element';
|
||||
import '../../../components/input-document-picker/input-document-picker.element';
|
||||
import type { DataTypePropertyData } from '@umbraco-cms/models';
|
||||
|
||||
@customElement('umb-property-editor-ui-document-picker')
|
||||
|
||||
@@ -1,23 +1,53 @@
|
||||
import { html } from 'lit';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { customElement, property, state } from 'lit/decorators.js';
|
||||
import { UmbInputMediaPickerElement } from '../../../../../backoffice/shared/components/input-media-picker/input-media-picker.element';
|
||||
import { UmbLitElement } from '@umbraco-cms/element';
|
||||
import type { DataTypePropertyData } from '@umbraco-cms/models';
|
||||
|
||||
/**
|
||||
* @element umb-property-editor-ui-media-picker
|
||||
*/
|
||||
@customElement('umb-property-editor-ui-media-picker')
|
||||
export class UmbPropertyEditorUIMediaPickerElement extends UmbLitElement {
|
||||
static styles = [UUITextStyles];
|
||||
private _value: Array<string> = [];
|
||||
|
||||
@property()
|
||||
value = '';
|
||||
@property({ type: Array })
|
||||
public get value(): Array<string> {
|
||||
return this._value;
|
||||
}
|
||||
public set value(value: Array<string>) {
|
||||
this._value = value || [];
|
||||
}
|
||||
|
||||
@property({ type: Array, attribute: false })
|
||||
public config = [];
|
||||
public set config(config: Array<DataTypePropertyData>) {
|
||||
const validationLimit = config.find((x) => x.alias === 'validationLimit');
|
||||
if (!validationLimit) return;
|
||||
|
||||
this._limitMin = (validationLimit?.value as any).min;
|
||||
this._limitMax = (validationLimit?.value as any).max;
|
||||
}
|
||||
|
||||
@state()
|
||||
private _limitMin?: number;
|
||||
@state()
|
||||
private _limitMax?: number;
|
||||
|
||||
private _onChange(event: CustomEvent) {
|
||||
this.value = (event.target as UmbInputMediaPickerElement).selectedKeys;
|
||||
this.dispatchEvent(new CustomEvent('property-value-change'));
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<div>umb-property-editor-ui-media-picker</div>`;
|
||||
return html`
|
||||
<umb-input-media-picker
|
||||
@change=${this._onChange}
|
||||
.selectedKeys=${this._value}
|
||||
.min=${this._limitMin}
|
||||
.max=${this._limitMax}
|
||||
>Add</umb-input-media-picker
|
||||
>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { css, html } from 'lit';
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { customElement, state } from 'lit/decorators.js';
|
||||
import type { IRoute, IRoutingInfo } from 'router-slot';
|
||||
import { UmbUserStore, UMB_USER_STORE_CONTEXT_TOKEN } from '../../../users/user.store';
|
||||
import { umbExtensionsRegistry , createExtensionElement } from '@umbraco-cms/extensions-api';
|
||||
|
||||
import './list-view-layouts/table/workspace-view-users-table.element';
|
||||
@@ -9,7 +10,6 @@ import './list-view-layouts/grid/workspace-view-users-grid.element';
|
||||
import './workspace-view-users-selection.element';
|
||||
import './workspace-view-users-invite.element';
|
||||
import type { ManifestWorkspace, UserDetails } from '@umbraco-cms/models';
|
||||
import { UmbUserStore, UMB_USER_STORE_CONTEXT_TOKEN } from 'src/backoffice/users/users/user.store';
|
||||
import { UmbLitElement } from '@umbraco-cms/element';
|
||||
import { DeepState } from '@umbraco-cms/observable-api';
|
||||
|
||||
@@ -128,7 +128,7 @@ export class UmbSectionViewUsersElement extends UmbLitElement {
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<router-slot .routes=${this._routes}></router-slot>`;
|
||||
return html`<umb-router-slot .routes=${this._routes}></umb-router-slot>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -220,7 +220,7 @@ export class UmbWorkspaceViewUsersOverviewElement extends UmbLitElement {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<router-slot .routes=${this._routes}></router-slot>
|
||||
<umb-router-slot .routes=${this._routes}></umb-router-slot>
|
||||
|
||||
${this._renderSelection()}
|
||||
`;
|
||||
|
||||
@@ -255,7 +255,15 @@ export const data: Array<DataTypeDetails> = [
|
||||
isFolder: false,
|
||||
propertyEditorModelAlias: 'Umbraco.CheckboxList',
|
||||
propertyEditorUIAlias: 'Umb.PropertyEditorUI.CheckboxList',
|
||||
data: [],
|
||||
data: [
|
||||
{
|
||||
alias: 'itemList',
|
||||
value: [
|
||||
{ label: 'Label 1', key: '123' },
|
||||
{ label: 'Label 2', key: '456' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Block List',
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import type { UUIColorSwatchesEvent } from '@umbraco-ui/uui-color-swatches';
|
||||
import type { UUIColorSwatchesEvent } from '@umbraco-ui/uui';
|
||||
|
||||
import { css, html } from 'lit';
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { customElement, property, state } from 'lit/decorators.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
import { UmbModalLayoutElement } from '../modal-layout.element';
|
||||
|
||||
import icons from '../../../../../public-assets/icons/icons.json';
|
||||
|
||||
import '@umbraco-ui/uui-color-swatch';
|
||||
import '@umbraco-ui/uui-color-swatches';
|
||||
|
||||
export interface UmbModalIconPickerData {
|
||||
multiple: boolean;
|
||||
selection: string[];
|
||||
@@ -56,6 +54,7 @@ export class UmbModalLayoutIconPickerElement extends UmbModalLayoutElement<UmbMo
|
||||
overflow-y: scroll;
|
||||
max-height: 100%;
|
||||
min-height: 0;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#icon-selection .icon {
|
||||
@@ -74,7 +73,7 @@ export class UmbModalLayoutIconPickerElement extends UmbModalLayoutElement<UmbMo
|
||||
#icon-selection .icon:focus,
|
||||
#icon-selection .icon:hover,
|
||||
#icon-selection .icon.selected {
|
||||
background-color: var(--uui-color-selected);
|
||||
outline: 2px solid var(--uui-color-selected);
|
||||
}
|
||||
|
||||
uui-button {
|
||||
@@ -91,7 +90,7 @@ export class UmbModalLayoutIconPickerElement extends UmbModalLayoutElement<UmbMo
|
||||
iconlist = icons.map((icon) => icon.name);
|
||||
|
||||
@property({ type: Array })
|
||||
iconlistFiltered: Array<string>;
|
||||
iconlistFiltered: Array<string> = [];
|
||||
|
||||
@property({ type: Array })
|
||||
colorlist = [
|
||||
@@ -118,10 +117,10 @@ export class UmbModalLayoutIconPickerElement extends UmbModalLayoutElement<UmbMo
|
||||
];
|
||||
|
||||
@state()
|
||||
private _currentColor: string;
|
||||
private _currentColor?: string;
|
||||
|
||||
@state()
|
||||
private _currentIcon: string;
|
||||
private _currentIcon?: string;
|
||||
|
||||
private _changeIcon(e: { target: HTMLInputElement; type: any; key: unknown }) {
|
||||
if (e.type == 'click' || (e.type == 'keyup' && e.key == 'Enter')) {
|
||||
@@ -137,10 +136,6 @@ export class UmbModalLayoutIconPickerElement extends UmbModalLayoutElement<UmbMo
|
||||
}
|
||||
}
|
||||
|
||||
private _setColor(color: string) {
|
||||
return 'color: ' + color;
|
||||
}
|
||||
|
||||
private _close() {
|
||||
this.modalHandler?.close();
|
||||
}
|
||||
@@ -149,11 +144,8 @@ export class UmbModalLayoutIconPickerElement extends UmbModalLayoutElement<UmbMo
|
||||
this.modalHandler?.close({ color: this._currentColor, icon: this._currentIcon });
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._currentColor = '';
|
||||
this._currentIcon = '';
|
||||
this.iconlistFiltered = [];
|
||||
private _onColorChange(e: UUIColorSwatchesEvent) {
|
||||
this._currentColor = e.target.value;
|
||||
}
|
||||
|
||||
connectedCallback(): void {
|
||||
@@ -170,8 +162,14 @@ export class UmbModalLayoutIconPickerElement extends UmbModalLayoutElement<UmbMo
|
||||
${this.renderSearchbar()}
|
||||
<hr />
|
||||
<uui-color-swatches
|
||||
.swatches="${this.colorlist}"
|
||||
@change="${(e: UUIColorSwatchesEvent) => (this._currentColor = e.target.value)}"></uui-color-swatches>
|
||||
.value="${this._currentColor || ''}"
|
||||
label="Color switcher for icons"
|
||||
@change="${this._onColorChange}">
|
||||
${this.colorlist.map(
|
||||
(color) =>
|
||||
html` <uui-color-swatch label="${color}" title="${color}" value="${color}"></uui-color-swatch> `
|
||||
)}
|
||||
</uui-color-swatches>
|
||||
|
||||
<hr />
|
||||
<uui-scroll-container id="icon-selection">${this.renderIconSelection()}</uui-scroll-container>
|
||||
@@ -199,8 +197,9 @@ export class UmbModalLayoutIconPickerElement extends UmbModalLayoutElement<UmbMo
|
||||
return html`
|
||||
<uui-icon
|
||||
tabindex="0"
|
||||
.style="${this._setColor(this._currentColor)}"
|
||||
style=${styleMap({ color: this._currentColor })}
|
||||
class="icon ${icon === this._currentIcon ? 'selected' : ''}"
|
||||
title="${icon}"
|
||||
name="${icon}"
|
||||
label="${icon}"
|
||||
id="${icon}"
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user