Merge branch 'chore/update-form-mixin' of https://github.com/umbraco/Umbraco.CMS.Backoffice into chore/update-form-mixin

This commit is contained in:
Niels Lyngsø
2024-04-17 15:35:15 +02:00
3 changed files with 160 additions and 37 deletions

View File

@@ -1893,6 +1893,8 @@ export default {
searchAllChildren: 'Search all children',
languagesHelp: 'Limit the languages users have access to edit',
allowAccessToAllLanguages: 'Allow access to all languages',
allowAccessToAllDocuments: 'Allow access to all documents',
allowAccessToAllMedia: 'Allow access to all media',
sectionsHelp: 'Add sections to give users access',
selectUserGroup: (multiple: boolean) => {
return multiple ? 'Select User Groups' : 'Select User Group';

View File

@@ -1,12 +1,9 @@
import type { UmbUserGroupDetailModel } from '../../types.js';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { html, customElement, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import type { UmbDefaultCollectionContext } from '@umbraco-cms/backoffice/collection';
import { UMB_DEFAULT_COLLECTION_CONTEXT } from '@umbraco-cms/backoffice/collection';
import '../components/user-group-table-name-column-layout.element.js';
import '../components/user-group-table-sections-column-layout.element.js';
import type {
UmbTableColumn,
UmbTableConfig,
@@ -15,7 +12,13 @@ import type {
UmbTableItem,
UmbTableSelectedEvent,
} from '@umbraco-cms/backoffice/components';
import type { UmbUserGroupDetailModel } from '../../types.js';
import { UmbDocumentItemRepository } from '@umbraco-cms/backoffice/document';
import { UmbMediaItemRepository } from '@umbraco-cms/backoffice/media';
import type { UmbItemRepository } from '@umbraco-cms/backoffice/repository';
import type { UmbUniqueItemModel } from '@umbraco-cms/backoffice/models';
import '../components/user-group-table-name-column-layout.element.js';
import '../components/user-group-table-sections-column-layout.element.js';
@customElement('umb-user-group-collection-table-view')
export class UmbUserGroupCollectionTableViewElement extends UmbLitElement {
@@ -54,6 +57,13 @@ export class UmbUserGroupCollectionTableViewElement extends UmbLitElement {
#collectionContext?: UmbDefaultCollectionContext;
// TODO: hardcoded dependencies on document and media modules. We should figure out how these dependencies can be added through extensions.
#documentItemRepository = new UmbDocumentItemRepository(this);
#mediaItemRepository = new UmbMediaItemRepository(this);
#documentStartNodeMap = new Map<string, string>();
#mediaStartNodeMap = new Map<string, string>();
constructor() {
super();
@@ -74,7 +84,17 @@ export class UmbUserGroupCollectionTableViewElement extends UmbLitElement {
});
}
private _createTableItems(userGroups: Array<UmbUserGroupDetailModel>) {
private async _createTableItems(userGroups: Array<UmbUserGroupDetailModel>) {
await Promise.all([
this.#requestAndCacheStartNodes(
userGroups,
'documentStartNode',
this.#documentItemRepository,
this.#documentStartNodeMap,
),
this.#requestAndCacheStartNodes(userGroups, 'mediaStartNode', this.#mediaItemRepository, this.#mediaStartNodeMap),
]);
this._tableItems = userGroups.map((userGroup) => {
return {
id: userGroup.unique,
@@ -92,25 +112,52 @@ export class UmbUserGroupCollectionTableViewElement extends UmbLitElement {
},
{
columnAlias: 'userGroupContentStartNode',
value: userGroup.documentStartNode?.unique || this.localize.term('content_contentRoot'),
value: userGroup.documentStartNode
? this.#documentStartNodeMap.get(userGroup.documentStartNode.unique)
: this.localize.term('content_contentRoot'),
},
{
columnAlias: 'userGroupMediaStartNode',
value: userGroup.mediaStartNode?.unique || this.localize.term('media_mediaRoot'),
value: userGroup.mediaStartNode?.unique
? this.#mediaStartNodeMap.get(userGroup.mediaStartNode.unique)
: this.localize.term('media_mediaRoot'),
},
],
};
});
}
private _handleSelected(event: UmbTableSelectedEvent) {
async #requestAndCacheStartNodes(
userGroups: Array<UmbUserGroupDetailModel>,
startNodeField: 'documentStartNode' | 'mediaStartNode',
itemRepository: UmbItemRepository<UmbUniqueItemModel>,
map: Map<string, string>,
) {
const allStartNodes = userGroups.map((userGroup) => userGroup[startNodeField]?.unique).filter(Boolean) as string[];
const uniqueStartNodes = [...new Set(allStartNodes)];
const uncachedStartNodes = uniqueStartNodes.filter((unique) => !map.has(unique));
// If there are no uncached start nodes, we don't need to make a request
if (uncachedStartNodes.length === 0) return;
const { data: items } = await itemRepository.requestItems(uncachedStartNodes);
if (items) {
items.forEach((item) => {
// cache the start node
map.set(item.unique, item.name);
});
}
}
#onSelected(event: UmbTableSelectedEvent) {
event.stopPropagation();
const table = event.target as UmbTableElement;
const selection = table.selection;
this.#collectionContext?.selection.setSelection(selection);
}
private _handleDeselected(event: UmbTableDeselectedEvent) {
#onDeselected(event: UmbTableDeselectedEvent) {
event.stopPropagation();
const table = event.target as UmbTableElement;
const selection = table.selection;
@@ -124,8 +171,8 @@ export class UmbUserGroupCollectionTableViewElement extends UmbLitElement {
.columns=${this._tableColumns}
.items=${this._tableItems}
.selection=${this._selection}
@selected="${this._handleSelected}"
@deselected="${this._handleDeselected}"></umb-table>
@selected="${this.#onSelected}"
@deselected="${this.#onDeselected}"></umb-table>
`;
}

View File

@@ -39,9 +39,15 @@ export class UmbUserGroupWorkspaceEditorElement extends UmbLitElement {
@state()
private _documentStartNode?: UmbUserGroupDetailModel['documentStartNode'];
@state()
private _documentRootAccess: UmbUserGroupDetailModel['documentRootAccess'] = false;
@state()
private _mediaStartNode?: UmbUserGroupDetailModel['mediaStartNode'];
@state()
private _mediaRootAccess: UmbUserGroupDetailModel['mediaRootAccess'] = false;
#workspaceContext?: typeof UMB_USER_GROUP_WORKSPACE_CONTEXT.TYPE;
constructor() {
@@ -66,46 +72,82 @@ export class UmbUserGroupWorkspaceEditorElement extends UmbLitElement {
'_observeHasAccessToAllLanguages',
);
this.observe(
this.#workspaceContext.documentRootAccess,
(value) => (this._documentRootAccess = value),
'_observeDocumentRootAccess',
);
this.observe(
this.#workspaceContext.documentStartNode,
(value) => (this._documentStartNode = value),
'_observeDocumentStartNode',
);
this.observe(
this.#workspaceContext.mediaRootAccess,
(value) => (this._mediaRootAccess = value),
'_observeMediaRootAccess',
);
this.observe(
this.#workspaceContext.mediaStartNode,
(value) => (this._mediaStartNode = value),
'_observeDocumentStartNode',
'_observeMediaStartNode',
);
}
#onSectionsChange(event: UmbChangeEvent) {
event.stopPropagation();
const target = event.target as UmbInputSectionElement;
// TODO make contexts method
this.#workspaceContext?.updateProperty('sections', target.selection);
}
#onAllowAllLanguagesChange(event: UUIBooleanInputEvent) {
event.stopPropagation();
const target = event.target;
// TODO make contexts method
this.#workspaceContext?.updateProperty('hasAccessToAllLanguages', target.checked);
}
#onLanguagePermissionChange(event: UmbChangeEvent) {
event.stopPropagation();
const target = event.target as UmbInputLanguageElement;
// TODO make contexts method
this.#workspaceContext?.updateProperty('languages', target.selection);
}
#onAllowAllDocumentsChange(event: UUIBooleanInputEvent) {
event.stopPropagation();
const target = event.target;
// TODO make contexts method
this.#workspaceContext?.updateProperty('documentRootAccess', target.checked);
this.#workspaceContext?.updateProperty('documentStartNode', null);
}
#onDocumentStartNodeChange(event: CustomEvent) {
event.stopPropagation();
const target = event.target as UmbInputDocumentElement;
this.#workspaceContext?.updateProperty('documentStartNode', { unique: target.selection[0] });
const selected = target.selection?.[0];
// TODO make contexts method
this.#workspaceContext?.updateProperty('documentStartNode', selected ? { unique: selected } : null);
}
#onAllowAllMediaChange(event: UUIBooleanInputEvent) {
event.stopPropagation();
const target = event.target;
// TODO make contexts method
this.#workspaceContext?.updateProperty('mediaRootAccess', target.checked);
this.#workspaceContext?.updateProperty('mediaStartNode', null);
}
#onMediaStartNodeChange(event: CustomEvent) {
event.stopPropagation();
const target = event.target as UmbInputMediaElement;
this.#workspaceContext?.updateProperty('mediaStartNode', { unique: target.selection[0] });
const selected = target.selection?.[0];
// TODO make contexts method
this.#workspaceContext?.updateProperty('mediaStartNode', selected ? { unique: selected } : null);
}
#onNameChange(event: UUIInputEvent) {
@@ -188,27 +230,7 @@ export class UmbUserGroupWorkspaceEditorElement extends UmbLitElement {
@change=${this.#onSectionsChange}></umb-input-section>
</umb-property-layout>
${this.#renderLanguagePermissions()}
<umb-property-layout
label=${this.localize.term('defaultdialogs_selectContentStartNode')}
description=${this.localize.term('user_startnodehelp')}>
<umb-input-document
slot="editor"
max="1"
.selection=${this._documentStartNode?.unique ? [this._documentStartNode.unique] : []}
@change=${this.#onDocumentStartNodeChange}></umb-input-document>
</umb-property-layout>
<umb-property-layout
label=${this.localize.term('defaultdialogs_selectMediaStartNode')}
description=${this.localize.term('user_mediastartnodehelp')}>
<umb-input-media
slot="editor"
max="1"
.selection=${this._mediaStartNode?.unique ? [this._mediaStartNode.unique] : []}
@change=${this.#onMediaStartNodeChange}></umb-input-media>
</umb-property-layout>
${this.#renderLanguageAccess()} ${this.#renderDocumentAccess()} ${this.#renderMediaAccess()}
</uui-box>
<uui-box>
@@ -226,7 +248,7 @@ export class UmbUserGroupWorkspaceEditorElement extends UmbLitElement {
`;
}
#renderLanguagePermissions() {
#renderLanguageAccess() {
return html`
<umb-property-layout
label=${this.localize.term('treeHeaders_languages')}
@@ -250,6 +272,58 @@ export class UmbUserGroupWorkspaceEditorElement extends UmbLitElement {
`;
}
#renderDocumentAccess() {
return html`
<umb-property-layout
label=${this.localize.term('defaultdialogs_selectContentStartNode')}
description=${this.localize.term('user_startnodehelp')}>
<div slot="editor">
<uui-toggle
style="margin-bottom: var(--uui-size-space-3);"
label="${this.localize.term('user_allowAccessToAllDocuments')}"
.checked=${this._documentRootAccess}
@change=${this.#onAllowAllDocumentsChange}></uui-toggle>
</div>
${this._documentRootAccess === false
? html`
<umb-input-document
slot="editor"
max="1"
.selection=${this._documentStartNode?.unique ? [this._documentStartNode.unique] : []}
@change=${this.#onDocumentStartNodeChange}></umb-input-document>
`
: nothing}
</umb-property-layout>
`;
}
#renderMediaAccess() {
return html`
<umb-property-layout
label=${this.localize.term('defaultdialogs_selectMediaStartNode')}
description=${this.localize.term('user_mediastartnodehelp')}>
<div slot="editor">
<uui-toggle
style="margin-bottom: var(--uui-size-space-3);"
label="${this.localize.term('user_allowAccessToAllMedia')}"
.checked=${this._mediaRootAccess}
@change=${this.#onAllowAllMediaChange}></uui-toggle>
</div>
${this._mediaRootAccess === false
? html`
<umb-input-media
slot="editor"
max="1"
.selection=${this._mediaStartNode?.unique ? [this._mediaStartNode.unique] : []}
@change=${this.#onMediaStartNodeChange}></umb-input-media>
`
: nothing}
</umb-property-layout>
`;
}
#renderRightColumn() {
return html` <uui-box headline="Actions">
<umb-entity-action-list .entityType=${UMB_USER_GROUP_ENTITY_TYPE} .unique=${this._unique}></umb-entity-action-list