diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts index 0e17b9bed6..4325eecb74 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts @@ -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'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/views/user-group-table-collection-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/views/user-group-table-collection-view.element.ts index 06e927feb2..a1f71aff04 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/views/user-group-table-collection-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/views/user-group-table-collection-view.element.ts @@ -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(); + #mediaStartNodeMap = new Map(); + constructor() { super(); @@ -74,7 +84,17 @@ export class UmbUserGroupCollectionTableViewElement extends UmbLitElement { }); } - private _createTableItems(userGroups: Array) { + private async _createTableItems(userGroups: Array) { + 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, + startNodeField: 'documentStartNode' | 'mediaStartNode', + itemRepository: UmbItemRepository, + map: Map, + ) { + 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}"> + @selected="${this.#onSelected}" + @deselected="${this.#onDeselected}"> `; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/workspace/user-group-workspace-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/workspace/user-group-workspace-editor.element.ts index f3f794a314..0735e68e0c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/workspace/user-group-workspace-editor.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/workspace/user-group-workspace-editor.element.ts @@ -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}> - ${this.#renderLanguagePermissions()} - - - - - - - - + ${this.#renderLanguageAccess()} ${this.#renderDocumentAccess()} ${this.#renderMediaAccess()} @@ -226,7 +248,7 @@ export class UmbUserGroupWorkspaceEditorElement extends UmbLitElement { `; } - #renderLanguagePermissions() { + #renderLanguageAccess() { return html` +
+ +
+ + ${this._documentRootAccess === false + ? html` + + ` + : nothing} +
+ `; + } + + #renderMediaAccess() { + return html` + +
+ +
+ + ${this._mediaRootAccess === false + ? html` + + ` + : nothing} +
+ `; + } + #renderRightColumn() { return html`