Merge remote-tracking branch 'origin/feature/permissions' into feature/permissions

This commit is contained in:
Jesper Møller Jensen
2023-09-27 21:09:01 +13:00
11 changed files with 95 additions and 111 deletions

View File

@@ -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;
}
}

View File

@@ -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: {

View File

@@ -66,7 +66,6 @@ export class UmbInputDocumentGranularPermissionElement extends FormControlMixin(
});
modalContext?.onSubmit().then(({ selection }: any) => {
debugger;
//this.#setSelection(selection);
});
}

View File

@@ -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);
}
}

View File

@@ -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>
`;

View File

@@ -33,8 +33,6 @@ export class UmbDocumentPermissionsEntityAction extends UmbEntityActionBase<UmbD
const { selection } = await modalContext.onSubmit();
console.log(selection);
debugger;
//await this.repository?.setPermissions();
}
}

View File

@@ -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';

View File

@@ -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(),
]);
}

View File

@@ -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];

View File

@@ -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

View File

@@ -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