Merge remote-tracking branch 'origin/feature/permissions' into feature/permissions
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
import { UmbInputListBaseElement } from '../input-list-base/input-list-base.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 { UMB_SECTION_PICKER_MODAL } from '@umbraco-cms/backoffice/modal';
|
||||
import { ManifestSection, umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
@customElement('umb-input-section')
|
||||
export class UmbInputPickerSectionElement extends UmbInputListBaseElement {
|
||||
export class UmbInputSectionElement extends UmbInputListBaseElement {
|
||||
@state()
|
||||
private _sections: Array<ManifestSection> = [];
|
||||
|
||||
@@ -47,7 +47,7 @@ export class UmbInputPickerSectionElement extends UmbInputListBaseElement {
|
||||
label="remove"
|
||||
color="danger"></uui-button>
|
||||
</div>
|
||||
`
|
||||
`,
|
||||
)}
|
||||
</div>
|
||||
`;
|
||||
@@ -85,6 +85,6 @@ export class UmbInputPickerSectionElement extends UmbInputListBaseElement {
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-input-section': UmbInputPickerSectionElement;
|
||||
'umb-input-section': UmbInputSectionElement;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Meta, StoryObj } from '@storybook/web-components';
|
||||
import './input-section.element.js';
|
||||
import type { UmbInputPickerSectionElement } from './input-section.element.js';
|
||||
import type { UmbInputSectionElement } from './input-section.element.js';
|
||||
|
||||
const meta: Meta<UmbInputPickerSectionElement> = {
|
||||
const meta: Meta<UmbInputSectionElement> = {
|
||||
title: 'Components/Inputs/Section',
|
||||
component: 'umb-input-section',
|
||||
argTypes: {
|
||||
@@ -22,7 +22,7 @@ const meta: Meta<UmbInputPickerSectionElement> = {
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<UmbInputPickerSectionElement>;
|
||||
type Story = StoryObj<UmbInputSectionElement>;
|
||||
|
||||
export const Overview: Story = {
|
||||
args: {
|
||||
|
||||
@@ -66,7 +66,6 @@ export class UmbInputDocumentGranularPermissionElement extends FormControlMixin(
|
||||
});
|
||||
|
||||
modalContext?.onSubmit().then(({ selection }: any) => {
|
||||
debugger;
|
||||
//this.#setSelection(selection);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import { UmbPickerInputContext } from '@umbraco-cms/backoffice/picker-input';
|
||||
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { UMB_DOCUMENT_PICKER_MODAL } from '@umbraco-cms/backoffice/modal';
|
||||
import { DocumentItemResponseModel } from '@umbraco-cms/backoffice/backend-api';
|
||||
|
||||
export class UmbDocumentPickerContext extends UmbPickerInputContext<DocumentItemResponseModel> {
|
||||
constructor(host: UmbControllerHostElement) {
|
||||
super(host, 'Umb.Repository.Document', UMB_DOCUMENT_PICKER_MODAL);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,8 @@
|
||||
import { UMB_DOCUMENT_TREE_STORE_CONTEXT_TOKEN } from '../../repository/document.tree.store.js';
|
||||
import type { UmbDocumentTreeStore } from '../../repository/document.tree.store.js';
|
||||
import { css, html, nothing, customElement, property, state, ifDefined } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbDocumentPickerContext } from './input-document.context.js';
|
||||
import { css, html, customElement, property, state, ifDefined } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui';
|
||||
import {
|
||||
UmbModalManagerContext,
|
||||
UMB_MODAL_MANAGER_CONTEXT_TOKEN,
|
||||
UMB_CONFIRM_MODAL,
|
||||
UMB_DOCUMENT_PICKER_MODAL,
|
||||
} from '@umbraco-cms/backoffice/modal';
|
||||
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
|
||||
import type { DocumentTreeItemResponseModel, EntityTreeItemResponseModel } from '@umbraco-cms/backoffice/backend-api';
|
||||
import type { UmbObserverController } from '@umbraco-cms/backoffice/observable-api';
|
||||
import type { DocumentItemResponseModel } from '@umbraco-cms/backoffice/backend-api';
|
||||
|
||||
@customElement('umb-input-document')
|
||||
export class UmbInputDocumentElement extends FormControlMixin(UmbLitElement) {
|
||||
@@ -18,10 +10,15 @@ export class UmbInputDocumentElement extends FormControlMixin(UmbLitElement) {
|
||||
* This is a minimum amount of selected items in this input.
|
||||
* @type {number}
|
||||
* @attr
|
||||
* @default undefined
|
||||
* @default 0
|
||||
*/
|
||||
@property({ type: Number })
|
||||
min?: number;
|
||||
public get min(): number {
|
||||
return this.#pickerContext.min;
|
||||
}
|
||||
public set min(value: number) {
|
||||
this.#pickerContext.min = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Min validation message.
|
||||
@@ -36,10 +33,15 @@ export class UmbInputDocumentElement extends FormControlMixin(UmbLitElement) {
|
||||
* This is a maximum amount of selected items in this input.
|
||||
* @type {number}
|
||||
* @attr
|
||||
* @default undefined
|
||||
* @default Infinity
|
||||
*/
|
||||
@property({ type: Number })
|
||||
max?: number;
|
||||
public get max(): number {
|
||||
return this.#pickerContext.max;
|
||||
}
|
||||
public set max(value: number) {
|
||||
this.#pickerContext.max = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Max validation message.
|
||||
@@ -50,30 +52,23 @@ export class UmbInputDocumentElement extends FormControlMixin(UmbLitElement) {
|
||||
@property({ type: String, attribute: 'min-message' })
|
||||
maxMessage = 'This field exceeds the allowed amount of items';
|
||||
|
||||
// TODO: do we need both selectedIds and value? If we just use value we follow the same pattern as native form controls.
|
||||
private _selectedIds: Array<string> = [];
|
||||
public get selectedIds(): Array<string> {
|
||||
return this._selectedIds;
|
||||
return this.#pickerContext.getSelection();
|
||||
}
|
||||
public set selectedIds(ids: Array<string>) {
|
||||
this._selectedIds = ids;
|
||||
super.value = ids.join(',');
|
||||
this._observePickedDocuments();
|
||||
this.#pickerContext.setSelection(ids);
|
||||
}
|
||||
|
||||
@property()
|
||||
public set value(idsString: string) {
|
||||
if (idsString !== this._value) {
|
||||
this.selectedIds = idsString.split(/[ ,]+/);
|
||||
}
|
||||
// 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<DocumentTreeItemResponseModel>;
|
||||
private _items?: Array<DocumentItemResponseModel>;
|
||||
|
||||
private _modalContext?: UmbModalManagerContext;
|
||||
private _documentStore?: UmbDocumentTreeStore;
|
||||
private _pickedItemsObserver?: UmbObserverController<EntityTreeItemResponseModel[]>;
|
||||
#pickerContext = new UmbDocumentPickerContext(this);
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@@ -81,84 +76,43 @@ export class UmbInputDocumentElement extends FormControlMixin(UmbLitElement) {
|
||||
this.addValidator(
|
||||
'rangeUnderflow',
|
||||
() => this.minMessage,
|
||||
() => !!this.min && this._selectedIds.length < this.min,
|
||||
() => !!this.min && this.#pickerContext.getSelection().length < this.min,
|
||||
);
|
||||
|
||||
this.addValidator(
|
||||
'rangeOverflow',
|
||||
() => this.maxMessage,
|
||||
() => !!this.max && this._selectedIds.length > this.max,
|
||||
() => !!this.max && this.#pickerContext.getSelection().length > this.max,
|
||||
);
|
||||
|
||||
this.consumeContext(UMB_DOCUMENT_TREE_STORE_CONTEXT_TOKEN, (instance) => {
|
||||
this._documentStore = instance;
|
||||
this._observePickedDocuments();
|
||||
});
|
||||
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT_TOKEN, (instance) => {
|
||||
this._modalContext = instance;
|
||||
});
|
||||
this.observe(this.#pickerContext.selection, (selection) => (super.value = selection.join(',')));
|
||||
this.observe(this.#pickerContext.selectedItems, (selectedItems) => (this._items = selectedItems));
|
||||
}
|
||||
|
||||
protected getFormElement() {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private _observePickedDocuments() {
|
||||
this._pickedItemsObserver?.destroy();
|
||||
|
||||
if (!this._documentStore) return;
|
||||
|
||||
// TODO: consider changing this to the list data endpoint when it is available
|
||||
this._pickedItemsObserver = this.observe(this._documentStore.items(this._selectedIds), (items) => {
|
||||
this._items = items;
|
||||
});
|
||||
}
|
||||
|
||||
private _openPicker() {
|
||||
// We send a shallow copy(good enough as its just an array of ids) of our this._selectedIds, as we don't want the modal to manipulate our data:
|
||||
const modalContext = this._modalContext?.open(UMB_DOCUMENT_PICKER_MODAL, {
|
||||
multiple: this.max === 1 ? false : true,
|
||||
selection: [...this._selectedIds],
|
||||
});
|
||||
|
||||
modalContext?.onSubmit().then(({ selection }: any) => {
|
||||
this._setSelection(selection);
|
||||
});
|
||||
}
|
||||
|
||||
private async _removeItem(item: EntityTreeItemResponseModel) {
|
||||
const modalContext = this._modalContext?.open(UMB_CONFIRM_MODAL, {
|
||||
color: 'danger',
|
||||
headline: `Remove ${item.name}?`,
|
||||
content: 'Are you sure you want to remove this item',
|
||||
confirmLabel: 'Remove',
|
||||
});
|
||||
|
||||
await modalContext?.onSubmit();
|
||||
const newSelection = this._selectedIds.filter((value) => value !== item.id);
|
||||
this._setSelection(newSelection);
|
||||
}
|
||||
|
||||
private _setSelection(newSelection: Array<string>) {
|
||||
this.selectedIds = newSelection;
|
||||
this.dispatchEvent(new CustomEvent('change', { bubbles: true, composed: true }));
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
${this._items?.map((item) => this._renderItem(item))}
|
||||
<uui-button id="add-button" look="placeholder" @click=${this._openPicker} label="open">Add</uui-button>
|
||||
<uui-ref-list>${this._items?.map((item) => this._renderItem(item))}</uui-ref-list>
|
||||
<uui-button id="add-button" look="placeholder" @click=${() => this.#pickerContext.openPicker()} label="open"
|
||||
>Add</uui-button
|
||||
>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderItem(item: EntityTreeItemResponseModel) {
|
||||
// TODO: remove when we have a way to handle trashed items
|
||||
const tempItem = item as EntityTreeItemResponseModel & { isTrashed: boolean };
|
||||
|
||||
private _renderItem(item: DocumentItemResponseModel) {
|
||||
if (!item.id) return;
|
||||
return html`
|
||||
<uui-ref-node name=${ifDefined(item.name === null ? undefined : item.name)} detail=${ifDefined(item.id)}>
|
||||
${tempItem.isTrashed ? html` <uui-tag size="s" slot="tag" color="danger">Trashed</uui-tag> ` : nothing}
|
||||
<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._removeItem(item)} label="Remove document ${item.name}">Remove</uui-button>
|
||||
<uui-button
|
||||
@click=${() => this.#pickerContext.requestRemoveItem(item.id!)}
|
||||
label="Remove document ${item.name}"
|
||||
>Remove</uui-button
|
||||
>
|
||||
</uui-action-bar>
|
||||
</uui-ref-node>
|
||||
`;
|
||||
|
||||
@@ -33,8 +33,6 @@ export class UmbDocumentPermissionsEntityAction extends UmbEntityActionBase<UmbD
|
||||
|
||||
const { selection } = await modalContext.onSubmit();
|
||||
console.log(selection);
|
||||
debugger;
|
||||
|
||||
//await this.repository?.setPermissions();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ export * from './repository/index.js';
|
||||
export * from './workspace/index.js';
|
||||
export * from './recycle-bin/index.js';
|
||||
export * from './user-permissions/index.js';
|
||||
export * from './components/index.js';
|
||||
|
||||
import './components/index.js';
|
||||
|
||||
|
||||
@@ -46,19 +46,19 @@ export class UmbDocumentRepository
|
||||
this.#init = Promise.all([
|
||||
new UmbContextConsumerController(this.#host, UMB_DOCUMENT_TREE_STORE_CONTEXT_TOKEN, (instance) => {
|
||||
this.#treeStore = instance;
|
||||
}),
|
||||
}).asPromise(),
|
||||
|
||||
new UmbContextConsumerController(this.#host, UMB_DOCUMENT_STORE_CONTEXT_TOKEN, (instance) => {
|
||||
this.#store = instance;
|
||||
}),
|
||||
}).asPromise(),
|
||||
|
||||
new UmbContextConsumerController(this.#host, UMB_DOCUMENT_ITEM_STORE_CONTEXT_TOKEN, (instance) => {
|
||||
this.#itemStore = instance;
|
||||
}),
|
||||
}).asPromise(),
|
||||
|
||||
new UmbContextConsumerController(this.#host, UMB_NOTIFICATION_CONTEXT_TOKEN, (instance) => {
|
||||
this.#notificationContext = instance;
|
||||
}),
|
||||
}).asPromise(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import { UmbDocumentRepository } from '../repository/document.repository.js';
|
||||
import { UmbDocumentItemStore } from './document-item.store.js';
|
||||
import { UmbDocumentStore } from './document.store.js';
|
||||
import { UmbDocumentTreeStore } from './document.tree.store.js';
|
||||
import type { ManifestRepository, ManifestStore, ManifestTreeStore } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import type {
|
||||
ManifestItemStore,
|
||||
ManifestRepository,
|
||||
ManifestStore,
|
||||
ManifestTreeStore,
|
||||
} from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
export const DOCUMENT_REPOSITORY_ALIAS = 'Umb.Repository.Document';
|
||||
|
||||
@@ -14,6 +20,7 @@ const repository: ManifestRepository = {
|
||||
|
||||
export const DOCUMENT_STORE_ALIAS = 'Umb.Store.Document';
|
||||
export const DOCUMENT_TREE_STORE_ALIAS = 'Umb.Store.DocumentTree';
|
||||
export const DOCUMENT_ITEM_STORE_ALIAS = 'Umb.Store.DocumentItem';
|
||||
|
||||
const store: ManifestStore = {
|
||||
type: 'store',
|
||||
@@ -29,4 +36,11 @@ const treeStore: ManifestTreeStore = {
|
||||
class: UmbDocumentTreeStore,
|
||||
};
|
||||
|
||||
export const manifests = [repository, store, treeStore];
|
||||
const itemStore: ManifestItemStore = {
|
||||
type: 'itemStore',
|
||||
alias: DOCUMENT_ITEM_STORE_ALIAS,
|
||||
name: 'Document Item Store',
|
||||
class: UmbDocumentItemStore,
|
||||
};
|
||||
|
||||
export const manifests = [repository, store, treeStore, itemStore];
|
||||
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
UmbModalManagerContext,
|
||||
} from '@umbraco-cms/backoffice/modal';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import { UmbInputDocumentElement } from '@umbraco-cms/backoffice/document';
|
||||
import { UmbInputSectionElement } from '@umbraco-cms/backoffice/components';
|
||||
|
||||
import './components/user-group-default-permission-list.element.js';
|
||||
import './components/user-group-granular-permission-list.element.js';
|
||||
@@ -42,8 +44,14 @@ export class UmbUserGroupWorkspaceEditorElement extends UmbLitElement {
|
||||
this.#workspaceContext?.updateUserKeys(userIds);
|
||||
}
|
||||
|
||||
#onSectionsChange(value: string[]) {
|
||||
this.#workspaceContext?.updateProperty('sections', value);
|
||||
#onSectionsChange(event: CustomEvent) {
|
||||
const target = event.target as UmbInputSectionElement;
|
||||
this.#workspaceContext?.updateProperty('sections', target.value);
|
||||
}
|
||||
|
||||
#onDocumentStartNodeChange(event: CustomEvent) {
|
||||
const target = event.target as UmbInputDocumentElement;
|
||||
this.#workspaceContext?.updateProperty('documentStartNodeId', target.selectedIds[0]);
|
||||
}
|
||||
|
||||
async #onDelete() {
|
||||
@@ -116,7 +124,7 @@ export class UmbUserGroupWorkspaceEditorElement extends UmbLitElement {
|
||||
<umb-input-section
|
||||
slot="editor"
|
||||
.value=${this._userGroup.sections ?? []}
|
||||
@change=${(e: any) => this.#onSectionsChange(e.target.value)}></umb-input-section>
|
||||
@change=${this.#onSectionsChange}></umb-input-section>
|
||||
</umb-workspace-property-layout>
|
||||
<umb-workspace-property-layout
|
||||
label=${this.localize.term('defaultdialogs_selectContentStartNode')}
|
||||
@@ -125,7 +133,7 @@ export class UmbUserGroupWorkspaceEditorElement extends UmbLitElement {
|
||||
slot="editor"
|
||||
max="1"
|
||||
.selectedIds=${this._userGroup.documentStartNodeId ? [this._userGroup.documentStartNodeId] : []}
|
||||
@change=${(e: any) => this.#onSectionsChange(e.target.value)}
|
||||
@change=${this.#onDocumentStartNodeChange}
|
||||
multiple></umb-input-document>
|
||||
</umb-workspace-property-layout>
|
||||
<umb-workspace-property-layout
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { UmbUserPickerContext } from './user-input.context.js';
|
||||
import { css, html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { css, html, customElement, property, state, ifDefined } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui';
|
||||
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
|
||||
import type { UserItemResponseModel } from '@umbraco-cms/backoffice/backend-api';
|
||||
@@ -76,13 +76,13 @@ export class UmbUserInputElement extends FormControlMixin(UmbLitElement) {
|
||||
this.addValidator(
|
||||
'rangeUnderflow',
|
||||
() => this.minMessage,
|
||||
() => !!this.min && this.#pickerContext.getSelection().length < this.min
|
||||
() => !!this.min && this.#pickerContext.getSelection().length < this.min,
|
||||
);
|
||||
|
||||
this.addValidator(
|
||||
'rangeOverflow',
|
||||
() => this.maxMessage,
|
||||
() => !!this.max && this.#pickerContext.getSelection().length > this.max
|
||||
() => !!this.max && this.#pickerContext.getSelection().length > this.max,
|
||||
);
|
||||
|
||||
this.observe(this.#pickerContext.selection, (selection) => (super.value = selection.join(',')));
|
||||
@@ -105,7 +105,7 @@ export class UmbUserInputElement extends FormControlMixin(UmbLitElement) {
|
||||
private _renderItem(item: UserItemResponseModel) {
|
||||
if (!item.id) return;
|
||||
return html`
|
||||
<uui-ref-node-user name=${item.name}>
|
||||
<uui-ref-node-user name=${ifDefined(item.name)}>
|
||||
<uui-action-bar slot="actions">
|
||||
<uui-button @click=${() => this.#pickerContext.requestRemoveItem(item.id!)} label="Remove ${item.name}"
|
||||
>Remove</uui-button
|
||||
|
||||
Reference in New Issue
Block a user