diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 142fbb11c3..a3cc6a58ba 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -68,6 +68,7 @@ "./user-group": "./dist-cms/packages/user/user-group/index.js", "./current-user": "./dist-cms/packages/user/current-user/index.js", "./user": "./dist-cms/packages/user/user/index.js", + "./user-permission": "./dist-cms/packages/user/user-permission/index.js", "./code-editor": "./dist-cms/packages/templating/code-editor/index.js", "./external/*": "./dist-cms/external/*/index.js" }, diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/da-dk.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/da-dk.ts index acd5f33aae..ec140cb744 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/da-dk.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/da-dk.ts @@ -1829,7 +1829,9 @@ export default { searchAllChildren: "Søg alle 'børn'", languagesHelp: 'Tilføj sprog for at give brugerne adgang til at redigere', sectionsHelp: 'Tilføj sektioner for at give brugerne adgang', - selectUserGroups: 'Vælg brugergrupper', + selectUserGroup: (multiple: boolean) => { + return multiple ? 'Vælg brugergrupper' : 'Vælg brugergruppe'; + }, noStartNode: 'Ingen startnode valgt', noStartNodes: 'Ingen startnoder valgt', startnode: 'Indhold startnode', 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 37adbf86e2..d2f2882fe8 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 @@ -1830,7 +1830,9 @@ export default { languagesHelp: 'Limit the languages users have access to edit', allowAccessToAllLanguages: 'Allow access to all languages', sectionsHelp: 'Add sections to give users access', - selectUserGroups: 'Select user groups', + selectUserGroup: (multiple: boolean) => { + return multiple ? 'Select User Groups' : 'Select User Group'; + }, noStartNode: 'No start node selected', noStartNodes: 'No start nodes selected', startnode: 'Content start node', diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/document-type.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/document-type.data.ts index f510fa8468..ef912e8795 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/document-type.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/document-type.data.ts @@ -1107,11 +1107,6 @@ class UmbDocumentTypeData extends UmbEntityData { const items = this.treeData.filter((item) => allowedTypeKeys.includes(item.id ?? '')); return items.map((item) => item); } - - /** For internal use */ - getAll() { - return this.data; - } } export const umbDocumentTypeData = new UmbDocumentTypeData(); diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/document.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/document.data.ts index d7d2a183b3..5c4edfb0d2 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/document.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/document.data.ts @@ -1,4 +1,5 @@ import { umbDocumentTypeData } from './document-type.data.js'; +import { umbUserPermissionData } from './user-permission.data.js'; import { UmbEntityData } from './entity.data.js'; import { createDocumentTreeItem } from './utils.js'; import { @@ -10,6 +11,7 @@ import { PagedDocumentTypeResponseModel, PagedRecycleBinItemResponseModel, } from '@umbraco-cms/backoffice/backend-api'; +import { DOCUMENT_ENTITY_TYPE } from '@umbraco-cms/backoffice/document'; export const data: Array = [ { @@ -654,10 +656,7 @@ class UmbDocumentData extends UmbEntityData { } getAllowedDocumentTypesAtRoot(): PagedDocumentTypeResponseModel { - const items = umbDocumentTypeData.getAll(); //.filter((docType) => docType.allowedAsRoot); - - const total = items?.length; - return { items, total }; + return umbDocumentTypeData.getAll(); //.filter((docType) => docType.allowedAsRoot); } getRecycleBinRoot(): PagedRecycleBinItemResponseModel { @@ -673,6 +672,17 @@ class UmbDocumentData extends UmbEntityData { const total = items.length; return { items: treeItems, total }; } + + // permissions + + getUserPermissionsForDocument(id: string): Array { + return umbUserPermissionData + .getAll() + .items.filter( + (permission: any) => + permission.target.entityType === DOCUMENT_ENTITY_TYPE && permission.target.documentId === id, + ); + } } export const umbDocumentData = new UmbDocumentData(); diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/entity.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/entity.data.ts index d6c442fe89..cc63fa64dd 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/entity.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/entity.data.ts @@ -8,6 +8,13 @@ export class UmbEntityData extends UmbData { super(data); } + getAll() { + return { + total: this.data.length, + items: this.data, + }; + } + getList(skip: number, take: number) { return this.data.slice(skip, skip + take); } diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/user-group.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/user-group.data.ts index 4fea6c2999..1fd17caccf 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/user-group.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/user-group.data.ts @@ -4,11 +4,7 @@ import { UMB_USER_PERMISSION_DOCUMENT_DELETE, UMB_USER_PERMISSION_DOCUMENT_READ, } from '@umbraco-cms/backoffice/document'; -import { - PagedUserGroupResponseModel, - UserGroupItemResponseModel, - UserGroupResponseModel, -} from '@umbraco-cms/backoffice/backend-api'; +import { UserGroupItemResponseModel, UserGroupResponseModel } from '@umbraco-cms/backoffice/backend-api'; const createUserGroupItem = (item: UserGroupResponseModel): UserGroupItemResponseModel => { return { @@ -24,13 +20,6 @@ class UmbUserGroupData extends UmbEntityData { super(data); } - getAll(): PagedUserGroupResponseModel { - return { - total: this.data.length, - items: this.data, - }; - } - getItems(ids: Array): Array { const items = this.data.filter((item) => ids.includes(item.id ?? '')); return items.map((item) => createUserGroupItem(item)); diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/user-permission.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/user-permission.data.ts new file mode 100644 index 0000000000..84ce815969 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/user-permission.data.ts @@ -0,0 +1,50 @@ +import { UmbEntityData } from './entity.data.js'; +import { + DOCUMENT_ENTITY_TYPE, + UMB_USER_PERMISSION_DOCUMENT_CREATE, + UMB_USER_PERMISSION_DOCUMENT_READ, +} from '@umbraco-cms/backoffice/document'; + +export type UserPermissionModel = { + id: string; + target: unknown; + permissions: Array; +}; + +export const data: Array = [ + { + id: '408074bb-f776-485e-b85e-c2473e45663b', + target: { + entityType: DOCUMENT_ENTITY_TYPE, + documentId: 'simple-document-id', + userGroupId: '9d24dc47-a4bf-427f-8a4a-b900f03b8a12', + }, + permissions: [UMB_USER_PERMISSION_DOCUMENT_READ], + }, + { + id: 'b70b1453-a912-4157-ba62-20c2f0ab6a88', + target: { + entityType: DOCUMENT_ENTITY_TYPE, + documentId: 'simple-document-id', + userGroupId: 'f4626511-b0d7-4ab1-aebc-a87871a5dcfa', + }, + permissions: [UMB_USER_PERMISSION_DOCUMENT_READ, UMB_USER_PERMISSION_DOCUMENT_CREATE], + }, + { + id: 'b70b1453-a912-4157-ba62-20c2f0ab6a88', + target: { + entityType: DOCUMENT_ENTITY_TYPE, + documentId: 'c05da24d-7740-447b-9cdc-bd8ce2172e38', + userGroupId: '9d24dc47-a4bf-427f-8a4a-b900f03b8a12', + }, + permissions: [UMB_USER_PERMISSION_DOCUMENT_READ], + }, +]; + +class UmbUserPermissionData extends UmbEntityData { + constructor() { + super(data); + } +} + +export const umbUserPermissionData = new UmbUserPermissionData(); diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/user.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/user.data.ts index 18dbc0ce37..77d0c6b639 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/user.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/user.data.ts @@ -2,7 +2,6 @@ import { UmbEntityData } from './entity.data.js'; import { umbUserGroupData } from './user-group.data.js'; import { UmbLoggedInUser } from '@umbraco-cms/backoffice/auth'; import { - PagedUserResponseModel, UpdateUserGroupsOnUserRequestModel, UserItemResponseModel, UserResponseModel, @@ -22,13 +21,6 @@ class UmbUserData extends UmbEntityData { super(data); } - getAll(): PagedUserResponseModel { - return { - total: this.data.length, - items: this.data, - }; - } - getItems(ids: Array): Array { const items = this.data.filter((item) => ids.includes(item.id ?? '')); return items.map((item) => createUserItem(item)); diff --git a/src/Umbraco.Web.UI.Client/src/mocks/handlers/document/index.ts b/src/Umbraco.Web.UI.Client/src/mocks/handlers/document/index.ts index e847bf2188..f02652bc13 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/handlers/document/index.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/handlers/document/index.ts @@ -2,5 +2,12 @@ import { handlers as recycleBinHandlers } from './recycle-bin.handlers.js'; import { handlers as treeHandlers } from './tree.handlers.js'; import { handlers as documentHandlers } from './document.handlers.js'; import { handlers as itemHandlers } from './item.handlers.js'; +import { handlers as permissionHandlers } from './permission.handlers.js'; -export const handlers = [...recycleBinHandlers, ...treeHandlers, ...itemHandlers, ...documentHandlers]; +export const handlers = [ + ...recycleBinHandlers, + ...permissionHandlers, + ...treeHandlers, + ...itemHandlers, + ...documentHandlers, +]; diff --git a/src/Umbraco.Web.UI.Client/src/mocks/handlers/document/permission.handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/handlers/document/permission.handlers.ts new file mode 100644 index 0000000000..b130b533f4 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/mocks/handlers/document/permission.handlers.ts @@ -0,0 +1,14 @@ +const { rest } = window.MockServiceWorker; +import { umbDocumentData } from '../../data/document.data.js'; +import { slug } from './slug.js'; +import { umbracoPath } from '@umbraco-cms/backoffice/utils'; + +// TODO: temp handlers until we have a real API +export const handlers = [ + rest.get(umbracoPath(`${slug}/:id/permissions`), (req, res, ctx) => { + const id = req.params.id as string; + if (!id) return; + const response = umbDocumentData.getUserPermissionsForDocument(id); + return res(ctx.status(200), ctx.json(response)); + }), +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/event/deselected.event.ts b/src/Umbraco.Web.UI.Client/src/packages/core/event/deselected.event.ts new file mode 100644 index 0000000000..0f2594503a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/event/deselected.event.ts @@ -0,0 +1,10 @@ +export class UmbDeselectedEvent extends Event { + public static readonly TYPE = 'deselected'; + public unique: string; + + public constructor(unique: string) { + // mimics the native change event + super(UmbDeselectedEvent.TYPE, { bubbles: true, composed: false, cancelable: false }); + this.unique = unique; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/event/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/event/index.ts index 417d64836b..d3d128c997 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/event/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/event/index.ts @@ -1,5 +1,7 @@ export * from './input.event.js'; export * from './change.event.js'; export * from './delete.event.js'; +export * from './selection-change.event.js'; export * from './action-executed.event.js'; export * from './selected.event.js'; +export * from './deselected.event.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/event/selected.event.ts b/src/Umbraco.Web.UI.Client/src/packages/core/event/selected.event.ts index 9ecf5d7767..e3ae853d01 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/event/selected.event.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/event/selected.event.ts @@ -1,6 +1,10 @@ export class UmbSelectedEvent extends Event { - public constructor() { + public static readonly TYPE = 'selected'; + public unique: string; + + public constructor(unique: string) { // mimics the native change event - super('selected', { bubbles: true, composed: false, cancelable: false }); + super(UmbSelectedEvent.TYPE, { bubbles: true, composed: false, cancelable: false }); + this.unique = unique; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/event/selection-change.event.ts b/src/Umbraco.Web.UI.Client/src/packages/core/event/selection-change.event.ts new file mode 100644 index 0000000000..ac3351a749 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/event/selection-change.event.ts @@ -0,0 +1,8 @@ +export class UmbSelectionChangeEvent extends Event { + public static readonly TYPE = 'selection-change'; + + public constructor() { + // mimics the native change event + super(UmbSelectionChangeEvent.TYPE, { bubbles: true, composed: false, cancelable: false }); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/conditions/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/conditions/types.ts index e655d3211f..164b747814 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/conditions/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/conditions/types.ts @@ -1,8 +1,11 @@ +import type { UserPermissionConditionConfig } from '@umbraco-cms/backoffice/user-permission'; import type { SectionAliasConditionConfig } from './section-alias.condition.js'; import type { SwitchConditionConfig } from './switch.condition.js'; -import type { WorkspaceAliasConditionConfig, WorkspaceEntityTypeConditionConfig } from '@umbraco-cms/backoffice/workspace'; +import type { + WorkspaceAliasConditionConfig, + WorkspaceEntityTypeConditionConfig, +} from '@umbraco-cms/backoffice/workspace'; import type { UmbConditionConfigBase } from '@umbraco-cms/backoffice/extension-api'; -import type { UserPermissionConditionConfig } from '@umbraco-cms/backoffice/current-user'; import type { CollectionEntityTypeConditionConfig } from '@umbraco-cms/backoffice/collection'; /* TODO: in theory should't the core package import from other packages. @@ -10,7 +13,7 @@ Are there any other way we can do this? Niels: Sadly I don't see any other solutions currently. But are very open for ideas :-) now that I think about it maybe there is some ability to extend a global type, similar to the 'declare global' trick we use on Elements. */ export type ConditionTypes = - | CollectionEntityTypeConditionConfig + | CollectionEntityTypeConditionConfig | SectionAliasConditionConfig | WorkspaceAliasConditionConfig | WorkspaceEntityTypeConditionConfig diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/common/entity-user-permission-settings/entity-user-permission-settings-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/common/entity-user-permission-settings/entity-user-permission-settings-modal.element.ts deleted file mode 100644 index cacdb22527..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/common/entity-user-permission-settings/entity-user-permission-settings-modal.element.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { html, customElement, property, state, css } from '@umbraco-cms/backoffice/external/lit'; -import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import { - UmbEntityUserPermissionSettingsModalData, - UmbEntityUserPermissionSettingsModalResult, - UmbModalContext, -} from '@umbraco-cms/backoffice/modal'; -import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import { ManifestUserPermission, umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; -import { UUIBooleanInputEvent } from '@umbraco-cms/backoffice/external/uui'; - -@customElement('umb-entity-user-permission-settings-modal') -export class UmbEntityUserPermissionSettingsModalElement extends UmbLitElement { - @property({ attribute: false }) - modalContext?: UmbModalContext; - - @property({ type: Object }) - data?: UmbEntityUserPermissionSettingsModalData; - - @state() - private _userPermissionManifests: Array = []; - - private _handleConfirm() { - this.modalContext?.submit(); - } - - private _handleCancel() { - this.modalContext?.reject(); - } - - constructor() { - super(); - this.observe( - umbExtensionsRegistry.extensionsOfType('userPermission'), - (userPermissionManifests) => (this._userPermissionManifests = userPermissionManifests), - ); - } - - render() { - return html` - - - Render user permissions for ${this.data?.entityType} ${this.data?.unique} - ${this._userPermissionManifests.map((permission) => this.#renderPermission(permission))} - - Cancel - - - - `; - } - - #onChangeUserPermission(event: UUIBooleanInputEvent, userPermissionManifest: ManifestUserPermission) { - console.log(userPermissionManifest); - console.log(event.target.checked); - } - - #isAllowed(userPermissionManifest: ManifestUserPermission) { - return true; - //return this._userGroup?.permissions?.includes(userPermissionManifest.alias); - } - - #renderPermission(userPermissionManifest: ManifestUserPermission) { - return html`
- this.#onChangeUserPermission(event, userPermissionManifest)}> -
-
${userPermissionManifest.meta.label}
- ${userPermissionManifest.meta.description} -
-
-
`; - } - - static styles = [ - UmbTextStyles, - css` - .permission-toggle { - display: flex; - align-items: center; - border-bottom: 1px solid var(--uui-color-divider); - padding: var(--uui-size-space-3) 0 var(--uui-size-space-4) 0; - } - - .permission-meta { - margin-left: var(--uui-size-space-4); - line-height: 1.2em; - } - - .permission-name { - font-weight: bold; - } - `, - ]; -} - -export default UmbEntityUserPermissionSettingsModalElement; - -declare global { - interface HTMLElementTagNameMap { - 'umb-entity-user-permission-modal': UmbEntityUserPermissionSettingsModalElement; - } -} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/common/link-picker/link-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/common/link-picker/link-picker-modal.element.ts index 3189ddaaa4..abdf01270e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/common/link-picker/link-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/common/link-picker/link-picker-modal.element.ts @@ -155,7 +155,7 @@ export class UmbLinkPickerModalElement extends UmbModalBaseElement this._handleSelectionChange(event, 'document')} + @selection-change=${(event: CustomEvent) => this._handleSelectionChange(event, 'document')} .selection=${[this._selectedKey ?? '']} selectable> @@ -166,7 +166,7 @@ export class UmbLinkPickerModalElement extends UmbModalBaseElement this._handleSelectionChange(event, 'media')} + @selection-change=${(event: CustomEvent) => this._handleSelectionChange(event, 'media')} .selection=${[this._selectedKey ?? '']} selectable>`; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/common/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/common/manifests.ts index 9f8b55d4f9..6d7044fd52 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/common/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/common/manifests.ts @@ -61,12 +61,6 @@ const modals: Array = [ name: 'Tree Picker Modal', loader: () => import('./tree-picker/tree-picker-modal.element.js'), }, - { - type: 'modal', - alias: 'Umb.Modal.EntityUserPermissionSettings', - name: 'Entity User Permission Settings Modal', - loader: () => import('./entity-user-permission-settings/entity-user-permission-settings-modal.element.js'), - }, ]; export const manifests = [...modals]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/common/tree-picker/tree-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/common/tree-picker/tree-picker-modal.element.ts index 11840794dd..8184f5c3be 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/common/tree-picker/tree-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/common/tree-picker/tree-picker-modal.element.ts @@ -1,10 +1,10 @@ import { type UmbTreeElement } from '../../../tree/tree.element.js'; -import { UmbSelectedEvent } from '@umbraco-cms/backoffice/event'; import { html, customElement, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UmbTreePickerModalData, UmbPickerModalValue } from '@umbraco-cms/backoffice/modal'; import { UmbModalBaseElement } from '@umbraco-cms/internal/modal'; import { TreeItemPresentationModel } from '@umbraco-cms/backoffice/backend-api'; +import { UmbSelectionChangeEvent } from '@umbraco-cms/backoffice/event'; @customElement('umb-tree-picker-modal') export class UmbTreePickerModalElement extends UmbModalBaseElement< @@ -28,7 +28,7 @@ export class UmbTreePickerModalElement ; + entityType: string; + allowedPermissions: Array; + headline?: string; } -export type UmbEntityUserPermissionSettingsModalResult = undefined; +export type UmbEntityUserPermissionSettingsModalValue = { + allowedPermissions: Array; +}; export const UMB_ENTITY_USER_PERMISSION_MODAL = new UmbModalToken< UmbEntityUserPermissionSettingsModalData, - UmbEntityUserPermissionSettingsModalResult + UmbEntityUserPermissionSettingsModalValue >('Umb.Modal.EntityUserPermissionSettings', { type: 'sidebar', }); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/index.ts index 6871d578f7..16e5b7ebb4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/index.ts @@ -34,3 +34,4 @@ export * from './workspace-modal.token.js'; export * from './data-type-picker-flow-modal.token.js'; export * from './data-type-picker-flow-data-type-picker-modal.token.js'; export * from './entity-user-permission-settings-modal.token.js'; +export * from './permissions-modal.token.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/permissions-modal.token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/permissions-modal.token.ts new file mode 100644 index 0000000000..e3105cbb3f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/permissions-modal.token.ts @@ -0,0 +1,15 @@ +import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; + +export interface UmbPermissionsModalData { + unique: string; + entityType: string; +} + +export type UmbPermissionsModalValue = undefined; + +export const UMB_PERMISSIONS_MODAL = new UmbModalToken( + 'Umb.Modal.Permissions', + { + type: 'sidebar', + }, +); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/store/store-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/store/store-base.ts index d8e404867b..004499b99b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/store/store-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/store/store-base.ts @@ -63,4 +63,12 @@ export class UmbStoreBase implements UmbStore) { this._data.remove(uniques); } + + /** + * Returns an observable of the entire store + * @memberof UmbStoreBase + */ + all() { + return this._data.asObservable(); + } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree.context.ts index ef0aca5c7f..6e8d373f51 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree.context.ts @@ -6,7 +6,7 @@ import { UmbBaseController, UmbControllerHostElement } from '@umbraco-cms/backof import { createExtensionApi } from '@umbraco-cms/backoffice/extension-api'; import { ProblemDetails, TreeItemPresentationModel } from '@umbraco-cms/backoffice/backend-api'; import { UmbSelectionManagerBase } from '@umbraco-cms/backoffice/utils'; -import { UmbSelectedEvent } from '@umbraco-cms/backoffice/event'; +import { UmbSelectionChangeEvent } from '@umbraco-cms/backoffice/event'; // TODO: update interface export interface UmbTreeContext extends UmbBaseController { @@ -102,12 +102,12 @@ export class UmbTreeContextBase public select(unique: string | null) { if (!this.getSelectable()) return; this.#selectionManager.select(unique); - this._host.getHostElement().dispatchEvent(new UmbSelectedEvent()); + this._host.getHostElement().dispatchEvent(new UmbSelectionChangeEvent()); } public deselect(unique: string | null) { this.#selectionManager.deselect(unique); - this._host.getHostElement().dispatchEvent(new UmbSelectedEvent()); + this._host.getHostElement().dispatchEvent(new UmbSelectionChangeEvent()); } public async requestTreeRoot() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/entity-actions/import/import-dictionary-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/entity-actions/import/import-dictionary-modal.element.ts index 105e026be4..ca791dbb67 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/entity-actions/import/import-dictionary-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dictionary/dictionary/entity-actions/import/import-dictionary-modal.element.ts @@ -138,7 +138,7 @@ export class UmbImportDictionaryModalLayout extends UmbModalBaseElement< Choose where to import dictionary items (optional) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/manifests.ts index af6600593c..529db4ea71 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/manifests.ts @@ -4,10 +4,10 @@ import { UmbPublishDocumentEntityAction } from './publish.action.js'; import { UmbDocumentCultureAndHostnamesEntityAction } from './culture-and-hostnames.action.js'; import { UmbCreateDocumentBlueprintEntityAction } from './create-blueprint.action.js'; import { UmbDocumentPublicAccessEntityAction } from './public-access.action.js'; -import { UmbDocumentPermissionsEntityAction } from './permissions.action.js'; import { UmbUnpublishDocumentEntityAction } from './unpublish.action.js'; import { UmbRollbackDocumentEntityAction } from './rollback.action.js'; import { manifests as createManifests } from './create/manifests.js'; +import { manifests as permissionManifests } from './permissions/manifests.js'; import { UmbCopyEntityAction, UmbMoveEntityAction, @@ -17,6 +17,7 @@ import { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; const entityActions: Array = [ ...createManifests, + ...permissionManifests, { type: 'entityAction', alias: 'Umb.EntityAction.Document.CreateBlueprint', @@ -82,18 +83,6 @@ const entityActions: Array = [ entityTypes: [DOCUMENT_ENTITY_TYPE], }, }, - { - type: 'entityAction', - alias: 'Umb.EntityAction.Document.Permissions', - name: 'Document Permissions Entity Action', - api: UmbDocumentPermissionsEntityAction, - meta: { - icon: 'umb:vcard', - label: 'Permissions (TBD)', - repositoryAlias: DOCUMENT_REPOSITORY_ALIAS, - entityTypes: [DOCUMENT_ENTITY_TYPE], - }, - }, { type: 'entityAction', alias: 'Umb.EntityAction.Document.PublicAccess', diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/permissions/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/permissions/manifests.ts new file mode 100644 index 0000000000..47f639e0a3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/permissions/manifests.ts @@ -0,0 +1,30 @@ +import { DOCUMENT_REPOSITORY_ALIAS } from '../../repository/manifests.js'; +import { UmbDocumentPermissionsEntityAction } from './permissions.action.js'; +import { ManifestEntityAction, ManifestModal } from '@umbraco-cms/backoffice/extension-registry'; +import { DOCUMENT_ENTITY_TYPE } from '@umbraco-cms/backoffice/document'; + +const entityActions: Array = [ + { + type: 'entityAction', + alias: 'Umb.EntityAction.Document.Permissions', + name: 'Document Permissions Entity Action', + api: UmbDocumentPermissionsEntityAction, + meta: { + icon: 'umb:vcard', + label: 'Permissions (TBD)', + repositoryAlias: DOCUMENT_REPOSITORY_ALIAS, + entityTypes: [DOCUMENT_ENTITY_TYPE], + }, + }, +]; + +const modals: Array = [ + { + type: 'modal', + alias: 'Umb.Modal.Permissions', + name: 'Permissions Modal', + loader: () => import('./permissions-modal.element.js'), + }, +]; + +export const manifests = [...entityActions, ...modals]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/permissions/permissions-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/permissions/permissions-modal.element.ts new file mode 100644 index 0000000000..52723230e7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/permissions/permissions-modal.element.ts @@ -0,0 +1,170 @@ +import { UmbDocumentPermissionRepository } from '../../user-permissions/index.js'; +import { UmbDocumentRepository } from '../../repository/index.js'; +import { UmbUserGroupRepository } from '@umbraco-cms/backoffice/user-group'; +import { html, customElement, property, state, ifDefined } from '@umbraco-cms/backoffice/external/lit'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { + UMB_ENTITY_USER_PERMISSION_MODAL, + UMB_MODAL_MANAGER_CONTEXT_TOKEN, + UMB_USER_GROUP_PICKER_MODAL, + UmbEntityUserPermissionSettingsModalData, + UmbEntityUserPermissionSettingsModalValue, + UmbModalContext, + UmbModalManagerContext, +} from '@umbraco-cms/backoffice/modal'; +import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; +import { UmbSelectedEvent } from '@umbraco-cms/backoffice/event'; + +type UmbUserGroupRefData = { + id: string; + name?: string; + icon?: string | null; + permissions: Array; +}; + +@customElement('umb-permissions-modal') +export class UmbPermissionsModalElement extends UmbLitElement { + @property({ attribute: false }) + modalContext?: UmbModalContext; + + @property({ type: Object }) + data?: UmbEntityUserPermissionSettingsModalData; + + @state() + _entityItem?: any; + + @state() + _userGroupRefs: Array = []; + + #userPermissions: Array = []; + #userGroupRepository = new UmbUserGroupRepository(this); + #documentPermissionRepository = new UmbDocumentPermissionRepository(this); + #documentRepository = new UmbDocumentRepository(this); + #modalManagerContext?: UmbModalManagerContext; + #userGroupPickerModal?: UmbModalContext; + + private _handleConfirm() { + this.modalContext?.submit(); + } + + private _handleCancel() { + this.modalContext?.reject(); + } + + constructor() { + super(); + + this.consumeContext(UMB_MODAL_MANAGER_CONTEXT_TOKEN, (instance) => { + this.#modalManagerContext = instance; + }); + } + + protected async firstUpdated(): Promise { + if (!this.data?.unique) throw new Error('Could not load permissions, no unique was provided'); + this.#getEntityItem(this.data.unique); + this.#getEntityPermissions(this.data.unique); + } + + async #getEntityItem(unique: string) { + const { data } = await this.#documentRepository.requestItems([unique]); + if (!data) throw new Error('Could not load item'); + this._entityItem = data[0]; + } + + async #getEntityPermissions(unique: string) { + const { data } = await this.#documentPermissionRepository.requestPermissions(unique); + if (data) { + this.#userPermissions = data; + this.#mapToUserGroupRefs(); + } + } + + async #mapToUserGroupRefs() { + const userGroupIds = [...new Set(this.#userPermissions.map((permission) => permission.target.userGroupId))]; + const { data } = await this.#userGroupRepository.requestItems(userGroupIds); + + const userGroups = data ?? []; + + this._userGroupRefs = this.#userPermissions.map((entry) => { + const userGroup = userGroups.find((userGroup) => userGroup.id == entry.target.userGroupId); + return { + id: entry.target.userGroupId, + name: userGroup?.name, + icon: userGroup?.icon, + permissions: entry.permissions, + }; + }); + } + + #openUserGroupPickerModal() { + if (!this.#modalManagerContext) return; + + this.#userGroupPickerModal = this.#modalManagerContext.open(UMB_USER_GROUP_PICKER_MODAL); + + this.#userGroupPickerModal.addEventListener(UmbSelectedEvent.TYPE, (event) => + this.#openUserPermissionsModal((event as UmbSelectedEvent).unique), + ); + } + + #openUserPermissionsModal(id: string) { + if (!id) throw new Error('Could not open permissions modal, no id was provided'); + if (!this.data?.entityType) throw new Error('Could not open permissions modal, no entity type was provided'); + + const userGroupRef = this._userGroupRefs.find((userGroup) => userGroup.id == id); + + const modalContext = this.#modalManagerContext?.open(UMB_ENTITY_USER_PERMISSION_MODAL, { + unique: id, + entityType: this.data.entityType, + allowedPermissions: userGroupRef?.permissions || [], + headline: `Permissions for ${userGroupRef?.name}`, + }); + + modalContext?.onSubmit().then((value) => { + console.log(value); + }); + } + + render() { + return html` + + + Permissions set for User Groups for document: ${this.data?.entityType}: + + ${this._userGroupRefs.map( + (userGroup) => + html` this.#openUserPermissionsModal(userGroup.id)} + border> + + `, + )} + + Select user group + + + Cancel + + + `; + } + + static styles = [UmbTextStyles]; +} + +export default UmbPermissionsModalElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-permissions-modal': UmbPermissionsModalElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/permissions.action.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/permissions/permissions.action.ts similarity index 62% rename from src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/permissions.action.ts rename to src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/permissions/permissions.action.ts index b7f53612c2..f20cd187e4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/permissions.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/permissions/permissions.action.ts @@ -1,11 +1,11 @@ -import { type UmbDocumentRepository } from '../repository/document.repository.js'; +import { type UmbDocumentRepository } from '../../repository/document.repository.js'; import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action'; import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; import { UmbContextConsumerController } from '@umbraco-cms/backoffice/context-api'; import { UMB_MODAL_MANAGER_CONTEXT_TOKEN, UmbModalManagerContext, - UMB_ENTITY_USER_PERMISSION_MODAL, + UMB_PERMISSIONS_MODAL, } from '@umbraco-cms/backoffice/modal'; export class UmbDocumentPermissionsEntityAction extends UmbEntityActionBase { @@ -20,22 +20,12 @@ export class UmbDocumentPermissionsEntityAction extends UmbEntityActionBase = [ }, ]; -export const manifests = [...permissions, ...granularPermissions]; +export const manifests = [...repositoryManifests, ...permissions, ...granularPermissions]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/repository/document-permission.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/repository/document-permission.repository.ts new file mode 100644 index 0000000000..dd5c7bab41 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/repository/document-permission.repository.ts @@ -0,0 +1,19 @@ +import { t } from 'msw/lib/glossary-de6278a9.js'; +import { UmbDocumentPermissionServerDataSource } from './document-permission.server.data.js'; +import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; + +export class UmbDocumentPermissionRepository { + #host: UmbControllerHostElement; + + #permissionSource: UmbDocumentPermissionServerDataSource; + + constructor(host: UmbControllerHostElement) { + this.#host = host; + this.#permissionSource = new UmbDocumentPermissionServerDataSource(this.#host); + } + + async requestPermissions(id: string) { + if (!id) throw new Error(`id is required`); + return this.#permissionSource.requestPermissions(id); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/repository/document-permission.server.data.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/repository/document-permission.server.data.ts new file mode 100644 index 0000000000..2b093c3137 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/repository/document-permission.server.data.ts @@ -0,0 +1,32 @@ +import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; +import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; + +/** + * @export + * @class UmbUserGroupCollectionServerDataSource + * @implements {RepositoryDetailDataSource} + */ +export class UmbDocumentPermissionServerDataSource { + #host: UmbControllerHostElement; + + /** + * Creates an instance of UmbDocumentPermissionServerDataSource. + * @param {UmbControllerHostElement} host + * @memberof UmbDocumentPermissionServerDataSource + */ + constructor(host: UmbControllerHostElement) { + this.#host = host; + } + + requestPermissions(id: string) { + return tryExecuteAndNotify( + this.#host, + fetch(`/umbraco/management/api/v1/document/${id}/permissions`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }).then((res) => res.json()), + ); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/repository/index.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/repository/index.ts new file mode 100644 index 0000000000..79337c7f92 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/repository/index.ts @@ -0,0 +1 @@ +export * from './document-permission.repository.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/repository/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/repository/manifests.ts new file mode 100644 index 0000000000..d20227b8c3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/repository/manifests.ts @@ -0,0 +1,13 @@ +import { UmbDocumentPermissionRepository } from './document-permission.repository.js'; +import { ManifestRepository } from '@umbraco-cms/backoffice/extension-registry'; + +export const UMB_DOCUMENT_PERMISSION_REPOSITORY_ALIAS = 'Umb.Repository.Document.Permission'; + +const repository: ManifestRepository = { + type: 'repository', + alias: UMB_DOCUMENT_PERMISSION_REPOSITORY_ALIAS, + name: 'Document Permission Repository', + api: UmbDocumentPermissionRepository, +}; + +export const manifests = [repository]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/manifests.ts index 17fc8e51b5..0ece551478 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/manifests.ts @@ -4,6 +4,7 @@ import { manifests as contentMenuManifest } from './menu.manifests.js'; import { manifests as documentBlueprintManifests } from './document-blueprints/manifests.js'; import { manifests as documentTypeManifests } from './document-types/manifests.js'; import { manifests as documentManifests } from './documents/manifests.js'; +import { manifests as documentPermissionManifests } from './documents/user-permissions/index.js'; export const manifests = [ ...dashboardManifests, @@ -12,4 +13,5 @@ export const manifests = [ ...documentBlueprintManifests, ...documentTypeManifests, ...documentManifests, + ...documentPermissionManifests, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/modals/partial-view-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/modals/partial-view-picker-modal.element.ts index 0f9dbc6864..3488a98cff 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/modals/partial-view-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/modals/partial-view-picker-modal.element.ts @@ -43,7 +43,7 @@ export default class UmbPartialViewPickerModalElement extends UmbModalBaseElemen diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/index.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/index.ts index 4cbce7fc3e..0b68da794d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/index.ts @@ -1,3 +1,2 @@ // TODO:Do not export store, but instead export future repository export * from './current-user-history.store.js'; -export * from './conditions/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/manifests.ts index 8765bc4d95..f6fe418913 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/manifests.ts @@ -1,6 +1,5 @@ import { manifests as modalManifests } from './modals/manifests.js'; import { manifests as userProfileAppsManifests } from './user-profile-apps/manifests.js'; -import { manifest as userPermissionConditionManifest } from './conditions/user-permission.condition.js'; import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; export const headerApps: Array = [ @@ -24,9 +23,4 @@ export const headerApps: Array = [ }, ]; -export const manifests = [ - ...headerApps, - ...modalManifests, - ...userProfileAppsManifests, - userPermissionConditionManifest, -]; +export const manifests = [...headerApps, ...modalManifests, ...userProfileAppsManifests]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/user/manifests.ts index 02ea6974d6..318b72e7ce 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/manifests.ts @@ -2,9 +2,17 @@ import { manifests as userGroupManifests } from './user-group/manifests.js'; import { manifests as userManifests } from './user/manifests.js'; import { manifests as userSectionManifests } from './user-section/manifests.js'; import { manifests as currentUserManifests } from './current-user/manifests.js'; +import { manifests as userPermissionManifests } from './user-permission/manifests.js'; // We need to load any components that are not loaded by the user management bundle to register them in the browser. import './user-group/components/index.js'; +import './user-permission/components/index.js'; import './user/components/index.js'; -export const manifests = [...userGroupManifests, ...userManifests, ...userSectionManifests, ...currentUserManifests]; +export const manifests = [ + ...userGroupManifests, + ...userManifests, + ...userSectionManifests, + ...currentUserManifests, + ...userPermissionManifests, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/user-group-collection-header.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/components/user-group-collection-header.element.ts similarity index 76% rename from src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/user-group-collection-header.element.ts rename to src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/components/user-group-collection-header.element.ts index a53ae056da..bdae8b7434 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/user-group-collection-header.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/components/user-group-collection-header.element.ts @@ -1,4 +1,4 @@ -import { UmbUserGroupCollectionContext } from './user-group-collection.context.js'; +import { UmbUserGroupCollectionContext } from '../user-group-collection.context.js'; import { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui'; import { css, html, customElement } from '@umbraco-cms/backoffice/external/lit'; import { UMB_COLLECTION_CONTEXT } from '@umbraco-cms/backoffice/collection'; @@ -26,8 +26,15 @@ export class UmbUserGroupCollectionHeaderElement extends UmbLitElement { render() { return html` - - + + `; } static styles = [ diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/user-group-table-name-column-layout.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/components/user-group-table-name-column-layout.element.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/user-group-table-name-column-layout.element.ts rename to src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/components/user-group-table-name-column-layout.element.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/user-group-table-sections-column-layout.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/components/user-group-table-sections-column-layout.element.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/user-group-table-sections-column-layout.element.ts rename to src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/components/user-group-table-sections-column-layout.element.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/index.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/index.ts new file mode 100644 index 0000000000..3d76f338dd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/index.ts @@ -0,0 +1 @@ +export * from './repository/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/manifests.ts new file mode 100644 index 0000000000..4e1826b900 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/manifests.ts @@ -0,0 +1,3 @@ +import { manifests as repositoryManifests } from './repository/manifests.js'; + +export const manifests = [...repositoryManifests]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/repository/index.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/repository/index.ts new file mode 100644 index 0000000000..b502942922 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/repository/index.ts @@ -0,0 +1 @@ +export * from './user-group-collection.repository.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/repository/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/repository/manifests.ts new file mode 100644 index 0000000000..726f0f5e33 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/repository/manifests.ts @@ -0,0 +1,13 @@ +import { UmbUserGroupCollectionRepository } from './user-group-collection.repository.js'; +import { ManifestRepository } from '@umbraco-cms/backoffice/extension-registry'; + +export const USER_GROUP_COLLECTION_REPOSITORY_ALIAS = 'Umb.Repository.UserGroupCollection'; + +const repository: ManifestRepository = { + type: 'repository', + alias: USER_GROUP_COLLECTION_REPOSITORY_ALIAS, + name: 'User Group Collection Repository', + api: UmbUserGroupCollectionRepository, +}; + +export const manifests = [repository]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/repository/user-group-collection.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/repository/user-group-collection.repository.ts new file mode 100644 index 0000000000..c0cccf6e8c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/repository/user-group-collection.repository.ts @@ -0,0 +1,38 @@ +import { UmbUserGroupCollectionFilterModel } from '../../types.js'; +import { UMB_USER_GROUP_STORE_CONTEXT_TOKEN, UmbUserGroupStore } from '../../repository/user-group.store.js'; +import { UmbUserGroupCollectionServerDataSource } from './user-group-collection.server.data.js'; +import { UserGroupResponseModel } from '@umbraco-cms/backoffice/backend-api'; +import { UmbCollectionDataSource, UmbCollectionRepository } from '@umbraco-cms/backoffice/repository'; +import { UmbContextConsumerController } from '@umbraco-cms/backoffice/context-api'; +import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; + +export class UmbUserGroupCollectionRepository implements UmbCollectionRepository { + #host: UmbControllerHostElement; + #init; + + #detailStore?: UmbUserGroupStore; + #collectionSource: UmbCollectionDataSource; + + constructor(host: UmbControllerHostElement) { + this.#host = host; + this.#collectionSource = new UmbUserGroupCollectionServerDataSource(this.#host); + + this.#init = Promise.all([ + new UmbContextConsumerController(this.#host, UMB_USER_GROUP_STORE_CONTEXT_TOKEN, (instance) => { + this.#detailStore = instance; + }).asPromise(), + ]); + } + + async requestCollection(filter: UmbUserGroupCollectionFilterModel = { skip: 0, take: 100 }) { + await this.#init; + + const { data, error } = await this.#collectionSource.filterCollection(filter); + + if (data) { + this.#detailStore?.appendItems(data.items); + } + + return { data, error, asObservable: () => this.#detailStore!.all() }; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/repository/sources/user-group-collection.server.data.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/repository/user-group-collection.server.data.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/user/user-group/repository/sources/user-group-collection.server.data.ts rename to src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/repository/user-group-collection.server.data.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/user-group-collection.context.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/user-group-collection.context.ts index 5e34f817b6..da336368a7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/user-group-collection.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/user-group-collection.context.ts @@ -1,5 +1,5 @@ -import { USER_GROUP_REPOSITORY_ALIAS } from '../repository/manifests.js'; import type { UmbUserGroupCollectionFilterModel } from '../types.js'; +import { USER_GROUP_COLLECTION_REPOSITORY_ALIAS } from './repository/manifests.js'; import { UmbCollectionContext } from '@umbraco-cms/backoffice/collection'; import type { UserGroupResponseModel } from '@umbraco-cms/backoffice/backend-api'; import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; @@ -9,6 +9,6 @@ export class UmbUserGroupCollectionContext extends UmbCollectionContext< UmbUserGroupCollectionFilterModel > { constructor(host: UmbControllerHostElement) { - super(host, 'user-group', USER_GROUP_REPOSITORY_ALIAS); + super(host, 'user-group', USER_GROUP_COLLECTION_REPOSITORY_ALIAS); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/user-group-collection.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/user-group-collection.element.ts index 357a8e559b..f6ee2b8259 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/user-group-collection.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/user-group-collection.element.ts @@ -1,11 +1,11 @@ import { UmbUserGroupCollectionContext } from './user-group-collection.context.js'; import { css, html, customElement } from '@umbraco-cms/backoffice/external/lit'; -import { UmbTextStyles } from "@umbraco-cms/backoffice/style"; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UMB_COLLECTION_CONTEXT } from '@umbraco-cms/backoffice/collection'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import './user-group-collection-view.element.js'; -import './user-group-collection-header.element.js'; +import './views/user-group-collection-view.element.js'; +import './components/user-group-collection-header.element.js'; @customElement('umb-user-group-collection') export class UmbUserCollectionElement extends UmbLitElement { @@ -20,7 +20,7 @@ export class UmbUserCollectionElement extends UmbLitElement { return html` - + `; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/user-group-collection-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/views/user-group-collection-view.element.ts similarity index 85% rename from src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/user-group-collection-view.element.ts rename to src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/views/user-group-collection-view.element.ts index db6a62353f..2a9719d42f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/user-group-collection-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/views/user-group-collection-view.element.ts @@ -1,12 +1,12 @@ -import { UmbUserGroupCollectionContext } from './user-group-collection.context.js'; -import { UmbTextStyles } from "@umbraco-cms/backoffice/style"; +import { UmbUserGroupCollectionContext } from '../user-group-collection.context.js'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import { UMB_COLLECTION_CONTEXT } from '@umbraco-cms/backoffice/collection'; import { UserGroupResponseModel } from '@umbraco-cms/backoffice/backend-api'; -import './user-group-table-name-column-layout.element.js'; -import './user-group-table-sections-column-layout.element.js'; +import '../components/user-group-table-name-column-layout.element.js'; +import '../components/user-group-table-sections-column-layout.element.js'; import { UmbTableColumn, UmbTableConfig, @@ -16,8 +16,8 @@ import { UmbTableSelectedEvent, } from '@umbraco-cms/backoffice/components'; -@customElement('umb-user-group-collection-view') -export class UmbUserGroupCollectionViewElement extends UmbLitElement { +@customElement('umb-user-group-collection-table-view') +export class UmbUserGroupCollectionTableViewElement extends UmbLitElement { @state() private _tableConfig: UmbTableConfig = { allowSelection: true, @@ -134,10 +134,10 @@ export class UmbUserGroupCollectionViewElement extends UmbLitElement { ]; } -export default UmbUserGroupCollectionViewElement; +export default UmbUserGroupCollectionTableViewElement; declare global { interface HTMLElementTagNameMap { - 'umb-user-group-collection-view': UmbUserGroupCollectionViewElement; + 'umb-user-group-collection-table-view': UmbUserGroupCollectionTableViewElement; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/components/index.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/components/index.ts index 11d389d6f6..94dd470899 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/components/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/components/index.ts @@ -1,3 +1,5 @@ import './input-user-group/user-group-input.element.js'; +import './user-group-ref/user-group-ref.element.js'; export * from './input-user-group/user-group-input.element.js'; +export * from './user-group-ref/user-group-ref.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/components/input-user-group/user-group-input.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/components/input-user-group/user-group-input.element.ts index c2cbe1bcbd..910d439cb9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/components/input-user-group/user-group-input.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/components/input-user-group/user-group-input.element.ts @@ -1,5 +1,5 @@ import { UmbUserGroupPickerContext } from './user-group-input.context.js'; -import { css, html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; +import { css, html, customElement, property, state, ifDefined, nothing } from '@umbraco-cms/backoffice/external/lit'; import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import type { UserGroupItemResponseModel } from '@umbraco-cms/backoffice/backend-api'; @@ -76,13 +76,13 @@ export class UmbUserGroupInputElement 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,13 +105,15 @@ export class UmbUserGroupInputElement extends FormControlMixin(UmbLitElement) { private _renderItem(item: UserGroupItemResponseModel) { if (!item.id) return; return html` - + + ${item.icon ? html`` : nothing} + this.#pickerContext.requestRemoveItem(item.id!)} label="Remove ${item.name}" >Remove - + `; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/components/user-group-ref/user-group-ref.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/components/user-group-ref/user-group-ref.element.ts new file mode 100644 index 0000000000..9001c994ef --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/components/user-group-ref/user-group-ref.element.ts @@ -0,0 +1,67 @@ +import { UUIRefNodeElement } from '@umbraco-cms/backoffice/external/uui'; +import { customElement, html, property } from '@umbraco-cms/backoffice/external/lit'; +import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api'; +import { ManifestUserPermission, umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; +import { map } from '@umbraco-cms/backoffice/external/rxjs'; + +/** + * @element umb-user-group-ref + * @description - Component for displaying a reference to a User Group + * @extends UUIRefNodeElement + */ +@customElement('umb-user-group-ref') +export class UmbUserGroupRefElement extends UmbElementMixin(UUIRefNodeElement) { + @property({ type: Array, attribute: 'user-permission-aliases' }) + public get userPermissionAliases(): Array { + return []; + } + public set userPermissionAliases(value: Array) { + this.#observeUserPermissions(value); + } + + #userPermissionLabels: Array = []; + + async #observeUserPermissions(value: Array) { + if (value) { + this.observe( + umbExtensionsRegistry.extensionsOfType('userPermission').pipe( + map((manifests) => { + return manifests.filter((manifest) => manifest.alias && value.includes(manifest.alias)); + }), + ), + (userPermissionManifests) => this.#setUserPermissionLabels(userPermissionManifests), + 'userPermissionLabels', + ); + } else { + this.removeControllerByAlias('userPermissionLabels'); + } + } + + #setUserPermissionLabels(manifests: Array) { + this.#userPermissionLabels = manifests.map((manifest) => + manifest.meta.labelKey ? this.localize.term(manifest.meta.labelKey) : manifest.meta.label ?? '', + ); + } + + protected renderDetail() { + const details: string[] = []; + + if (this.#userPermissionLabels.length > 0) { + details.push(this.#userPermissionLabels.join(', ')); + } + + if (this.detail !== '') { + details.push(this.detail); + } + + return html`${details.join(' | ')}`; + } + + static styles = [...UUIRefNodeElement.styles]; +} + +declare global { + interface HTMLElementTagNameMap { + 'umb-user-group-ref': UmbUserGroupRefElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/index.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/index.ts index 81c17a6f51..b52ae132ad 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/index.ts @@ -1,4 +1,6 @@ -export * from './types.js'; +export * from './collection/index.js'; export * from './components/index.js'; +export * from './repository/index.js'; +export * from './types.js'; export const UMB_USER_GROUP_ENTITY_TYPE = 'user-group'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/manifests.ts index 5d01d53186..0fd9d7fe94 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/manifests.ts @@ -1,3 +1,4 @@ +import { manifests as collectionManifests } from './collection/manifests.js'; import { manifests as repositoryManifests } from './repository/manifests.js'; import { manifests as workspaceManifests } from './workspace/manifests.js'; import { manifests as modalManifests } from './modals/manifests.js'; @@ -6,6 +7,7 @@ import { manifests as entityActionManifests } from './entity-actions/manifests.j import { manifests as entityBulkActionManifests } from './entity-bulk-actions/manifests.js'; export const manifests = [ + ...collectionManifests, ...repositoryManifests, ...workspaceManifests, ...modalManifests, diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/modals/user-group-picker/user-group-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/modals/user-group-picker/user-group-picker-modal.element.ts index 6fabf3ba6c..9d8255a668 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/modals/user-group-picker/user-group-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/modals/user-group-picker/user-group-picker-modal.element.ts @@ -1,12 +1,11 @@ -import type { UmbUserGroupRepository } from '../../repository/user-group.repository.js'; -import { UmbTextStyles } from "@umbraco-cms/backoffice/style"; -import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit'; +import { UmbUserGroupCollectionRepository } from '../../collection/repository/index.js'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { css, html, customElement, state, ifDefined } from '@umbraco-cms/backoffice/external/lit'; import { UmbSelectionManagerBase } from '@umbraco-cms/backoffice/utils'; import { UmbModalBaseElement } from '@umbraco-cms/internal/modal'; -import { UmbObserverController } from '@umbraco-cms/backoffice/observable-api'; -import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; import { UserGroupResponseModel } from '@umbraco-cms/backoffice/backend-api'; -import { createExtensionApi } from '@umbraco-cms/backoffice/extension-api'; +import { UUIMenuItemEvent } from '@umbraco-cms/backoffice/external/uui'; +import { UmbSelectedEvent, UmbDeselectedEvent } from '@umbraco-cms/backoffice/event'; @customElement('umb-user-group-picker-modal') export class UmbUserGroupPickerModalElement extends UmbModalBaseElement { @@ -14,7 +13,7 @@ export class UmbUserGroupPickerModalElement extends UmbModalBaseElement = []; #selectionManager = new UmbSelectionManagerBase(); - #userGroupRepository?: UmbUserGroupRepository; + #userGroupCollectionRepository = new UmbUserGroupCollectionRepository(this); connectedCallback(): void { super.connectedCallback(); @@ -22,39 +21,42 @@ export class UmbUserGroupPickerModalElement extends UmbModalBaseElement { - if (!repositoryManifest) return; - - try { - const result = await createExtensionApi(repositoryManifest, [this]); - this.#userGroupRepository = result as UmbUserGroupRepository; - this.#observeUserGroups(); - } catch (error) { - throw new Error('Could not create repository with alias: Umb.Repository.User'); - } - } - ); + protected firstUpdated(): void { + this.#observeUserGroups(); } async #observeUserGroups() { - if (!this.#userGroupRepository) return; - // TODO is this the correct end point? - const { data } = await this.#userGroupRepository.requestCollection(); + const { error, asObservable } = await this.#userGroupCollectionRepository.requestCollection(); + if (error) return; + this.observe(asObservable(), (items) => (this._userGroups = items)); + } - if (data) { - this._userGroups = data.items; - } + #onSelected(event: UUIMenuItemEvent, item: UserGroupResponseModel) { + if (!item.id) throw new Error('User group id is required'); + event.stopPropagation(); + this.#selectionManager.select(item.id); + this.#updateSelectionValue(); + this.requestUpdate(); + this.modalContext?.dispatchEvent(new UmbSelectedEvent(item.id)); + } + + #onDeselected(event: UUIMenuItemEvent, item: UserGroupResponseModel) { + if (!item.id) throw new Error('User group id is required'); + event.stopPropagation(); + this.#selectionManager.deselect(item.id); + this.#updateSelectionValue(); + this.requestUpdate(); + this.modalContext?.dispatchEvent(new UmbDeselectedEvent(item.id)); + } + + #updateSelectionValue() { + this.modalContext?.updateValue({ selection: this.#selectionManager.getSelection() }); } #submit() { - this.modalContext?.submit({ - selection: this.#selectionManager.getSelection(), - }); + this.modalContext?.submit(this._value); } #close() { @@ -63,19 +65,19 @@ export class UmbUserGroupPickerModalElement extends UmbModalBaseElement + ${this._userGroups.map( (item) => html` this.#selectionManager.select(item.id!)} - @deselected=${() => this.#selectionManager.deselect(item.id!)} + @selected=${(event: UUIMenuItemEvent) => this.#onSelected(event, item)} + @deselected=${(event: UUIMenuItemEvent) => this.#onDeselected(event, item)} ?selected=${this.#selectionManager.isSelected(item.id!)}> - + - ` + `, )}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/repository/index.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/repository/index.ts new file mode 100644 index 0000000000..019a8ceb26 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/repository/index.ts @@ -0,0 +1 @@ +export * from './user-group.repository.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/repository/user-group.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/repository/user-group.repository.ts index 6f4e013de3..8910e5ffed 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/repository/user-group.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/repository/user-group.repository.ts @@ -1,6 +1,5 @@ -import { UmbUserGroupCollectionFilterModel, UmbUserGroupDetailDataSource } from '../types.js'; +import { UmbUserGroupDetailDataSource } from '../types.js'; import { UmbUserGroupServerDataSource } from './sources/user-group.server.data.js'; -import { UmbUserGroupCollectionServerDataSource } from './sources/user-group-collection.server.data.js'; import { UMB_USER_GROUP_ITEM_STORE_CONTEXT_TOKEN, UmbUserGroupItemStore } from './user-group-item.store.js'; import { UMB_USER_GROUP_STORE_CONTEXT_TOKEN, UmbUserGroupStore } from './user-group.store.js'; import { UmbUserGroupItemServerDataSource } from './sources/user-group-item.server.data.js'; @@ -13,8 +12,6 @@ import { UserGroupResponseModel, } from '@umbraco-cms/backoffice/backend-api'; import { - UmbCollectionDataSource, - UmbCollectionRepository, UmbDetailRepository, UmbItemDataSource, UmbItemRepository, @@ -29,7 +26,6 @@ import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controlle export class UmbUserGroupRepository implements UmbDetailRepository, - UmbCollectionRepository, UmbItemRepository { #host: UmbControllerHostElement; @@ -38,8 +34,6 @@ export class UmbUserGroupRepository #detailSource: UmbUserGroupDetailDataSource; #detailStore?: UmbUserGroupStore; - #collectionSource: UmbCollectionDataSource; - #itemSource: UmbItemDataSource; #itemStore?: UmbUserGroupItemStore; @@ -49,7 +43,6 @@ export class UmbUserGroupRepository this.#host = host; this.#detailSource = new UmbUserGroupServerDataSource(this.#host); this.#itemSource = new UmbUserGroupItemServerDataSource(this.#host); - this.#collectionSource = new UmbUserGroupCollectionServerDataSource(this.#host); new UmbContextConsumerController(this.#host, UMB_NOTIFICATION_CONTEXT_TOKEN, (instance) => { this.#notificationContext = instance; @@ -69,16 +62,11 @@ export class UmbUserGroupRepository }).asPromise(), ]); } + createScaffold(parentId: string | null): Promise> { return this.#detailSource.createScaffold(parentId); } - // COLLECTION - async requestCollection(filter: UmbUserGroupCollectionFilterModel = { skip: 0, take: 100 }) { - //TODO: missing observable - return this.#collectionSource.filterCollection(filter); - } - // ITEMS: async requestItems(ids: Array) { if (!ids) throw new Error('Ids are missing'); diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/repository/user-group.store.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/repository/user-group.store.ts index 8fe2457412..8ca9044e7c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/repository/user-group.store.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/repository/user-group.store.ts @@ -2,10 +2,7 @@ import type { UserGroupDetails } from '../types.js'; import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api'; -import { UmbEntityDetailStore, UmbStoreBase } from '@umbraco-cms/backoffice/store'; - -// TODO: get rid of this type addition & { ... }: -//export type UmbUserGroupStoreItemType = UserGroupDetails & { users?: Array }; +import { UmbStoreBase } from '@umbraco-cms/backoffice/store'; export const UMB_USER_GROUP_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbUserGroupStore'); @@ -15,74 +12,8 @@ export const UMB_USER_GROUP_STORE_CONTEXT_TOKEN = new UmbContextToken { - #groups = new UmbArrayState([], (x) => x.id); - public groups = this.#groups.asObservable(); - +export class UmbUserGroupStore extends UmbStoreBase { constructor(host: UmbControllerHostElement) { super(host, UMB_USER_GROUP_STORE_CONTEXT_TOKEN.toString(), new UmbArrayState([], (x) => x.id)); } - - getScaffold(entityType: string, parentId: string | null) { - return { - id: '', - name: '', - icon: '', - type: 'user-group', - hasChildren: false, - parentId: '', - sections: [], - permissions: [], - users: [], - } as UserGroupDetails; - } - - getAll() { - // TODO: use Fetcher API. - // TODO: only fetch if the data type is not in the store? - fetch(`/umbraco/backoffice/user-groups/list/items`) - .then((res) => res.json()) - .then((data) => { - this.#groups.append(data.items); - }); - - return this.groups; - } - - getByKey(id: string) { - // TODO: use Fetcher API. - // TODO: only fetch if the data type is not in the store? - fetch(`/umbraco/backoffice/user-groups/details/${id}`) - .then((res) => res.json()) - .then((data) => { - this.#groups.append([data]); - }); - - return this.#groups.asObservablePart((userGroups) => userGroups.find((userGroup) => userGroup.id === id)); - } - - async save(userGroups: Array) { - // TODO: use Fetcher API. - - // TODO: implement so user group store updates the users, but these needs to save as well..? - /* - if (this._userStore && userGroup.users) { - await this._userStore.updateUserGroup(userGroup.users, userGroup.id); - } - */ - - try { - const res = await fetch('/umbraco/backoffice/user-groups/save', { - method: 'POST', - body: JSON.stringify(userGroups), - headers: { - 'Content-Type': 'application/json', - }, - }); - const json = await res.json(); - this.#groups.append(json); - } catch (error) { - console.error('Save Data Type error', error); - } - } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/workspace/components/user-group-default-permission-list.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/workspace/components/user-group-default-permission-list.element.ts index 93adba485c..58c2876c10 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/workspace/components/user-group-default-permission-list.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/workspace/components/user-group-default-permission-list.element.ts @@ -1,20 +1,14 @@ import { UMB_USER_GROUP_WORKSPACE_CONTEXT } from '../user-group-workspace.context.js'; -import { type UmbUserPermissionSettingElement } from '@umbraco-cms/backoffice/user'; -import { html, customElement, state, ifDefined, nothing } from '@umbraco-cms/backoffice/external/lit'; +import { html, customElement, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import { UserGroupResponseModel } from '@umbraco-cms/backoffice/backend-api'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import { ManifestUserPermission, umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; -import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; -import { groupBy } from '@umbraco-cms/backoffice/external/lodash'; +import { UmbSelectionChangeEvent } from '@umbraco-cms/backoffice/event'; +import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; @customElement('umb-user-group-default-permission-list') export class UmbUserGroupDefaultPermissionListElement extends UmbLitElement { @state() - private _userGroup?: UserGroupResponseModel; - - @state() - private _manifests: Array = []; + private _userGroupDefaultPermissions?: Array; @state() private _entityTypes: Array = []; @@ -28,27 +22,23 @@ export class UmbUserGroupDefaultPermissionListElement extends UmbLitElement { this.consumeContext(UMB_USER_GROUP_WORKSPACE_CONTEXT, (instance) => { this.#userGroupWorkspaceContext = instance; - this.observe(this.#userGroupWorkspaceContext.data, (userGroup) => (this._userGroup = userGroup)); + this.observe( + this.#userGroupWorkspaceContext.data, + (userGroup) => (this._userGroupDefaultPermissions = userGroup?.permissions), + ); }); } #observeUserPermissions() { this.observe(umbExtensionsRegistry.extensionsOfType('userPermission'), (userPermissionManifests) => { - this._manifests = userPermissionManifests; this._entityTypes = [...new Set(userPermissionManifests.map((manifest) => manifest.meta.entityType))]; }); } - #onChangeUserPermission(event: UmbChangeEvent, userPermissionManifest: ManifestUserPermission) { - const target = event.target as UmbUserPermissionSettingElement; - - target.allowed - ? this.#userGroupWorkspaceContext?.addPermission(userPermissionManifest.alias) - : this.#userGroupWorkspaceContext?.removePermission(userPermissionManifest.alias); - } - - #isAllowed(permissionAlias: string) { - return this._userGroup?.permissions?.includes(permissionAlias); + #onSelectedUserPermission(event: UmbSelectionChangeEvent) { + const target = event.target as any; + const selection = target.selectedPermissions; + this.#userGroupWorkspaceContext?.setDefaultPermissions(selection); } render() { @@ -56,38 +46,15 @@ export class UmbUserGroupDefaultPermissionListElement extends UmbLitElement { } #renderPermissionsByEntityType(entityType: string) { - const permissionsForEntityType = this._manifests.filter((manifest) => manifest.meta.entityType === entityType); return html`

${entityType}

- ${this.#renderGroupedPermissions(permissionsForEntityType)} + `; } - #renderGroupedPermissions(permissions: Array) { - const groupedPermissions = groupBy(permissions, (manifest) => manifest.meta.group); - return html` - ${Object.entries(groupedPermissions).map( - ([group, manifests]) => html` - ${group !== 'undefined' - ? html`
${group}
` - : nothing} - ${manifests.map((manifest) => html` ${this.#renderPermission(manifest)} `)} - `, - )} - `; - } - - #renderPermission(manifest: ManifestUserPermission) { - return html` - this.#onChangeUserPermission(event, manifest)}>`; - } - static styles = [UmbTextStyles]; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/workspace/user-group-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/workspace/user-group-workspace.context.ts index 196e529a54..dfe5b4da11 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/workspace/user-group-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/workspace/user-group-workspace.context.ts @@ -107,22 +107,39 @@ export class UmbUserGroupWorkspaceContext } /** - * Adds a permission to the user group permission array. + * Sets the user group default permissions. + * @param {Array} permissionAliases + * @memberof UmbUserGroupWorkspaceContext + */ + setDefaultPermissions(permissionAliases: Array) { + this.#data.update({ permissions: permissionAliases }); + } + + /** + * Gets the user group default permissions. + * @memberof UmbUserGroupWorkspaceContext + */ + getDefaultPermissions() { + return this.#data.getValue()?.permissions ?? []; + } + + /** + * Allows a default permission on the user group. * @param {string} permissionAlias * @memberof UmbUserGroupWorkspaceContext */ - addPermission(permissionAlias: string) { + allowDefaultPermission(permissionAlias: string) { const permissions = this.#data.getValue()?.permissions ?? []; const newValue = [...permissions, permissionAlias]; this.#data.update({ permissions: newValue }); } /** - * Removes a permission from the user group permission array. + * Disallows a default permission on the user group. * @param {string} permissionAlias * @memberof UmbUserGroupWorkspaceContext */ - removePermission(permissionAlias: string) { + disallowDefaultPermission(permissionAlias: string) { const permissions = this.#data.getValue()?.permissions ?? []; const newValue = permissions.filter((alias) => alias !== permissionAlias); this.#data.update({ permissions: newValue }); diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-permission/components/entity-user-permission-settings-list/entity-user-permission-settings-list.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-permission/components/entity-user-permission-settings-list/entity-user-permission-settings-list.element.ts new file mode 100644 index 0000000000..2203b1f362 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-permission/components/entity-user-permission-settings-list/entity-user-permission-settings-list.element.ts @@ -0,0 +1,108 @@ +import { UmbChangeEvent, UmbSelectionChangeEvent } from '@umbraco-cms/backoffice/event'; +import { + ManifestEntityAction, + ManifestUserPermission, + umbExtensionsRegistry, +} from '@umbraco-cms/backoffice/extension-registry'; +import { css, html, customElement, property, state, nothing, ifDefined } from '@umbraco-cms/backoffice/external/lit'; +import { groupBy } from '@umbraco-cms/backoffice/external/lodash'; +import { UmbObserverController } from '@umbraco-cms/backoffice/observable-api'; +import { UmbUserPermissionSettingElement } from '@umbraco-cms/backoffice/user'; +import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; + +@customElement('umb-entity-user-permission-settings-list') +export class UmbEntityUserPermissionSettingsListElement extends UmbLitElement { + @property({ type: String, attribute: 'entity-type' }) + public get entityType(): string { + return this._entityType; + } + public set entityType(value: string) { + if (value === this._entityType) return; + this._entityType = value; + this.#observeUserPermissions(); + } + private _entityType: string = ''; + + @property({ attribute: false }) + selectedPermissions: Array = []; + + @state() + private _manifests: Array = []; + + #manifestObserver?: UmbObserverController>; + + #isAllowed(permissionAlias: string) { + return this.selectedPermissions?.includes(permissionAlias); + } + + #observeUserPermissions() { + this.#manifestObserver?.destroy(); + + this.#manifestObserver = this.observe( + umbExtensionsRegistry.extensionsOfType('userPermission'), + (userPermissionManifests) => { + this._manifests = userPermissionManifests.filter((manifest) => manifest.meta.entityType === this.entityType); + }, + ); + } + + #onChangeUserPermission(event: UmbChangeEvent, permissionAlias: string) { + event.stopPropagation(); + const target = event.target as UmbUserPermissionSettingElement; + target.allowed ? this.#addUserPermission(permissionAlias) : this.#removeUserPermission(permissionAlias); + } + + #addUserPermission(permissionAlias: string) { + this.selectedPermissions = [...this.selectedPermissions, permissionAlias]; + this.dispatchEvent(new UmbSelectionChangeEvent()); + } + + #removeUserPermission(permissionAlias: string) { + this.selectedPermissions = this.selectedPermissions.filter((alias) => alias !== permissionAlias); + this.dispatchEvent(new UmbSelectionChangeEvent()); + } + + render() { + return html`${this.#renderGroupedPermissions(this._manifests)} `; + } + + #renderGroupedPermissions(permissionManifests: Array) { + const groupedPermissions = groupBy(permissionManifests, (manifest) => manifest.meta.group); + return html` + ${Object.entries(groupedPermissions).map( + ([group, manifests]) => html` + ${group !== 'undefined' + ? html`
${group}
` + : nothing} + ${manifests.map((manifest) => html` ${this.#renderPermission(manifest)} `)} + `, + )} + `; + } + + #renderPermission(manifest: ManifestUserPermission) { + return html` + this.#onChangeUserPermission(event, manifest.alias)}>`; + } + + disconnectedCallback() { + super.disconnectedCallback(); + this.#manifestObserver?.destroy(); + } + + static styles = [css``]; +} + +export default UmbEntityUserPermissionSettingsListElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-entity-user-permission-settings-list': UmbEntityUserPermissionSettingsListElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-permission/components/index.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-permission/components/index.ts new file mode 100644 index 0000000000..782bc56df3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-permission/components/index.ts @@ -0,0 +1,3 @@ +import './entity-user-permission-settings-list/entity-user-permission-settings-list.element.js'; + +export * from './entity-user-permission-settings-list/entity-user-permission-settings-list.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/conditions/index.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-permission/conditions/index.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/user/current-user/conditions/index.ts rename to src/Umbraco.Web.UI.Client/src/packages/user/user-permission/conditions/index.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/conditions/user-permission.condition.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-permission/conditions/user-permission.condition.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/user/current-user/conditions/user-permission.condition.ts rename to src/Umbraco.Web.UI.Client/src/packages/user/user-permission/conditions/user-permission.condition.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-permission/index.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-permission/index.ts new file mode 100644 index 0000000000..8eed47ad34 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-permission/index.ts @@ -0,0 +1,15 @@ +export * from './components/index.js'; +export * from './conditions/index.js'; + +export type UserPermissionModel = { + id: string; + target: PermissionTargetType; + permissions: Array; +}; + +// TODO: this should only be known by the document +export type UmbDocumentGranularPermission = { + entityType: 'document'; + documentId: string; + userGroupId: string; +}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-permission/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-permission/manifests.ts new file mode 100644 index 0000000000..3cdcdd9998 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-permission/manifests.ts @@ -0,0 +1,4 @@ +import { manifest as userPermissionConditionManifest } from './conditions/user-permission.condition.js'; +import { manifests as userPermissionModalManifests } from './modals/manifests.js'; + +export const manifests = [userPermissionConditionManifest, ...userPermissionModalManifests]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-permission/modals/entity-user-permission-settings/entity-user-permission-settings-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-permission/modals/entity-user-permission-settings/entity-user-permission-settings-modal.element.ts new file mode 100644 index 0000000000..c61c8c3f55 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-permission/modals/entity-user-permission-settings/entity-user-permission-settings-modal.element.ts @@ -0,0 +1,102 @@ +import { html, customElement, property, css, nothing, state } from '@umbraco-cms/backoffice/external/lit'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { + UmbEntityUserPermissionSettingsModalData, + UmbEntityUserPermissionSettingsModalValue, + UmbModalContext, +} from '@umbraco-cms/backoffice/modal'; +import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; +import { UmbSelectionChangeEvent } from '@umbraco-cms/backoffice/event'; + +@customElement('umb-entity-user-permission-settings-modal') +export class UmbEntityUserPermissionSettingsModalElement extends UmbLitElement { + @property({ attribute: false }) + modalContext?: UmbModalContext; + + #data?: UmbEntityUserPermissionSettingsModalData; + + @property({ type: Object }) + get data(): UmbEntityUserPermissionSettingsModalData | undefined { + return this.#data; + } + set data(data: UmbEntityUserPermissionSettingsModalData | undefined) { + this._entityType = data?.entityType; + this._allowedPermissions = data?.allowedPermissions ?? []; + this._headline = data?.headline ?? this._headline; + } + + @state() + _headline: string = 'Set permissions'; + + @state() + _entityType?: string; + + @state() + _allowedPermissions: Array = []; + + private _handleConfirm() { + this.modalContext?.submit({ allowedPermissions: this._allowedPermissions }); + } + + private _handleCancel() { + this.modalContext?.reject(); + } + + #onSelectedUserPermission(event: UmbSelectionChangeEvent) { + const target = event.target as any; + this._allowedPermissions = target.selectedPermissions; + } + + render() { + return html` + + + ${this._entityType + ? html` ` + : nothing} + + + Cancel + + + `; + } + + static styles = [ + UmbTextStyles, + css` + .permission-toggle { + display: flex; + align-items: center; + border-bottom: 1px solid var(--uui-color-divider); + padding: var(--uui-size-space-3) 0 var(--uui-size-space-4) 0; + } + + .permission-meta { + margin-left: var(--uui-size-space-4); + line-height: 1.2em; + } + + .permission-name { + font-weight: bold; + } + `, + ]; +} + +export default UmbEntityUserPermissionSettingsModalElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-entity-user-permission-modal': UmbEntityUserPermissionSettingsModalElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-permission/modals/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-permission/modals/manifests.ts new file mode 100644 index 0000000000..ca7d2173bf --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-permission/modals/manifests.ts @@ -0,0 +1,8 @@ +export const manifests = [ + { + type: 'modal', + alias: 'Umb.Modal.EntityUserPermissionSettings', + name: 'Entity User Permission Settings Modal', + loader: () => import('./entity-user-permission-settings/entity-user-permission-settings-modal.element.js'), + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/collection/views/table/user-collection-table-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/collection/views/table/user-collection-table-view.element.ts index c70403b8ab..d3f2b7de6c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/collection/views/table/user-collection-table-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/collection/views/table/user-collection-table-view.element.ts @@ -1,12 +1,8 @@ import { UmbUserCollectionContext } from '../../user-collection.context.js'; -import { - UmbUserGroupStore, - UMB_USER_GROUP_STORE_CONTEXT_TOKEN, -} from '../../../../user-group/repository/user-group.store.js'; import type { UmbUserDetail } from '../../../types.js'; import type { UserGroupEntity } from '@umbraco-cms/backoffice/user-group'; import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit'; -import { UmbTextStyles } from "@umbraco-cms/backoffice/style"; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UmbTableElement, UmbTableColumn, @@ -18,9 +14,11 @@ import { } from '@umbraco-cms/backoffice/components'; import { UMB_COLLECTION_CONTEXT } from '@umbraco-cms/backoffice/collection'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; +import { UmbUserGroupRepository } from '@umbraco-cms/backoffice/user-group'; import './column-layouts/name/user-table-name-column-layout.element.js'; import './column-layouts/status/user-table-status-column-layout.element.js'; +import { UserGroupItemResponseModel } from '@umbraco-cms/backoffice/backend-api'; @customElement('umb-user-collection-table-view') export class UmbUserCollectionTableViewElement extends UmbLitElement { @@ -55,9 +53,9 @@ export class UmbUserCollectionTableViewElement extends UmbLitElement { private _tableItems: Array = []; @state() - private _userGroups: Array = []; + private _userGroupItems: Array = []; - private _userGroupStore?: UmbUserGroupStore; + #UmbUserGroupRepository = new UmbUserGroupRepository(this); @state() private _users: Array = []; @@ -70,36 +68,36 @@ export class UmbUserCollectionTableViewElement extends UmbLitElement { constructor() { super(); - this.consumeContext(UMB_USER_GROUP_STORE_CONTEXT_TOKEN, (instance) => { - this._userGroupStore = instance; - this._observeUserGroups(); - }); - this.consumeContext(UMB_COLLECTION_CONTEXT, (instance) => { this.#collectionContext = instance as UmbUserCollectionContext; this.observe(this.#collectionContext.selection, (selection) => (this._selection = selection)); - this.observe(this.#collectionContext.items, (items) => (this._users = items)); + this.observe(this.#collectionContext.items, (items) => { + this._users = items; + this.#observeUserGroups(); + }); }); } - private _observeUserGroups() { - if (!this._userGroupStore) return; - this.observe(this._userGroupStore.getAll(), (userGroups) => { - this._userGroups = userGroups; - this._createTableItems(this._users); + async #observeUserGroups() { + if (this._users.length === 0) return; + const userGroupsIds = [...new Set(this._users.flatMap((user) => user.userGroupIds ?? []))]; + const { asObservable } = await this.#UmbUserGroupRepository.requestItems(userGroupsIds); + this.observe(asObservable(), (userGroups) => { + this._userGroupItems = userGroups; + this.#createTableItems(); }); } - private _getUserGroupNames(ids: Array) { + #getUserGroupNames(ids: Array) { return ids .map((id: string) => { - return this._userGroups.find((x) => x.id === id)?.name; + return this._userGroupItems.find((x) => x.id === id)?.name; }) .join(', '); } - private _createTableItems(users: Array) { - this._tableItems = users.map((user) => { + #createTableItems() { + this._tableItems = this._users.map((user) => { return { id: user.id ?? '', icon: 'umb:user', @@ -112,7 +110,7 @@ export class UmbUserCollectionTableViewElement extends UmbLitElement { }, { columnAlias: 'userGroup', - value: this._getUserGroupNames(user.userGroupIds ?? []), + value: this.#getUserGroupNames(user.userGroupIds ?? []), }, { columnAlias: 'userLastLogin', @@ -129,21 +127,21 @@ export class UmbUserCollectionTableViewElement extends UmbLitElement { }); } - private _handleSelected(event: UmbTableSelectedEvent) { + #onSelected(event: UmbTableSelectedEvent) { event.stopPropagation(); const table = event.target as UmbTableElement; const selection = table.selection; this.#collectionContext?.setSelection(selection); } - private _handleDeselected(event: UmbTableDeselectedEvent) { + #onDeselected(event: UmbTableDeselectedEvent) { event.stopPropagation(); const table = event.target as UmbTableElement; const selection = table.selection; this.#collectionContext?.setSelection(selection); } - private _handleOrdering(event: UmbTableOrderedEvent) { + #onOrdering(event: UmbTableOrderedEvent) { const table = event.target as UmbTableElement; const orderingColumn = table.orderingColumn; const orderingDesc = table.orderingDesc; @@ -157,9 +155,9 @@ export class UmbUserCollectionTableViewElement extends UmbLitElement { .columns=${this._tableColumns} .items=${this._tableItems} .selection=${this._selection} - @selected="${this._handleSelected}" - @deselected="${this._handleDeselected}" - @ordered="${this._handleOrdering}"> + @selected="${this.#onSelected}" + @deselected="${this.#onDeselected}" + @ordered="${this.#onOrdering}"> `; } @@ -183,6 +181,6 @@ export default UmbUserCollectionTableViewElement; declare global { interface HTMLElementTagNameMap { - 'umb-workspace-view-users-table': UmbUserCollectionTableViewElement; + 'umb-user-collection-table-view': UmbUserCollectionTableViewElement; } } diff --git a/src/Umbraco.Web.UI.Client/tsconfig.json b/src/Umbraco.Web.UI.Client/tsconfig.json index 66ce9f19e9..5f2f3645ea 100644 --- a/src/Umbraco.Web.UI.Client/tsconfig.json +++ b/src/Umbraco.Web.UI.Client/tsconfig.json @@ -19,6 +19,7 @@ "baseUrl": ".", "incremental": true, "paths": { + // TODO: can we combine these of multiple tsconfigs so each package can hold their own? // APPS "@umbraco-cms/backoffice/app": ["src/apps/app"], @@ -81,34 +82,42 @@ "@umbraco-cms/backoffice/workspace": ["src/packages/core/workspace"], "@umbraco-cms/backoffice/culture": ["src/packages/core/culture"], - "@umbraco-cms/backoffice/dictionary": ["./src/packages/dictionary/dictionary/index.ts"], - - "@umbraco-cms/backoffice/document": ["./src/packages/documents/documents/index.ts"], - "@umbraco-cms/backoffice/document-blueprint": ["./src/packages/documents/document-blueprints/index.ts"], - "@umbraco-cms/backoffice/document-type": ["./src/packages/documents/document-types/index.ts"], - "@umbraco-cms/backoffice/media": ["./src/packages/media/media/index.ts"], - "@umbraco-cms/backoffice/media-type": ["./src/packages/media/media-types/index.ts"], - "@umbraco-cms/backoffice/member": ["./src/packages/members/members/index.ts"], - "@umbraco-cms/backoffice/member-group": ["./src/packages/members/member-groups/index.ts"], - "@umbraco-cms/backoffice/member-type": ["./src/packages/members/member-types/index.ts"], - "@umbraco-cms/backoffice/package": ["./src/packages/packages/package/index.ts"], - "@umbraco-cms/backoffice/data-type": ["./src/packages/settings/data-types/index.ts"], "@umbraco-cms/backoffice/language": ["./src/packages/settings/languages/index.ts"], "@umbraco-cms/backoffice/logviewer": ["src/packages/log-viewer/index.ts"], "@umbraco-cms/backoffice/relation-type": ["./src/packages/settings/relation-types/index.ts"], "@umbraco-cms/backoffice/tags": ["./src/packages/tags/index.ts"], - "@umbraco-cms/backoffice/partial-view": ["./src/packages/templating/partial-views/index.ts"], - "@umbraco-cms/backoffice/stylesheet": ["./src/packages/templating/stylesheets/index.ts"], - "@umbraco-cms/backoffice/template": ["./src/packages/templating/templates/index.ts"], - // USERS + "@umbraco-cms/backoffice/dictionary": ["./src/packages/dictionary/dictionary/index.ts"], + + // DOCUMENT MANAGEMENT + "@umbraco-cms/backoffice/document": ["./src/packages/documents/documents/index.ts"], + "@umbraco-cms/backoffice/document-blueprint": ["./src/packages/documents/document-blueprints/index.ts"], + "@umbraco-cms/backoffice/document-type": ["./src/packages/documents/document-types/index.ts"], + + // MEDIA MANAGEMENT + "@umbraco-cms/backoffice/media": ["./src/packages/media/media/index.ts"], + "@umbraco-cms/backoffice/media-type": ["./src/packages/media/media-types/index.ts"], + + // MEMBER MANAGEMENT + "@umbraco-cms/backoffice/member": ["./src/packages/members/members/index.ts"], + "@umbraco-cms/backoffice/member-group": ["./src/packages/members/member-groups/index.ts"], + "@umbraco-cms/backoffice/member-type": ["./src/packages/members/member-types/index.ts"], + + // PACKAGE MANAGEMENT + "@umbraco-cms/backoffice/package": ["./src/packages/packages/package/index.ts"], + + // USER MANAGEMENT "@umbraco-cms/backoffice/user-group": ["src/packages/user/user-group"], "@umbraco-cms/backoffice/current-user": ["src/packages/user/current-user"], "@umbraco-cms/backoffice/user": ["src/packages/user/user"], + "@umbraco-cms/backoffice/user-permission": ["src/packages/user/user-permission"], // TEMPLATING "@umbraco-cms/backoffice/code-editor": ["src/packages/templating/code-editor"], + "@umbraco-cms/backoffice/partial-view": ["./src/packages/templating/partial-views/index.ts"], + "@umbraco-cms/backoffice/stylesheet": ["./src/packages/templating/stylesheets/index.ts"], + "@umbraco-cms/backoffice/template": ["./src/packages/templating/templates/index.ts"], "@umbraco-cms/backoffice/css": ["src/shared/css/custom-properties.css"], diff --git a/src/Umbraco.Web.UI.Client/web-test-runner.config.mjs b/src/Umbraco.Web.UI.Client/web-test-runner.config.mjs index b1f37fb12d..42bbd5e551 100644 --- a/src/Umbraco.Web.UI.Client/web-test-runner.config.mjs +++ b/src/Umbraco.Web.UI.Client/web-test-runner.config.mjs @@ -106,6 +106,7 @@ export default { '@umbraco-cms/backoffice/user-group': './src/packages/user/user-group/index.ts', '@umbraco-cms/backoffice/current-user': './src/packages/user/current-user/index.ts', '@umbraco-cms/backoffice/user': './src/packages/user/user/index.ts', + '@umbraco-cms/backoffice/user-permission': './src/packages/user/user-permission/index.ts', '@umbraco-cms/backoffice/code-editor': './src/packages/templating/code-editor/index.ts',