Merge branch 'main' into v14/feature/readonly-properties
This commit is contained in:
@@ -702,6 +702,10 @@ export const data: Array<UmbMockDataTypeModel> = [
|
||||
alias: 'blockGroups',
|
||||
value: [{ key: 'demo-block-group-id', name: 'Demo Blocks' }],
|
||||
},
|
||||
{
|
||||
alias: 'layoutStylesheet',
|
||||
value: '/wwwroot/css/umbraco-blockgridlayout.css'
|
||||
},
|
||||
{
|
||||
alias: 'blocks',
|
||||
value: [
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { UmbBlockGridLayoutModel, UmbBlockGridTypeModel } from '../types.js';
|
||||
import type { UmbBlockGridWorkspaceData } from '../index.js';
|
||||
import { UmbArrayState, appendToFrozenArray, pushAtToUniqueArray } from '@umbraco-cms/backoffice/observable-api';
|
||||
import { removeInitialSlashFromPath, transformServerPathToClientPath } from '@umbraco-cms/backoffice/utils';
|
||||
import { removeLastSlashFromPath, transformServerPathToClientPath } from '@umbraco-cms/backoffice/utils';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { UMB_APP_CONTEXT } from '@umbraco-cms/backoffice/app';
|
||||
import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor';
|
||||
@@ -29,7 +29,7 @@ export class UmbBlockGridManagerContext<
|
||||
|
||||
if (layoutStylesheet) {
|
||||
// Cause we await initAppUrl in setting the _editorConfiguration, we can trust the appUrl begin here.
|
||||
return this.#appUrl! + removeInitialSlashFromPath(transformServerPathToClientPath(layoutStylesheet));
|
||||
return removeLastSlashFromPath(this.#appUrl!) + transformServerPathToClientPath(layoutStylesheet);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
@@ -6,13 +6,13 @@ import { html, customElement, property, state, ifDefined } from '@umbraco-cms/ba
|
||||
import { UmbRepositoryItemsManager } from '@umbraco-cms/backoffice/repository';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { UMB_APP_CONTEXT } from '@umbraco-cms/backoffice/app';
|
||||
import { removeInitialSlashFromPath, transformServerPathToClientPath } from '@umbraco-cms/backoffice/utils';
|
||||
import { removeLastSlashFromPath, transformServerPathToClientPath } from '@umbraco-cms/backoffice/utils';
|
||||
|
||||
@customElement('umb-block-type-card')
|
||||
export class UmbBlockTypeCardElement extends UmbLitElement {
|
||||
//
|
||||
#init: Promise<void>;
|
||||
#appUrl?: string;
|
||||
#appUrl: string = '';
|
||||
|
||||
#itemManager = new UmbRepositoryItemsManager<UmbDocumentTypeItemModel>(
|
||||
this,
|
||||
@@ -28,7 +28,7 @@ export class UmbBlockTypeCardElement extends UmbLitElement {
|
||||
value = transformServerPathToClientPath(value);
|
||||
if (value) {
|
||||
this.#init.then(() => {
|
||||
this._iconFile = this.#appUrl + removeInitialSlashFromPath(value);
|
||||
this._iconFile = removeLastSlashFromPath(this.#appUrl) + value;
|
||||
});
|
||||
} else {
|
||||
this._iconFile = undefined;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { UmbBlockTypeBaseModel } from '@umbraco-cms/backoffice/extension-registry';
|
||||
export type { UmbBlockTypeBaseModel } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
export interface UmbBlockTypeGroup {
|
||||
name?: string;
|
||||
|
||||
@@ -12,6 +12,7 @@ export * from './path/path-decode.function.js';
|
||||
export * from './path/path-encode.function.js';
|
||||
export * from './path/path-folder-name.function.js';
|
||||
export * from './path/remove-initial-slash-from-path.function.js';
|
||||
export * from './path/remove-last-slash-from-path.function.js';
|
||||
export * from './path/stored-path.function.js';
|
||||
export * from './path/transform-server-path-to-client-path.function.js';
|
||||
export * from './path/umbraco-path.function.js';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
*
|
||||
* Removes the initial slash from a path, if the first character is a slash.
|
||||
* @param path
|
||||
*/
|
||||
export function removeInitialSlashFromPath(path: string) {
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Remove the last slash from a path, if the last character is a slash.
|
||||
* @param path
|
||||
*/
|
||||
export function removeLastSlashFromPath(path: string) {
|
||||
return path.endsWith('/') ? path.slice(undefined, -1) : path;
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
import { UmbMemberCollectionRepository } from '../../collection/index.js';
|
||||
import { UmbMemberSearchProvider } from '../../search/member.search-provider.js';
|
||||
import type { UmbMemberDetailModel } from '../../types.js';
|
||||
import type { UmbMemberItemModel } from '../../repository/index.js';
|
||||
import type { UmbMemberPickerModalValue, UmbMemberPickerModalData } from './member-picker-modal.token.js';
|
||||
import { html, customElement, state, repeat } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbSelectionManager } from '@umbraco-cms/backoffice/utils';
|
||||
import { css, customElement, html, nothing, repeat, state, when } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { debounce, UmbSelectionManager } from '@umbraco-cms/backoffice/utils';
|
||||
import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import type { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui';
|
||||
|
||||
@customElement('umb-member-picker-modal')
|
||||
export class UmbMemberPickerModalElement extends UmbModalBaseElement<
|
||||
@@ -13,8 +17,18 @@ export class UmbMemberPickerModalElement extends UmbModalBaseElement<
|
||||
@state()
|
||||
private _members: Array<UmbMemberDetailModel> = [];
|
||||
|
||||
@state()
|
||||
private _searchQuery: string = '';
|
||||
|
||||
@state()
|
||||
private _searchResult: Array<UmbMemberItemModel> = [];
|
||||
|
||||
@state()
|
||||
private _searching = false;
|
||||
|
||||
#collectionRepository = new UmbMemberCollectionRepository(this);
|
||||
#selectionManager = new UmbSelectionManager(this);
|
||||
#searchProvider = new UmbMemberSearchProvider(this);
|
||||
|
||||
override connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
@@ -23,6 +37,18 @@ export class UmbMemberPickerModalElement extends UmbModalBaseElement<
|
||||
this.#selectionManager.setSelection(this.value?.selection ?? []);
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.observe(
|
||||
this.#selectionManager.selection,
|
||||
(selection) => {
|
||||
this.updateValue({ selection });
|
||||
this.requestUpdate();
|
||||
},
|
||||
'umbSelectionObserver',
|
||||
);
|
||||
}
|
||||
|
||||
override async firstUpdated() {
|
||||
const { data } = await this.#collectionRepository.requestCollection({});
|
||||
this._members = data?.items ?? [];
|
||||
@@ -36,43 +62,143 @@ export class UmbMemberPickerModalElement extends UmbModalBaseElement<
|
||||
}
|
||||
}
|
||||
|
||||
#submit() {
|
||||
this.value = { selection: this.#selectionManager.getSelection() };
|
||||
this.modalContext?.submit();
|
||||
#onSearchInput(event: UUIInputEvent) {
|
||||
const value = event.target.value as string;
|
||||
this._searchQuery = value;
|
||||
|
||||
if (!this._searchQuery) {
|
||||
this._searchResult = [];
|
||||
this._searching = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this._searching = true;
|
||||
this.#debouncedSearch();
|
||||
}
|
||||
|
||||
#close() {
|
||||
this.modalContext?.reject();
|
||||
#debouncedSearch = debounce(this.#search, 300);
|
||||
|
||||
async #search() {
|
||||
if (!this._searchQuery) return;
|
||||
const { data } = await this.#searchProvider.search({ query: this._searchQuery });
|
||||
this._searchResult = data?.items ?? [];
|
||||
this._searching = false;
|
||||
}
|
||||
|
||||
#onSearchClear() {
|
||||
this._searchQuery = '';
|
||||
this._searchResult = [];
|
||||
}
|
||||
|
||||
override render() {
|
||||
return html`<umb-body-layout headline=${this.localize.term('defaultdialogs_selectMembers')}>
|
||||
<uui-box>
|
||||
${repeat(
|
||||
this.#filteredMembers,
|
||||
(item) => item.unique,
|
||||
(item) => html`
|
||||
<uui-menu-item
|
||||
label=${item.variants[0].name ?? ''}
|
||||
selectable
|
||||
@selected=${() => this.#selectionManager.select(item.unique)}
|
||||
@deselected=${() => this.#selectionManager.deselect(item.unique)}
|
||||
?selected=${this.#selectionManager.isSelected(item.unique)}>
|
||||
<uui-icon slot="icon" name="icon-globe"></uui-icon>
|
||||
</uui-menu-item>
|
||||
return html`
|
||||
<umb-body-layout headline=${this.localize.term('defaultdialogs_selectMembers')}>
|
||||
<uui-box>${this.#renderSearch()} ${this.#renderItems()}</uui-box>
|
||||
<div slot="actions">
|
||||
<uui-button
|
||||
label=${this.localize.term('general_cancel')}
|
||||
@click=${() => this.modalContext?.reject()}></uui-button>
|
||||
<uui-button
|
||||
color="positive"
|
||||
look="primary"
|
||||
label=${this.localize.term('general_submit')}
|
||||
@click=${() => this.modalContext?.submit()}></uui-button>
|
||||
</div>
|
||||
</umb-body-layout>
|
||||
`;
|
||||
}
|
||||
|
||||
#renderItems() {
|
||||
if (this._searchQuery) return nothing;
|
||||
return html`
|
||||
${repeat(
|
||||
this.#filteredMembers,
|
||||
(item) => item.unique,
|
||||
(item) => this.#renderMemberItem(item),
|
||||
)}
|
||||
`;
|
||||
}
|
||||
|
||||
#renderSearch() {
|
||||
return html`
|
||||
<uui-input
|
||||
id="search-input"
|
||||
placeholder=${this.localize.term('placeholders_search')}
|
||||
.value=${this._searchQuery}
|
||||
@input=${this.#onSearchInput}>
|
||||
<div slot="prepend">
|
||||
${this._searching
|
||||
? html`<uui-loader-circle id="search-indicator"></uui-loader-circle>`
|
||||
: html`<uui-icon name="search"></uui-icon>`}
|
||||
</div>
|
||||
${when(
|
||||
this._searchQuery,
|
||||
() => html`
|
||||
<div slot="append">
|
||||
<uui-button type="button" @click=${this.#onSearchClear} compact>
|
||||
<uui-icon name="icon-delete"></uui-icon>
|
||||
</uui-button>
|
||||
</div>
|
||||
`,
|
||||
)}
|
||||
</uui-box>
|
||||
<div slot="actions">
|
||||
<uui-button label=${this.localize.term('general_cancel')} @click=${this.#close}></uui-button>
|
||||
<uui-button
|
||||
label=${this.localize.term('general_submit')}
|
||||
look="primary"
|
||||
color="positive"
|
||||
@click=${this.#submit}></uui-button>
|
||||
</div>
|
||||
</umb-body-layout> `;
|
||||
</uui-input>
|
||||
<div id="search-divider"></div>
|
||||
${this.#renderSearchResult()}
|
||||
`;
|
||||
}
|
||||
|
||||
#renderSearchResult() {
|
||||
if (this._searchQuery && this._searching === false && this._searchResult.length === 0) {
|
||||
return this.#renderEmptySearchResult();
|
||||
}
|
||||
|
||||
return html`
|
||||
${repeat(
|
||||
this._searchResult,
|
||||
(item) => item.unique,
|
||||
(item) => this.#renderMemberItem(item),
|
||||
)}
|
||||
`;
|
||||
}
|
||||
|
||||
#renderEmptySearchResult() {
|
||||
return html`<small>No result for <strong>"${this._searchQuery}"</strong>.</small>`;
|
||||
}
|
||||
|
||||
#renderMemberItem(item: UmbMemberItemModel | UmbMemberDetailModel) {
|
||||
return html`
|
||||
<uui-menu-item
|
||||
label=${item.variants[0].name ?? ''}
|
||||
selectable
|
||||
@selected=${() => this.#selectionManager.select(item.unique)}
|
||||
@deselected=${() => this.#selectionManager.deselect(item.unique)}
|
||||
?selected=${this.#selectionManager.isSelected(item.unique)}>
|
||||
<uui-icon slot="icon" name="icon-user"></uui-icon>
|
||||
</uui-menu-item>
|
||||
`;
|
||||
}
|
||||
|
||||
static override styles = [
|
||||
UmbTextStyles,
|
||||
css`
|
||||
#search-input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#search-divider {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: var(--uui-color-divider);
|
||||
margin-top: var(--uui-size-space-5);
|
||||
margin-bottom: var(--uui-size-space-3);
|
||||
}
|
||||
|
||||
#search-indicator {
|
||||
margin-left: 7px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
export default UmbMemberPickerModalElement;
|
||||
|
||||
Reference in New Issue
Block a user