Merge branch 'main' into feature/property-editor-tags
This commit is contained in:
@@ -3,12 +3,12 @@ import { UmbModalToken } from '@umbraco-cms/backoffice/modal';
|
||||
|
||||
export interface UmbLanguagePickerModalData {
|
||||
multiple?: boolean;
|
||||
selection?: Array<string>;
|
||||
selection?: Array<string | null>;
|
||||
filter?: (language: LanguageResponseModel) => boolean;
|
||||
}
|
||||
|
||||
export interface UmbLanguagePickerModalResult {
|
||||
selection: Array<string>;
|
||||
selection: Array<string | null>;
|
||||
}
|
||||
|
||||
export const UMB_LANGUAGE_PICKER_MODAL = new UmbModalToken<UmbLanguagePickerModalData, UmbLanguagePickerModalResult>(
|
||||
|
||||
@@ -2,10 +2,17 @@ import { UmbModalToken } from '@umbraco-cms/backoffice/modal';
|
||||
|
||||
export interface UmbSectionPickerModalData {
|
||||
multiple: boolean;
|
||||
selection: string[];
|
||||
selection: Array<string | null>;
|
||||
}
|
||||
|
||||
export const UMB_SECTION_PICKER_MODAL = new UmbModalToken<UmbSectionPickerModalData>('Umb.Modal.SectionPicker', {
|
||||
type: 'sidebar',
|
||||
size: 'small',
|
||||
});
|
||||
export interface UmbSectionPickerModalResult {
|
||||
selection: Array<string | null>;
|
||||
}
|
||||
|
||||
export const UMB_SECTION_PICKER_MODAL = new UmbModalToken<UmbSectionPickerModalData, UmbSectionPickerModalResult>(
|
||||
'Umb.Modal.SectionPicker',
|
||||
{
|
||||
type: 'sidebar',
|
||||
size: 'small',
|
||||
}
|
||||
);
|
||||
|
||||
@@ -4,7 +4,7 @@ import { UserResponseModel } from '@umbraco-cms/backoffice/backend-api';
|
||||
export type UmbUserPickerModalData = UmbPickerModalData<UserResponseModel>;
|
||||
|
||||
export interface UmbUserPickerModalResult {
|
||||
selection: Array<string>;
|
||||
selection: Array<string | null>;
|
||||
}
|
||||
|
||||
export const UMB_USER_PICKER_MODAL = new UmbModalToken<UmbUserPickerModalData, UmbUserPickerModalResult>(
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
import { Observable, map } from 'rxjs';
|
||||
import { UmbPagedData, UmbTreeRepository } from '@umbraco-cms/backoffice/repository';
|
||||
import type { ManifestTree } from '@umbraco-cms/backoffice/extensions-registry';
|
||||
import { UmbBooleanState, UmbArrayState, UmbObserverController } from '@umbraco-cms/backoffice/observable-api';
|
||||
import { UmbBooleanState, UmbObserverController } from '@umbraco-cms/backoffice/observable-api';
|
||||
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller';
|
||||
import { createExtensionClass, umbExtensionsRegistry } from '@umbraco-cms/backoffice/extensions-api';
|
||||
import { ProblemDetailsModel, TreeItemPresentationModel } from '@umbraco-cms/backoffice/backend-api';
|
||||
import { UmbContextProviderController } from '@umbraco-cms/backoffice/context-api';
|
||||
import { UmbSelectionManagerBase } from '@umbraco-cms/backoffice/utils';
|
||||
|
||||
// TODO: update interface
|
||||
export interface UmbTreeContext<TreeItemType extends TreeItemPresentationModel> {
|
||||
readonly selectable: Observable<boolean>;
|
||||
readonly selection: Observable<Array<string | null>>;
|
||||
setSelectable(value: boolean): void;
|
||||
getSelectable(): boolean;
|
||||
setMultiple(value: boolean): void;
|
||||
getMultiple(): boolean;
|
||||
setSelection(value: Array<string | null>): void;
|
||||
getSelection(): Array<string | null>;
|
||||
select(unique: string | null): void;
|
||||
deselect(unique: string | null): void;
|
||||
requestChildrenOf: (parentUnique: string | null) => Promise<{
|
||||
@@ -28,18 +32,16 @@ export class UmbTreeContextBase<TreeItemType extends TreeItemPresentationModel>
|
||||
{
|
||||
public host: UmbControllerHostElement;
|
||||
|
||||
#selectionManager = new UmbSelectionManagerBase();
|
||||
|
||||
#selectable = new UmbBooleanState(false);
|
||||
public readonly selectable = this.#selectable.asObservable();
|
||||
|
||||
#multiple = new UmbBooleanState(false);
|
||||
public readonly multiple = this.#multiple.asObservable();
|
||||
|
||||
#selection = new UmbArrayState(<Array<string | null>>[]);
|
||||
public readonly selection = this.#selection.asObservable();
|
||||
public readonly multiple = this.#selectionManager.multiple;
|
||||
public readonly selection = this.#selectionManager.selection;
|
||||
|
||||
#treeAlias?: string;
|
||||
repository?: UmbTreeRepository<TreeItemType>;
|
||||
|
||||
#treeManifestObserver?: UmbObserverController<any>;
|
||||
|
||||
#initResolver?: () => void;
|
||||
@@ -84,32 +86,29 @@ export class UmbTreeContextBase<TreeItemType extends TreeItemPresentationModel>
|
||||
}
|
||||
|
||||
public setMultiple(value: boolean) {
|
||||
this.#multiple.next(value);
|
||||
this.#selectionManager.setMultiple(value);
|
||||
}
|
||||
|
||||
public getMultiple() {
|
||||
return this.#multiple.getValue();
|
||||
return this.#selectionManager.getMultiple();
|
||||
}
|
||||
|
||||
public setSelection(value: Array<string | null>) {
|
||||
if (!value) return;
|
||||
this.#selection.next(value);
|
||||
this.#selectionManager.setSelection(value);
|
||||
}
|
||||
|
||||
public getSelection() {
|
||||
return this.#selection.getValue();
|
||||
return this.#selectionManager.getSelection();
|
||||
}
|
||||
|
||||
public select(unique: string | null) {
|
||||
if (!this.getSelectable()) return;
|
||||
const newSelection = this.getMultiple() ? [...this.getSelection(), unique] : [unique];
|
||||
this.#selection.next(newSelection);
|
||||
this.#selectionManager.select(unique);
|
||||
this.host.dispatchEvent(new CustomEvent('selected'));
|
||||
}
|
||||
|
||||
public deselect(unique: string | null) {
|
||||
const newSelection = this.getSelection().filter((x) => x !== unique);
|
||||
this.#selection.next(newSelection);
|
||||
this.#selectionManager.deselect(unique);
|
||||
this.host.dispatchEvent(new CustomEvent('selected'));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './umbraco-path';
|
||||
export * from './udi-service';
|
||||
export * from './selection-manager';
|
||||
|
||||
61
src/Umbraco.Web.UI.Client/libs/utils/selection-manager.ts
Normal file
61
src/Umbraco.Web.UI.Client/libs/utils/selection-manager.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { Observable } from 'rxjs';
|
||||
import { UmbArrayState, UmbBooleanState } from '../observable-api';
|
||||
|
||||
export interface UmbSelectionManager {
|
||||
selection: Observable<Array<string | null>>;
|
||||
multiple: Observable<boolean>;
|
||||
|
||||
getSelection(): Array<string | null>;
|
||||
setSelection(value: Array<string | null>): void;
|
||||
|
||||
getMultiple(): boolean;
|
||||
setMultiple(value: boolean): void;
|
||||
|
||||
toggleSelect(unique: string | null): void;
|
||||
select(unique: string | null): void;
|
||||
deselect(unique: string | null): void;
|
||||
isSelected(unique: string | null): boolean;
|
||||
}
|
||||
|
||||
export class UmbSelectionManagerBase implements UmbSelectionManager {
|
||||
#selection = new UmbArrayState(<Array<string | null>>[]);
|
||||
public readonly selection = this.#selection.asObservable();
|
||||
|
||||
#multiple = new UmbBooleanState(false);
|
||||
public readonly multiple = this.#multiple.asObservable();
|
||||
|
||||
public getSelection() {
|
||||
return this.#selection.getValue();
|
||||
}
|
||||
|
||||
public setSelection(value: Array<string | null>) {
|
||||
if (value === undefined) throw new Error('Value cannot be undefined');
|
||||
this.#selection.next(value);
|
||||
}
|
||||
|
||||
public getMultiple() {
|
||||
return this.#multiple.getValue();
|
||||
}
|
||||
|
||||
public setMultiple(value: boolean) {
|
||||
this.#multiple.next(value);
|
||||
}
|
||||
|
||||
public toggleSelect(unique: string | null) {
|
||||
this.isSelected(unique) ? this.deselect(unique) : this.select(unique);
|
||||
}
|
||||
|
||||
public select(unique: string | null) {
|
||||
const newSelection = this.getMultiple() ? [...this.getSelection(), unique] : [unique];
|
||||
this.#selection.next(newSelection);
|
||||
}
|
||||
|
||||
public deselect(unique: string | null) {
|
||||
const newSelection = this.getSelection().filter((x) => x !== unique);
|
||||
this.#selection.next(newSelection);
|
||||
}
|
||||
|
||||
public isSelected(unique: string | null) {
|
||||
return this.getSelection().includes(unique);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import 'element-internals-polyfill';
|
||||
|
||||
import './core/router/router-slot.element';
|
||||
import './core/router/variant-router-slot.element';
|
||||
import './core/context-provider/context-provider.element';
|
||||
|
||||
import { UUIIconRegistryEssential } from '@umbraco-ui/uui';
|
||||
import { css, html } from 'lit';
|
||||
|
||||
@@ -34,6 +34,7 @@ export class UmbTreeElement extends UmbLitElement {
|
||||
return this.#treeContext.getSelection();
|
||||
}
|
||||
set selection(newVal) {
|
||||
if (!Array.isArray(newVal)) return;
|
||||
this.#treeContext?.setSelection(newVal);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,96 +1,69 @@
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css';
|
||||
import { css, html } from 'lit';
|
||||
import { customElement, state } from 'lit/decorators.js';
|
||||
import { UmbModalElementPickerBase } from '@umbraco-cms/internal/modal';
|
||||
import { UmbSelectionManagerBase } from '@umbraco-cms/backoffice/utils';
|
||||
import { UmbModalBaseElement } from '@umbraco-cms/internal/modal';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extensions-api';
|
||||
import type { ManifestSection } from '@umbraco-cms/backoffice/extensions-registry';
|
||||
import { UmbSectionPickerModalData, UmbSectionPickerModalResult } from '@umbraco-cms/backoffice/modal';
|
||||
|
||||
@customElement('umb-section-picker-modal')
|
||||
export class UmbSectionPickerModalElement extends UmbModalElementPickerBase<ManifestSection> {
|
||||
|
||||
|
||||
export class UmbSectionPickerModalElement extends UmbModalBaseElement<
|
||||
UmbSectionPickerModalData,
|
||||
UmbSectionPickerModalResult
|
||||
> {
|
||||
@state()
|
||||
private _sections: Array<ManifestSection> = [];
|
||||
|
||||
#selectionManager = new UmbSelectionManagerBase();
|
||||
|
||||
#submit() {
|
||||
this.modalHandler?.submit({
|
||||
selection: this.#selectionManager.getSelection(),
|
||||
});
|
||||
}
|
||||
|
||||
#close() {
|
||||
this.modalHandler?.reject();
|
||||
}
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this.observe(umbExtensionsRegistry.extensionsOfType('section'), (sections: Array<ManifestSection>) => {
|
||||
this._sections = sections;
|
||||
});
|
||||
|
||||
// TODO: in theory this config could change during the lifetime of the modal, so we could observe it
|
||||
this.#selectionManager.setMultiple(this.data?.multiple ?? false);
|
||||
this.#selectionManager.setSelection(this.data?.selection ?? []);
|
||||
|
||||
this.observe(
|
||||
umbExtensionsRegistry.extensionsOfType('section'),
|
||||
(sections: Array<ManifestSection>) => (this._sections = sections)
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<umb-workspace-editor headline="Select sections">
|
||||
<uui-box>
|
||||
<uui-input label="search"></uui-input>
|
||||
<hr />
|
||||
<div id="item-list">
|
||||
${this._sections.map(
|
||||
(item) => html`
|
||||
<div
|
||||
@click=${() => this.handleSelection(item.alias)}
|
||||
@keydown=${(e: KeyboardEvent) => this._handleKeydown(e, item.alias)}
|
||||
class=${this.isSelected(item.alias) ? 'item selected' : 'item'}>
|
||||
<span>${item.meta.label}</span>
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
${this._sections.map(
|
||||
(item) => html`
|
||||
<uui-menu-item
|
||||
label=${item.meta.label}
|
||||
selectable
|
||||
?selected=${this.#selectionManager.isSelected(item.alias)}
|
||||
@selected=${() => this.#selectionManager.select(item.alias)}
|
||||
@unselected=${() => this.#selectionManager.deselect(item.alias)}></uui-menu-item>
|
||||
`
|
||||
)}
|
||||
</uui-box>
|
||||
<div slot="actions">
|
||||
<uui-button label="Close" @click=${this.close}></uui-button>
|
||||
<uui-button label="Submit" look="primary" color="positive" @click=${this.submit}></uui-button>
|
||||
<uui-button label="Close" @click=${this.#close}></uui-button>
|
||||
<uui-button label="Submit" look="primary" color="positive" @click=${this.#submit}></uui-button>
|
||||
</div>
|
||||
</umb-workspace-editor>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
uui-input {
|
||||
width: 100%;
|
||||
}
|
||||
hr {
|
||||
border: none;
|
||||
border-bottom: 1px solid var(--uui-color-divider);
|
||||
margin: 16px 0;
|
||||
}
|
||||
#item-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--uui-size-1);
|
||||
}
|
||||
.item {
|
||||
color: var(--uui-color-interactive);
|
||||
display: grid;
|
||||
grid-template-columns: var(--uui-size-8) 1fr;
|
||||
padding: var(--uui-size-4) var(--uui-size-2);
|
||||
gap: var(--uui-size-space-5);
|
||||
align-items: center;
|
||||
border-radius: var(--uui-border-radius);
|
||||
cursor: pointer;
|
||||
}
|
||||
.item.selected {
|
||||
background-color: var(--uui-color-selected);
|
||||
color: var(--uui-color-selected-contrast);
|
||||
}
|
||||
.item:not(.selected):hover {
|
||||
background-color: var(--uui-color-surface-emphasis);
|
||||
color: var(--uui-color-interactive-emphasis);
|
||||
}
|
||||
.item.selected:hover {
|
||||
background-color: var(--uui-color-selected-emphasis);
|
||||
}
|
||||
.item uui-icon {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
height: fit-content;
|
||||
}
|
||||
`,
|
||||
];
|
||||
static styles = [UUITextStyles, css``];
|
||||
}
|
||||
|
||||
export default UmbSectionPickerModalElement;
|
||||
|
||||
@@ -2,32 +2,32 @@ import { UUITextStyles } from '@umbraco-ui/uui-css';
|
||||
import { css, html } from 'lit';
|
||||
import { customElement, state } from 'lit/decorators.js';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
import { UUIMenuItemElement, UUIMenuItemEvent } from '@umbraco-ui/uui';
|
||||
import { ifDefined } from 'lit/directives/if-defined.js';
|
||||
import { UmbLanguageRepository } from '../../repository/language.repository';
|
||||
import { UmbModalElementPickerBase } from '@umbraco-cms/internal/modal';
|
||||
import { UmbModalBaseElement } from '@umbraco-cms/internal/modal';
|
||||
import { LanguageResponseModel } from '@umbraco-cms/backoffice/backend-api';
|
||||
import { UmbSelectionManagerBase } from '@umbraco-cms/backoffice/utils';
|
||||
import { UmbLanguagePickerModalResult, UmbLanguagePickerModalData } from '@umbraco-cms/backoffice/modal';
|
||||
|
||||
@customElement('umb-language-picker-modal')
|
||||
export class UmbLanguagePickerModalElement extends UmbModalElementPickerBase<LanguageResponseModel> {
|
||||
|
||||
|
||||
export class UmbLanguagePickerModalElement extends UmbModalBaseElement<
|
||||
UmbLanguagePickerModalData,
|
||||
UmbLanguagePickerModalResult
|
||||
> {
|
||||
@state()
|
||||
private _languages: Array<LanguageResponseModel> = [];
|
||||
|
||||
private _languageRepository = new UmbLanguageRepository(this);
|
||||
#languageRepository = new UmbLanguageRepository(this);
|
||||
#selectionManager = new UmbSelectionManagerBase();
|
||||
|
||||
async firstUpdated() {
|
||||
const { data } = await this._languageRepository.requestLanguages();
|
||||
this._languages = data?.items ?? [];
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this.#selectionManager.setMultiple(this.data?.multiple ?? false);
|
||||
this.#selectionManager.setSelection(this.data?.selection ?? []);
|
||||
}
|
||||
|
||||
#onSelection(event: UUIMenuItemEvent) {
|
||||
event?.stopPropagation();
|
||||
const language = event?.target as UUIMenuItemElement;
|
||||
const isoCode = language.dataset.isoCode;
|
||||
if (!isoCode) return;
|
||||
this.handleSelection(isoCode);
|
||||
async firstUpdated() {
|
||||
const { data } = await this.#languageRepository.requestLanguages();
|
||||
this._languages = data?.items ?? [];
|
||||
}
|
||||
|
||||
get #filteredLanguages() {
|
||||
@@ -38,6 +38,14 @@ export class UmbLanguagePickerModalElement extends UmbModalElementPickerBase<Lan
|
||||
}
|
||||
}
|
||||
|
||||
#submit() {
|
||||
this.modalHandler?.submit({ selection: this.#selectionManager.getSelection() });
|
||||
}
|
||||
|
||||
#close() {
|
||||
this.modalHandler?.reject();
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<umb-body-layout headline="Select languages">
|
||||
<uui-box>
|
||||
@@ -47,23 +55,22 @@ export class UmbLanguagePickerModalElement extends UmbModalElementPickerBase<Lan
|
||||
(item) => html`
|
||||
<uui-menu-item
|
||||
label=${item.name ?? ''}
|
||||
selectable="true"
|
||||
@selected=${this.#onSelection}
|
||||
@unselected=${this.#onSelection}
|
||||
?selected=${this.isSelected(item.isoCode!)}
|
||||
data-iso-code="${ifDefined(item.isoCode)}">
|
||||
selectable
|
||||
@selected=${() => this.#selectionManager.select(item.isoCode!)}
|
||||
@unselected=${() => this.#selectionManager.deselect(item.isoCode!)}
|
||||
?selected=${this.#selectionManager.isSelected(item.isoCode!)}>
|
||||
<uui-icon slot="icon" name="umb:globe"></uui-icon>
|
||||
</uui-menu-item>
|
||||
`
|
||||
)}
|
||||
</uui-box>
|
||||
<div slot="actions">
|
||||
<uui-button label="Close" @click=${this.close}></uui-button>
|
||||
<uui-button label="Submit" look="primary" color="positive" @click=${this.submit}></uui-button>
|
||||
<uui-button label="Close" @click=${this.#close}></uui-button>
|
||||
<uui-button label="Submit" look="primary" color="positive" @click=${this.#submit}></uui-button>
|
||||
</div>
|
||||
</umb-body-layout> `;
|
||||
}
|
||||
|
||||
|
||||
static styles = [UUITextStyles, css``];
|
||||
}
|
||||
|
||||
|
||||
@@ -3,17 +3,24 @@ import { css, html } from 'lit';
|
||||
import { customElement, state } from 'lit/decorators.js';
|
||||
import { UmbUserGroupStore, UMB_USER_GROUP_STORE_CONTEXT_TOKEN } from '../../repository/user-group.store';
|
||||
import type { UserGroupDetails } from '../../types';
|
||||
import { UmbModalElementPickerBase } from '@umbraco-cms/internal/modal';
|
||||
import { UmbSelectionManagerBase } from '@umbraco-cms/backoffice/utils';
|
||||
import { UmbModalBaseElement } from '@umbraco-cms/internal/modal';
|
||||
|
||||
@customElement('umb-user-group-picker-modal')
|
||||
export class UmbUserGroupPickerModalElement extends UmbModalElementPickerBase<UserGroupDetails> {
|
||||
export class UmbUserGroupPickerModalElement extends UmbModalBaseElement<any, any> {
|
||||
@state()
|
||||
private _userGroups: Array<UserGroupDetails> = [];
|
||||
|
||||
private _userGroupStore?: UmbUserGroupStore;
|
||||
#selectionManager = new UmbSelectionManagerBase();
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
|
||||
// TODO: in theory this config could change during the lifetime of the modal, so we could observe it
|
||||
this.#selectionManager.setMultiple(this.data?.multiple ?? false);
|
||||
this.#selectionManager.setSelection(this.data?.selection ?? []);
|
||||
|
||||
this.consumeContext(UMB_USER_GROUP_STORE_CONTEXT_TOKEN, (userGroupStore) => {
|
||||
this._userGroupStore = userGroupStore;
|
||||
this._observeUserGroups();
|
||||
@@ -25,79 +32,42 @@ export class UmbUserGroupPickerModalElement extends UmbModalElementPickerBase<Us
|
||||
this.observe(this._userGroupStore.getAll(), (userGroups) => (this._userGroups = userGroups));
|
||||
}
|
||||
|
||||
#submit() {
|
||||
this.modalHandler?.submit({
|
||||
selection: this.#selectionManager.getSelection(),
|
||||
});
|
||||
}
|
||||
|
||||
#close() {
|
||||
this.modalHandler?.reject();
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<umb-workspace-editor headline="Select user groups">
|
||||
<uui-box>
|
||||
<uui-input label="search"></uui-input>
|
||||
<hr />
|
||||
<div id="item-list">
|
||||
${this._userGroups.map(
|
||||
(item) => html`
|
||||
<div
|
||||
@click=${() => this.handleSelection(item.id)}
|
||||
@keydown=${(e: KeyboardEvent) => this._handleKeydown(e, item.id)}
|
||||
class=${this.isSelected(item.id) ? 'item selected' : 'item'}>
|
||||
<uui-icon .name=${item.icon}></uui-icon>
|
||||
<span>${item.name}</span>
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
${this._userGroups.map(
|
||||
(item) => html`
|
||||
<uui-menu-item
|
||||
label=${item.name}
|
||||
selectable
|
||||
@selected=${() => this.#selectionManager.select(item.id!)}
|
||||
@unselected=${() => this.#selectionManager.deselect(item.id!)}
|
||||
?selected=${this.#selectionManager.isSelected(item.id!)}>
|
||||
<uui-icon .name=${item.icon} slot="icon"></uui-icon>
|
||||
</uui-menu-item>
|
||||
`
|
||||
)}
|
||||
</uui-box>
|
||||
<div slot="actions">
|
||||
<uui-button label="Close" @click=${this.close}></uui-button>
|
||||
<uui-button label="Submit" look="primary" color="positive" @click=${this.submit}></uui-button>
|
||||
<uui-button label="Close" @click=${this.#close}></uui-button>
|
||||
<uui-button label="Submit" look="primary" color="positive" @click=${this.#submit}></uui-button>
|
||||
</div>
|
||||
</umb-workspace-editor>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
uui-input {
|
||||
width: 100%;
|
||||
}
|
||||
hr {
|
||||
border: none;
|
||||
border-bottom: 1px solid var(--uui-color-divider);
|
||||
margin: 16px 0;
|
||||
}
|
||||
#item-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--uui-size-1);
|
||||
}
|
||||
.item {
|
||||
color: var(--uui-color-interactive);
|
||||
display: grid;
|
||||
grid-template-columns: var(--uui-size-8) 1fr;
|
||||
padding: var(--uui-size-4) var(--uui-size-2);
|
||||
gap: var(--uui-size-space-5);
|
||||
align-items: center;
|
||||
border-radius: var(--uui-border-radius);
|
||||
cursor: pointer;
|
||||
}
|
||||
.item.selected {
|
||||
background-color: var(--uui-color-selected);
|
||||
color: var(--uui-color-selected-contrast);
|
||||
}
|
||||
.item:not(.selected):hover {
|
||||
background-color: var(--uui-color-surface-emphasis);
|
||||
color: var(--uui-color-interactive-emphasis);
|
||||
}
|
||||
.item.selected:hover {
|
||||
background-color: var(--uui-color-selected-emphasis);
|
||||
}
|
||||
.item uui-icon {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
height: fit-content;
|
||||
}
|
||||
`,
|
||||
];
|
||||
static styles = [UUITextStyles, css``];
|
||||
}
|
||||
|
||||
export default UmbUserGroupPickerModalElement;
|
||||
|
||||
@@ -1,30 +1,20 @@
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css';
|
||||
import { css, html } from 'lit';
|
||||
import { customElement, property, state } from 'lit/decorators.js';
|
||||
import { customElement, state } from 'lit/decorators.js';
|
||||
import { UmbUserRepository } from '../../repository/user.repository';
|
||||
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
|
||||
import { UmbModalHandler, UmbUserPickerModalData, UmbUserPickerModalResult } from '@umbraco-cms/backoffice/modal';
|
||||
import { UmbUserPickerModalData, UmbUserPickerModalResult } from '@umbraco-cms/backoffice/modal';
|
||||
import { createExtensionClass, umbExtensionsRegistry } from '@umbraco-cms/backoffice/extensions-api';
|
||||
import { UmbObserverController } from '@umbraco-cms/backoffice/observable-api';
|
||||
import { UserResponseModel } from '@umbraco-cms/backoffice/backend-api';
|
||||
import { UmbModalBaseElement } from '@umbraco-cms/internal/modal';
|
||||
import { UmbSelectionManagerBase } from '@umbraco-cms/backoffice/utils';
|
||||
|
||||
@customElement('umb-user-picker-modal')
|
||||
export class UmbUserPickerModalElement extends UmbLitElement {
|
||||
@property({ attribute: false })
|
||||
modalHandler?: UmbModalHandler<UmbUserPickerModalData, UmbUserPickerModalResult>;
|
||||
|
||||
@property({ type: Object, attribute: false })
|
||||
data?: UmbUserPickerModalData;
|
||||
|
||||
@state()
|
||||
_selection: Array<string> = [];
|
||||
|
||||
@state()
|
||||
_multiple = false;
|
||||
|
||||
export class UmbUserPickerModalElement extends UmbModalBaseElement<UmbUserPickerModalData, UmbUserPickerModalResult> {
|
||||
@state()
|
||||
private _users: Array<UserResponseModel> = [];
|
||||
|
||||
#selectionManager = new UmbSelectionManagerBase();
|
||||
#userRepository?: UmbUserRepository;
|
||||
|
||||
constructor() {
|
||||
@@ -59,7 +49,7 @@ export class UmbUserPickerModalElement extends UmbLitElement {
|
||||
}
|
||||
|
||||
#submit() {
|
||||
this.modalHandler?.submit({ selection: this._selection });
|
||||
this.modalHandler?.submit({ selection: this.#selectionManager.getSelection() });
|
||||
}
|
||||
|
||||
#close() {
|
||||
@@ -72,10 +62,14 @@ export class UmbUserPickerModalElement extends UmbLitElement {
|
||||
<uui-box>
|
||||
${this._users.map(
|
||||
(user) => html`
|
||||
<uui-menu-item label=${user.name} selectable>
|
||||
<uui-menu-item
|
||||
label=${user.name}
|
||||
selectable
|
||||
@selected=${() => this.#selectionManager.select(user.id!)}
|
||||
@unselected=${() => this.#selectionManager.deselect(user.id!)}
|
||||
?selected=${this.#selectionManager.isSelected(user.id!)}>
|
||||
<uui-avatar slot="icon" name=${user.name}></uui-avatar>
|
||||
Hello</uui-menu-item
|
||||
>
|
||||
</uui-menu-item>
|
||||
`
|
||||
)}
|
||||
</uui-box>
|
||||
|
||||
@@ -2,12 +2,21 @@ import { html } from 'lit';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
|
||||
import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller';
|
||||
import type { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
|
||||
|
||||
/**
|
||||
* Provides a value to the context down the DOM tree.
|
||||
*
|
||||
* @remarks This element is a wrapper around the `provideContext` function.
|
||||
* @slot - The context will be available to all descendants given in the default slot.
|
||||
* @throws {Error} If the key property is not set.
|
||||
* @throws {Error} If the value property is not set.
|
||||
*/
|
||||
@customElement('umb-context-provider')
|
||||
export class UmbContextProviderElement extends UmbLitElement {
|
||||
/**
|
||||
* The value to provide to the context.
|
||||
* @required
|
||||
* @optional
|
||||
*/
|
||||
@property({ type: Object, attribute: false })
|
||||
create?: (host: UmbControllerHostElement) => unknown;
|
||||
@@ -24,7 +33,7 @@ export class UmbContextProviderElement extends UmbLitElement {
|
||||
* @required
|
||||
*/
|
||||
@property({ type: String })
|
||||
key!: string;
|
||||
key!: string | UmbContextToken;
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
@@ -1,2 +1 @@
|
||||
export * from './modal-element-picker-base';
|
||||
export * from './modal-element.element';
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
import { property } from 'lit/decorators.js';
|
||||
import { UmbModalBaseElement } from './modal-element.element';
|
||||
import { UmbPickerModalData, UmbPickerModalResult } from '@umbraco-cms/backoffice/modal';
|
||||
|
||||
// TODO: we should consider moving this into a class/context instead of an element.
|
||||
// So we don't have to extend an element to get basic picker/selection logic
|
||||
export class UmbModalElementPickerBase<T> extends UmbModalBaseElement<UmbPickerModalData<T>, UmbPickerModalResult> {
|
||||
@property()
|
||||
selection: Array<string | null> = [];
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this.selection = this.data?.selection || [];
|
||||
}
|
||||
|
||||
submit() {
|
||||
this.modalHandler?.submit({ selection: this.selection });
|
||||
}
|
||||
|
||||
close() {
|
||||
this.modalHandler?.reject();
|
||||
}
|
||||
|
||||
protected _handleKeydown(e: KeyboardEvent, id?: string | null) {
|
||||
if (e.key === 'Enter') {
|
||||
this.handleSelection(id);
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: Write test for this select/deselect method. */
|
||||
handleSelection(id?: string | null) {
|
||||
if (id === undefined) throw new Error('No key provided');
|
||||
|
||||
if (this.data?.multiple) {
|
||||
if (this.isSelected(id)) {
|
||||
this.selection = this.selection.filter((selectedKey) => selectedKey !== id);
|
||||
} else {
|
||||
this.selection.push(id);
|
||||
}
|
||||
} else {
|
||||
this.selection = [id];
|
||||
}
|
||||
|
||||
this.requestUpdate('_selection');
|
||||
}
|
||||
|
||||
isSelected(id?: string | null): boolean {
|
||||
if (id === undefined) throw new Error('No Id provided');
|
||||
return this.selection.includes(id);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import { html } from 'lit';
|
||||
import { UmbInstallerContext } from '../installer.context';
|
||||
import '../../../storybook/utils/context-provider/context-provider.element';
|
||||
|
||||
export const installerContextProvider = (story: any, installerContext = new UmbInstallerContext()) => html`
|
||||
<umb-context-provider
|
||||
|
||||
Reference in New Issue
Block a user