diff --git a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/index.ts b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/index.ts index 4122fa6cda..dde20b1213 100644 --- a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/index.ts +++ b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/index.ts @@ -107,6 +107,7 @@ export type { DocumentCollectionResponseModel } from './models/DocumentCollectio export type { DocumentConfigurationResponseModel } from './models/DocumentConfigurationResponseModel'; export type { DocumentItemResponseModel } from './models/DocumentItemResponseModel'; export type { DocumentNotificationResponseModel } from './models/DocumentNotificationResponseModel'; +export type { DocumentPermissionPresentationModel } from './models/DocumentPermissionPresentationModel'; export type { DocumentRecycleBinItemResponseModel } from './models/DocumentRecycleBinItemResponseModel'; export type { DocumentResponseModel } from './models/DocumentResponseModel'; export type { DocumentTreeItemResponseModel } from './models/DocumentTreeItemResponseModel'; @@ -362,10 +363,10 @@ export type { TemplateQueryResultItemPresentationModel } from './models/Template export type { TemplateQueryResultResponseModel } from './models/TemplateQueryResultResponseModel'; export type { TemplateQuerySettingsResponseModel } from './models/TemplateQuerySettingsResponseModel'; export type { TemplateResponseModel } from './models/TemplateResponseModel'; -export type { TemporaryFileConfigurationResponseModel } from './models/TemporaryFileConfigurationResponseModel'; export type { TemporaryFileResponseModel } from './models/TemporaryFileResponseModel'; export type { TourStatusModel } from './models/TourStatusModel'; export type { TreeItemPresentationModel } from './models/TreeItemPresentationModel'; +export type { UnknownTypePermissionPresentationModel } from './models/UnknownTypePermissionPresentationModel'; export type { UnlockUsersRequestModel } from './models/UnlockUsersRequestModel'; export type { UnpublishDocumentRequestModel } from './models/UnpublishDocumentRequestModel'; export type { UpdateContentForDocumentRequestModel } from './models/UpdateContentForDocumentRequestModel'; diff --git a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/CurrentUserResponseModel.ts b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/CurrentUserResponseModel.ts index 2da7f46b48..986c653af0 100644 --- a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/CurrentUserResponseModel.ts +++ b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/CurrentUserResponseModel.ts @@ -3,6 +3,9 @@ /* tslint:disable */ /* eslint-disable */ +import type { DocumentPermissionPresentationModel } from './DocumentPermissionPresentationModel'; +import type { UnknownTypePermissionPresentationModel } from './UnknownTypePermissionPresentationModel'; + export type CurrentUserResponseModel = { id: string; email: string; @@ -14,6 +17,8 @@ export type CurrentUserResponseModel = { avatarUrls: Array; languages: Array; hasAccessToAllLanguages: boolean; - permissions: Array; + fallbackPermissions: Array; + permissions: Array<(DocumentPermissionPresentationModel | UnknownTypePermissionPresentationModel)>; + allowedSections: Array; }; diff --git a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/DocumentPermissionPresentationModel.ts b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/DocumentPermissionPresentationModel.ts new file mode 100644 index 0000000000..420b96ec34 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/DocumentPermissionPresentationModel.ts @@ -0,0 +1,13 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ReferenceByIdModel } from './ReferenceByIdModel'; + +export type DocumentPermissionPresentationModel = { + $type: string; + document: ReferenceByIdModel; + verbs: Array; +}; + diff --git a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/TemporaryFileConfigurationResponseModel.ts b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/TemporaryFileConfigurationResponseModel.ts deleted file mode 100644 index e9c57080fd..0000000000 --- a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/TemporaryFileConfigurationResponseModel.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* generated using openapi-typescript-codegen -- do no edit */ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ - -export type TemporaryFileConfigurationResponseModel = { - imageFileTypes: Array; - disallowedUploadedFilesExtensions: Array; - allowedUploadedFileExtensions: Array; - maxFileSize?: number | null; -}; - diff --git a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/UnknownTypePermissionPresentationModel.ts b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/UnknownTypePermissionPresentationModel.ts new file mode 100644 index 0000000000..ca089bbf6f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/UnknownTypePermissionPresentationModel.ts @@ -0,0 +1,11 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +export type UnknownTypePermissionPresentationModel = { + $type: string; + verbs: Array; + context: string; +}; + diff --git a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/UserGroupBaseModel.ts b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/UserGroupBaseModel.ts index 2340777c46..12df62d966 100644 --- a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/UserGroupBaseModel.ts +++ b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models/UserGroupBaseModel.ts @@ -3,7 +3,9 @@ /* tslint:disable */ /* eslint-disable */ +import type { DocumentPermissionPresentationModel } from './DocumentPermissionPresentationModel'; import type { ReferenceByIdModel } from './ReferenceByIdModel'; +import type { UnknownTypePermissionPresentationModel } from './UnknownTypePermissionPresentationModel'; export type UserGroupBaseModel = { name: string; @@ -15,6 +17,7 @@ export type UserGroupBaseModel = { documentRootAccess: boolean; mediaStartNode?: ReferenceByIdModel | null; mediaRootAccess: boolean; - permissions: Array; + fallbackPermissions: Array; + permissions: Array<(DocumentPermissionPresentationModel | UnknownTypePermissionPresentationModel)>; }; diff --git a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/services/TemporaryFileResource.ts b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/services/TemporaryFileResource.ts index c78ba660da..848076b99a 100644 --- a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/services/TemporaryFileResource.ts +++ b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/services/TemporaryFileResource.ts @@ -2,7 +2,6 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -import type { TemporaryFileConfigurationResponseModel } from '../models/TemporaryFileConfigurationResponseModel'; import type { TemporaryFileResponseModel } from '../models/TemporaryFileResponseModel'; import type { CancelablePromise } from '../core/CancelablePromise'; @@ -86,7 +85,7 @@ export class TemporaryFileResource { * @returns any Success * @throws ApiError */ - public static getTemporaryFileConfiguration(): CancelablePromise { + public static getTemporaryFileConfiguration(): CancelablePromise { return __request(OpenAPI, { method: 'GET', url: '/umbraco/management/api/v1/temporary-file/configuration', diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.db.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.db.ts index b1b0423c9c..468a194c24 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.db.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.db.ts @@ -47,10 +47,10 @@ const createDetailMockMapper = (request: CreateDataTypeRequestModel): UmbMockDat editorAlias: request.editorAlias, editorUiAlias: request.editorUiAlias, values: request.values, + canIgnoreStartNodes: false, isFolder: false, hasChildren: false, isDeletable: true, - canIgnoreStartNodes: false, }; }; diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/user-group/user-group.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/user-group/user-group.data.ts index 46986bd2d8..81a46cb651 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/user-group/user-group.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/user-group/user-group.data.ts @@ -1,9 +1,4 @@ import type { UserGroupItemResponseModel, UserGroupResponseModel } from '@umbraco-cms/backoffice/external/backend-api'; -import { - UMB_USER_PERMISSION_DOCUMENT_CREATE, - UMB_USER_PERMISSION_DOCUMENT_DELETE, - UMB_USER_PERMISSION_DOCUMENT_READ, -} from '@umbraco-cms/backoffice/document'; export type UmbMockUserGroupModel = UserGroupResponseModel & UserGroupItemResponseModel; @@ -13,10 +8,29 @@ export const data: Array = [ name: 'Administrators', icon: 'icon-medal', documentStartNode: { id: 'all-property-editors-document-id' }, + fallbackPermissions: [ + 'Umb.Document.Read', + 'Umb.Document.Create', + 'Umb.Document.Update', + 'Umb.Document.Delete', + 'Umb.Document.CreateBlueprint', + 'Umb.Document.Notifications', + 'Umb.Document.Publish', + 'Umb.Document.Permissions', + 'Umb.Document.Unpublish', + 'Umb.Document.Duplicate', + 'Umb.Document.Move', + 'Umb.Document.Sort', + 'Umb.Document.CultureAndHostnames', + 'Umb.Document.PublicAccess', + 'Umb.Document.Rollback', + ], permissions: [ - UMB_USER_PERMISSION_DOCUMENT_READ, - UMB_USER_PERMISSION_DOCUMENT_CREATE, - UMB_USER_PERMISSION_DOCUMENT_DELETE, + { + $type: 'DocumentPermissionPresentationModel', + verbs: ['Umb.Document.Rollback'], + document: { id: 'simple-document-id' }, + }, ], sections: [], languages: [], @@ -30,7 +44,22 @@ export const data: Array = [ name: 'Editors', icon: 'icon-tools', documentStartNode: { id: 'all-property-editors-document-id' }, - permissions: [UMB_USER_PERMISSION_DOCUMENT_CREATE, UMB_USER_PERMISSION_DOCUMENT_DELETE], + fallbackPermissions: [ + 'Umb.Document.Read', + 'Umb.Document.Create', + 'Umb.Document.Update', + 'Umb.Document.Delete', + 'Umb.Document.CreateBlueprint', + 'Umb.Document.Notifications', + 'Umb.Document.Publish', + 'Umb.Document.Unpublish', + 'Umb.Document.Duplicate', + 'Umb.Document.Move', + 'Umb.Document.Sort', + 'Umb.Document.PublicAccess', + 'Umb.Document.Rollback', + ], + permissions: [], sections: [], languages: [], hasAccessToAllLanguages: true, @@ -43,7 +72,8 @@ export const data: Array = [ name: 'Sensitive data', icon: 'icon-lock', documentStartNode: { id: 'all-property-editors-document-id' }, - permissions: [UMB_USER_PERMISSION_DOCUMENT_CREATE, UMB_USER_PERMISSION_DOCUMENT_DELETE], + fallbackPermissions: [], + permissions: [], sections: [], languages: [], hasAccessToAllLanguages: true, @@ -56,7 +86,8 @@ export const data: Array = [ name: 'Translators', icon: 'icon-globe', documentStartNode: { id: 'all-property-editors-document-id' }, - permissions: [UMB_USER_PERMISSION_DOCUMENT_CREATE, UMB_USER_PERMISSION_DOCUMENT_DELETE], + fallbackPermissions: ['Umb.Document.Read', 'Umb.Document.Update'], + permissions: [], sections: [], languages: [], hasAccessToAllLanguages: true, @@ -69,7 +100,13 @@ export const data: Array = [ name: 'Writers', icon: 'icon-edit', documentStartNode: { id: 'all-property-editors-document-id' }, - permissions: [UMB_USER_PERMISSION_DOCUMENT_CREATE, UMB_USER_PERMISSION_DOCUMENT_DELETE], + fallbackPermissions: [ + 'Umb.Document.Read', + 'Umb.Document.Create', + 'Umb.Document.Update', + 'Umb.Document.Notifications', + ], + permissions: [], sections: [], languages: [], hasAccessToAllLanguages: true, diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/user-group/user-group.db.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/user-group/user-group.db.ts index da149985fe..ad7daef15d 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/user-group/user-group.db.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/user-group/user-group.db.ts @@ -5,6 +5,8 @@ import type { UmbMockUserGroupModel } from './user-group.data.js'; import { data } from './user-group.data.js'; import type { CreateUserGroupRequestModel, + DocumentPermissionPresentationModel, + UnknownTypePermissionPresentationModel, UserGroupItemResponseModel, UserGroupResponseModel, } from '@umbraco-cms/backoffice/external/backend-api'; @@ -24,14 +26,17 @@ export class UmbUserGroupMockDB extends UmbEntityMockDbBase { const permissions = this.data - .filter((userGroup) => userGroupIds.includes(userGroup.id || '')) + .filter((userGroup) => userGroupIds.includes(userGroup.id)) .map((userGroup) => (userGroup.permissions?.length ? userGroup.permissions : [])) .flat(); // Remove duplicates - return [...new Set(permissions)]; + const uniqueArray = Array.from(new Set(permissions.map((e) => JSON.stringify(e)))).map((e) => JSON.parse(e)); + return uniqueArray; } } @@ -55,6 +60,7 @@ const createMockMapper = (item: CreateUserGroupRequestModel): UmbMockUserGroupMo mediaRootAccess: item.mediaRootAccess, mediaStartNode: item.mediaStartNode, name: item.name, + fallbackPermissions: item.fallbackPermissions, permissions: item.permissions, sections: item.sections, }; @@ -72,6 +78,7 @@ const detailResponseMapper = (item: UmbMockUserGroupModel): UserGroupResponseMod mediaRootAccess: item.mediaRootAccess, mediaStartNode: item.mediaStartNode, name: item.name, + fallbackPermissions: item.fallbackPermissions, permissions: item.permissions, sections: item.sections, }; 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 deleted file mode 100644 index e6121a1349..0000000000 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/user-permission.data.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { UmbEntityData } from './entity.data.js'; -import { - UMB_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: UMB_DOCUMENT_ENTITY_TYPE, - documentId: 'simple-document-id', - userGroupId: 'user-group-administrators-id', - }, - permissions: [UMB_USER_PERMISSION_DOCUMENT_READ], - }, - { - id: 'b70b1453-a912-4157-ba62-20c2f0ab6a88', - target: { - entityType: UMB_DOCUMENT_ENTITY_TYPE, - documentId: 'simple-document-id', - userGroupId: 'user-group-editors-id', - }, - permissions: [UMB_USER_PERMISSION_DOCUMENT_READ, UMB_USER_PERMISSION_DOCUMENT_CREATE], - }, - { - id: 'b70b1453-a912-4157-ba62-20c2f0ab6a88', - target: { - entityType: UMB_DOCUMENT_ENTITY_TYPE, - documentId: 'c05da24d-7740-447b-9cdc-bd8ce2172e38', - userGroupId: 'user-group-administrators-id', - }, - 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/user.db.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/user/user.db.ts index e12caf1687..0493571cf9 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/user/user.db.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/user/user.db.ts @@ -64,7 +64,9 @@ class UmbUserMockDB extends UmbEntityMockDbBase { languages: [], documentStartNodeIds: firstUser.documentStartNodeIds, mediaStartNodeIds: firstUser.mediaStartNodeIds, + fallbackPermissions: [], permissions, + allowedSections: [], }; } 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 index 0f2594503a..401db9833f 100644 --- 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 @@ -1,8 +1,8 @@ export class UmbDeselectedEvent extends Event { public static readonly TYPE = 'deselected'; - public unique: string; + public unique: string | null; - public constructor(unique: string) { + public constructor(unique: string | null) { // 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/selected.event.ts b/src/Umbraco.Web.UI.Client/src/packages/core/event/selected.event.ts index e3ae853d01..14374a1769 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,8 +1,8 @@ export class UmbSelectedEvent extends Event { public static readonly TYPE = 'selected'; - public unique: string; + public unique: string | null; - public constructor(unique: string) { + public constructor(unique: string | null) { // mimics the native change event super(UmbSelectedEvent.TYPE, { bubbles: true, composed: false, cancelable: false }); this.unique = unique; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/user-permission.model.ts b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/entity-user-permission.model.ts similarity index 50% rename from src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/user-permission.model.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/entity-user-permission.model.ts index 7a3b35a448..e89ce82af6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/user-permission.model.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/entity-user-permission.model.ts @@ -1,12 +1,13 @@ import type { ManifestBase } from '@umbraco-cms/backoffice/extension-api'; -export interface ManifestUserPermission extends ManifestBase { - type: 'userPermission'; - meta: MetaUserPermission; +export interface ManifestEntityUserPermission extends ManifestBase { + type: 'entityUserPermission'; + meta: MetaEntityUserPermission; } -export interface MetaUserPermission { +export interface MetaEntityUserPermission { entityType: string; + verbs: Array; label?: string; labelKey?: string; description?: string; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/index.ts index 32a47be86f..d8dcd66155 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/index.ts @@ -32,8 +32,8 @@ import type { ManifestWorkspaceAction } from './workspace-action.model.js'; import type { ManifestWorkspaceContext } from './workspace-context.model.js'; import type { ManifestWorkspaceFooterApp } from './workspace-footer-app.model.js'; import type { ManifestWorkspaceView } from './workspace-view.model.js'; -import type { ManifestUserPermission } from './user-permission.model.js'; -import type { ManifestUserGranularPermission } from './user-granular-permission.model.js'; +import type { ManifestEntityUserPermission } from './entity-user-permission.model.js'; +import type { ManifestGranularUserPermission } from './user-granular-permission.model.js'; import type { ManifestCollectionAction } from './collection-action.model.js'; import type { ManifestBase, @@ -72,7 +72,7 @@ export type * from './tinymce-plugin.model.js'; export type * from './tree-item.model.js'; export type * from './tree.model.js'; export type * from './user-granular-permission.model.js'; -export type * from './user-permission.model.js'; +export type * from './entity-user-permission.model.js'; export type * from './user-profile-app.model.js'; export type * from './workspace-action.model.js'; export type * from './workspace-context.model.js'; @@ -126,6 +126,6 @@ export type ManifestTypes = | ManifestWorkspaceContext | ManifestWorkspaceFooterApp | ManifestWorkspaceView - | ManifestUserPermission - | ManifestUserGranularPermission + | ManifestEntityUserPermission + | ManifestGranularUserPermission | ManifestBase; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/user-granular-permission.model.ts b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/user-granular-permission.model.ts index fc6698a802..e70b2caac2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/user-granular-permission.model.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/user-granular-permission.model.ts @@ -1,10 +1,14 @@ import type { ManifestElement } from '@umbraco-cms/backoffice/extension-api'; -export interface ManifestUserGranularPermission extends ManifestElement { +export interface ManifestGranularUserPermission extends ManifestElement { type: 'userGranularPermission'; - meta: MetaUserGranularPermission; + meta: MetaGranularUserPermission; } -export interface MetaUserGranularPermission { - entityType: string; +export interface MetaGranularUserPermission { + schemaType: string; + label?: string; + labelKey?: string; + description?: string; + descriptionKey?: string; } 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 33cc1bd999..d0f3fb25d0 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,7 +1,7 @@ import { html, customElement, state, ifDefined } from '@umbraco-cms/backoffice/external/lit'; import type { UmbTreePickerModalData, UmbPickerModalValue } from '@umbraco-cms/backoffice/modal'; import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; -import { UmbSelectionChangeEvent } from '@umbraco-cms/backoffice/event'; +import { UmbDeselectedEvent, UmbSelectedEvent, UmbSelectionChangeEvent } from '@umbraco-cms/backoffice/event'; import type { UmbTreeElement, UmbTreeItemModelBase, UmbTreeSelectionConfiguration } from '@umbraco-cms/backoffice/tree'; @customElement('umb-tree-picker-modal') @@ -29,11 +29,21 @@ export class UmbTreePickerModalElement diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/entity-user-permission-settings-modal.token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/entity-user-permission-settings-modal.token.ts index bac358195d..50bc5515ad 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/entity-user-permission-settings-modal.token.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/entity-user-permission-settings-modal.token.ts @@ -3,12 +3,11 @@ import { UmbModalToken } from './modal-token.js'; export interface UmbEntityUserPermissionSettingsModalData { unique: string; entityType: string; - allowedPermissions: Array; headline?: string; } export type UmbEntityUserPermissionSettingsModalValue = { - allowedPermissions: Array; + allowedVerbs: Array; }; export const UMB_ENTITY_USER_PERMISSION_MODAL = new UmbModalToken< diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/utils/selection-manager/selection.manager.ts b/src/Umbraco.Web.UI.Client/src/packages/core/utils/selection-manager/selection.manager.ts index 4f1f5a9d49..1d2a043c50 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/utils/selection-manager/selection.manager.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/utils/selection-manager/selection.manager.ts @@ -1,6 +1,6 @@ import { UmbBaseController } from '@umbraco-cms/backoffice/class-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -import { UmbSelectionChangeEvent } from '@umbraco-cms/backoffice/event'; +import { UmbDeselectedEvent, UmbSelectedEvent, UmbSelectionChangeEvent } from '@umbraco-cms/backoffice/event'; import { UmbArrayState, UmbBooleanState } from '@umbraco-cms/backoffice/observable-api'; /** @@ -8,7 +8,7 @@ import { UmbArrayState, UmbBooleanState } from '@umbraco-cms/backoffice/observab * @export * @class UmbSelectionManager */ -export class UmbSelectionManager extends UmbBaseController { +export class UmbSelectionManager extends UmbBaseController { #selectable = new UmbBooleanState(false); public readonly selectable = this.#selectable.asObservable(); @@ -105,6 +105,7 @@ export class UmbSelectionManager extends UmbBaseContr if (this.isSelected(unique)) return; const newSelection = this.getMultiple() ? [...this.getSelection(), unique] : [unique]; this.#selection.setValue(newSelection); + this.getHostElement().dispatchEvent(new UmbSelectedEvent(unique)); this.getHostElement().dispatchEvent(new UmbSelectionChangeEvent()); } @@ -117,6 +118,7 @@ export class UmbSelectionManager extends UmbBaseContr if (this.getSelectable() === false) return; const newSelection = this.getSelection().filter((x) => x !== unique); this.#selection.setValue(newSelection); + this.getHostElement().dispatchEvent(new UmbDeselectedEvent(unique)); this.getHostElement().dispatchEvent(new UmbSelectionChangeEvent()); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/index.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/index.ts index b6945bba60..361ab007ce 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/index.ts @@ -1,3 +1,2 @@ export * from './input-document/input-document.element.js'; -export * from './input-document-granular-permission/input-document-granular-permission.element.js'; export * from './input-document-root-picker/input-document-root-picker.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document-granular-permission/input-document-granular-permission.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document-granular-permission/input-document-granular-permission.element.ts deleted file mode 100644 index a34ff5248e..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document-granular-permission/input-document-granular-permission.element.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { UmbDocumentItemRepository } from '../../repository/index.js'; -import type { UmbDocumentItemModel } from '../../repository/item/types.js'; -import type { PropertyValueMap } from '@umbraco-cms/backoffice/external/lit'; -import { css, html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; -import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui'; -import type { UmbModalManagerContext } from '@umbraco-cms/backoffice/modal'; -import { UMB_MODAL_MANAGER_CONTEXT, UMB_DOCUMENT_PICKER_MODAL } from '@umbraco-cms/backoffice/modal'; -import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; -import type { UmbObserverController } from '@umbraco-cms/backoffice/observable-api'; -import { splitStringToArray } from '@umbraco-cms/backoffice/utils'; - -@customElement('umb-input-document-granular-permission') -export class UmbInputDocumentGranularPermissionElement extends FormControlMixin(UmbLitElement) { - private _selectedIds: Array = []; - public get selectedIds(): Array { - return this._selectedIds; - } - public set selectedIds(ids: Array) { - this._selectedIds = ids; - super.value = ids.join(','); - this.#observePickedDocuments(); - } - - @property() - public set value(idsString: string) { - if (idsString !== this._value) { - this.selectedIds = splitStringToArray(idsString); - } - } - - @state() - private _items?: Array; - - #documentItemRepository = new UmbDocumentItemRepository(this); - #modalContext?: UmbModalManagerContext; - #pickedItemsObserver?: UmbObserverController>; - - constructor() { - super(); - this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => (this.#modalContext = instance)); - } - - protected firstUpdated(_changedProperties: PropertyValueMap | Map): void { - super.firstUpdated(_changedProperties); - this.#observePickedDocuments(); - } - - protected getFormElement() { - return undefined; - } - - async #observePickedDocuments() { - this.#pickedItemsObserver?.destroy(); - - const { asObservable } = await this.#documentItemRepository.requestItems(this._selectedIds); - this.#pickedItemsObserver = this.observe(asObservable(), (items) => (this._items = items)); - } - - #openDocumentPicker() { - // We send a shallow copy(good enough as its just an array of ids) of our this._selectedIds, as we don't want the modal to manipulate our data: - // TODO: Use value instead: - const modalContext = this.#modalContext?.open(UMB_DOCUMENT_PICKER_MODAL, { - value: { - selection: [...this._selectedIds], - }, - }); - - //modalContext?.onSubmit().then((value) => { - //this.#setSelection(selection); - //}); - } - - #setSelection(newSelection: Array) { - this.selectedIds = newSelection; - this.dispatchEvent(new UmbChangeEvent()); - } - - disconnectedCallback(): void { - super.disconnectedCallback(); - this.#pickedItemsObserver?.destroy(); - } - - render() { - return html` - ${this._items?.map((item) => this.#renderItem(item))} - Add - `; - } - - #renderItem(item: UmbDocumentItemModel) { - return html`
Render something here ${item.unique}
`; - } - - static styles = [ - css` - #add-button { - width: 100%; - } - `, - ]; -} - -export default UmbInputDocumentGranularPermissionElement; - -declare global { - interface HTMLElementTagNameMap { - 'umb-input-document-granular-permission': UmbInputDocumentGranularPermissionElement; - } -} 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 2a7eed5f84..f49da1c251 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 @@ -5,7 +5,6 @@ import { UmbCreateDocumentBlueprintEntityAction } from './create-blueprint.actio 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 { manifests as publicAccessManifests } from './public-access/manifests.js'; import { manifests as cultureAndHostnamesManifests } from './culture-and-hostnames/manifests.js'; import { @@ -17,7 +16,6 @@ import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; const entityActions: Array = [ ...createManifests, - ...permissionManifests, ...publicAccessManifests, ...cultureAndHostnamesManifests, { 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 deleted file mode 100644 index 4342532571..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/permissions/manifests.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { UMB_DOCUMENT_DETAIL_REPOSITORY_ALIAS } from '../../repository/index.js'; -import { UMB_DOCUMENT_ENTITY_TYPE } from '../../entity.js'; -import { UmbDocumentPermissionsEntityAction } from './permissions.action.js'; -import type { ManifestEntityAction, ManifestModal } from '@umbraco-cms/backoffice/extension-registry'; - -const entityActions: Array = [ - { - type: 'entityAction', - alias: 'Umb.EntityAction.Document.Permissions', - name: 'Document Permissions Entity Action', - api: UmbDocumentPermissionsEntityAction, - meta: { - icon: 'icon-vcard', - label: 'Permissions (TBD)', - repositoryAlias: UMB_DOCUMENT_DETAIL_REPOSITORY_ALIAS, - entityTypes: [UMB_DOCUMENT_ENTITY_TYPE], - }, - }, -]; - -const modals: Array = [ - { - type: 'modal', - alias: 'Umb.Modal.Permissions', - name: 'Permissions Modal', - js: () => 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 deleted file mode 100644 index fb480097c9..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/permissions/permissions-modal.element.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { UmbDocumentPermissionRepository } from '../../user-permissions/index.js'; -import { UmbDocumentItemRepository } from '../../repository/index.js'; -import { UmbUserGroupItemRepository, UMB_USER_GROUP_PICKER_MODAL } from '@umbraco-cms/backoffice/user-group'; -import { html, customElement, property, state, ifDefined, nothing } from '@umbraco-cms/backoffice/external/lit'; -import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import type { - UmbEntityUserPermissionSettingsModalData, - UmbEntityUserPermissionSettingsModalValue, - UmbModalContext, - UmbModalManagerContext, -} from '@umbraco-cms/backoffice/modal'; -import { UMB_ENTITY_USER_PERMISSION_MODAL, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; -import { UmbLitElement } from '@umbraco-cms/backoffice/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 = []; - #userGroupIemRepository = new UmbUserGroupItemRepository(this); - #documentPermissionRepository = new UmbDocumentPermissionRepository(this); - #documentItemRepository = new UmbDocumentItemRepository(this); - #modalManagerContext?: UmbModalManagerContext; - #userGroupPickerModal?: UmbModalContext; - - private _handleConfirm() { - this.modalContext?.submit(); - } - - private _handleCancel() { - this.modalContext?.reject(); - } - - constructor() { - super(); - - this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (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.#documentItemRepository.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.#userGroupIemRepository.requestItems(userGroupIds); - - const userGroups = data ?? []; - - this._userGroupRefs = this.#userPermissions.map((entry) => { - const userGroup = userGroups.find((userGroup) => userGroup.unique == 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, { - data: { - 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)} - standalone> - ${userGroup.icon ? html`` : nothing} - `, - )} - - 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/permissions.action.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/permissions/permissions.action.ts deleted file mode 100644 index 2c08ad502b..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/permissions/permissions.action.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type { UmbDocumentDetailRepository } from '../../repository/index.js'; -import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action'; -import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; -import type { UmbModalManagerContext } from '@umbraco-cms/backoffice/modal'; -import { UMB_MODAL_MANAGER_CONTEXT, UMB_PERMISSIONS_MODAL } from '@umbraco-cms/backoffice/modal'; - -export class UmbDocumentPermissionsEntityAction extends UmbEntityActionBase { - #modalManagerContext?: UmbModalManagerContext; - - constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string, entityType: string) { - super(host, repositoryAlias, unique, entityType); - - this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => { - this.#modalManagerContext = instance; - }); - } - - async execute() { - if (!this.repository) return; - if (!this.#modalManagerContext) return; - - this.#modalManagerContext.open(UMB_PERMISSIONS_MODAL, { - data: { - unique: this.unique, - entityType: 'document', - }, - }); - } -} diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/item/document-item.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/item/document-item.server.data-source.ts index 6533faf01e..4b050657d0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/item/document-item.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/item/document-item.server.data-source.ts @@ -1,3 +1,4 @@ +import { UMB_DOCUMENT_ENTITY_TYPE } from '../../entity.js'; import type { UmbDocumentItemModel } from './types.js'; import type { DocumentItemResponseModel } from '@umbraco-cms/backoffice/external/backend-api'; import { DocumentResource } from '@umbraco-cms/backoffice/external/backend-api'; @@ -32,6 +33,7 @@ const getItems = (uniques: Array) => DocumentResource.getItemDocument({ const mapper = (item: DocumentItemResponseModel): UmbDocumentItemModel => { return { + entityType: UMB_DOCUMENT_ENTITY_TYPE, unique: item.id, isTrashed: item.isTrashed, isProtected: item.isProtected, diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/item/types.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/item/types.ts index d6dfcb6e28..18e03a5462 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/item/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/item/types.ts @@ -1,7 +1,9 @@ +import type { UmbDocumentEntityType } from '../../entity.js'; import type { DocumentVariantStateModel } from '@umbraco-cms/backoffice/external/backend-api'; import type { UmbReferenceByUnique } from '@umbraco-cms/backoffice/models'; export interface UmbDocumentItemModel { + entityType: UmbDocumentEntityType; name: string; // TODO: this is not correct. We need to get it from the variants. This is a temp solution. unique: string; isTrashed: boolean; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/constants.ts index 7dd4b77160..b33399bf8f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/constants.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/constants.ts @@ -8,7 +8,7 @@ export const UMB_USER_PERMISSION_DOCUMENT_PUBLISH = 'Umb.UserPermission.Document export const UMB_USER_PERMISSION_DOCUMENT_PERMISSIONS = 'Umb.UserPermission.Document.Permissions'; export const UMB_USER_PERMISSION_DOCUMENT_SEND_FOR_APPROVAL = 'Umb.UserPermission.Document.SendForApproval'; export const UMB_USER_PERMISSION_DOCUMENT_UNPUBLISH = 'Umb.UserPermission.Document.Unpublish'; -export const UMB_USER_PERMISSION_DOCUMENT_COPY = 'Umb.UserPermission.Document.Copy'; +export const UMB_USER_PERMISSION_DOCUMENT_DUPLICATE = 'Umb.UserPermission.Document.Duplicate'; export const UMB_USER_PERMISSION_DOCUMENT_MOVE = 'Umb.UserPermission.Document.Move'; export const UMB_USER_PERMISSION_DOCUMENT_SORT = 'Umb.UserPermission.Document.Sort'; export const UMB_USER_PERMISSION_DOCUMENT_CULTURE_AND_HOSTNAMES = 'Umb.UserPermission.Document.CultureAndHostnames'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/input-document-granular-user-permission/input-document-granular-user-permission.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/input-document-granular-user-permission/input-document-granular-user-permission.element.ts new file mode 100644 index 0000000000..11e021d7f3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/input-document-granular-user-permission/input-document-granular-user-permission.element.ts @@ -0,0 +1,251 @@ +import type { UmbDocumentUserPermissionModel } from '../types.js'; +import { UmbDocumentItemRepository, type UmbDocumentItemModel } from '../../repository/index.js'; +import { css, customElement, html, repeat, state } from '@umbraco-cms/backoffice/external/lit'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import type { UmbModalManagerContext } from '@umbraco-cms/backoffice/modal'; +import { + UMB_DOCUMENT_PICKER_MODAL, + UMB_ENTITY_USER_PERMISSION_MODAL, + UMB_MODAL_MANAGER_CONTEXT, +} from '@umbraco-cms/backoffice/modal'; +import type { UmbDeselectedEvent } from '@umbraco-cms/backoffice/event'; +import { UmbChangeEvent, UmbSelectedEvent } from '@umbraco-cms/backoffice/event'; +import type { ManifestEntityUserPermission } from '@umbraco-cms/backoffice/extension-registry'; +import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; +import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui'; + +@customElement('umb-input-document-granular-user-permission') +export class UmbInputDocumentGranularUserPermissionElement extends FormControlMixin(UmbLitElement) { + _permissions: Array = []; + public get permissions(): Array { + return this._permissions; + } + public set permissions(value: Array) { + this._permissions = value; + const uniques = value.map((item) => item.document.id); + this.#observePickedDocuments(uniques); + } + + @state() + private _items?: Array; + + #documentItemRepository = new UmbDocumentItemRepository(this); + #modalManagerContext?: UmbModalManagerContext; + #documentPickerModalContext?: any; + #entityUserPermissionModalContext?: any; + + constructor() { + super(); + + this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => (this.#modalManagerContext = instance)); + } + + protected getFormElement() { + return undefined; + } + + async #observePickedDocuments(uniques: Array) { + const { asObservable } = await this.#documentItemRepository.requestItems(uniques); + this.observe(asObservable(), (items) => (this._items = items)); + } + + async #editGranularPermission(item: UmbDocumentItemModel) { + const currentPermissionVerbs = this.#getPermissionForDocument(item.unique)?.verbs ?? []; + const result = await this.#selectEntityUserPermissionsForDocument(item, currentPermissionVerbs); + // don't do anything if the verbs have not been updated + if (JSON.stringify(result) === JSON.stringify(currentPermissionVerbs)) return; + + // update permission with new verbs + this.permissions = this._permissions.map((permission) => { + if (permission.document.id === item.unique) { + return { + ...permission, + verbs: result, + }; + } + return permission; + }); + + this.dispatchEvent(new UmbChangeEvent()); + } + + #addGranularPermission() { + this.#documentPickerModalContext = this.#modalManagerContext?.open(UMB_DOCUMENT_PICKER_MODAL, { + data: { + hideTreeRoot: true, + // prevent already selected items to be picked again + // TODO: this type is wrong. It should be the tree item type + pickableFilter: (treeItem: UmbDocumentItemModel) => + !this._items?.map((i) => i.unique).includes(treeItem.unique), + }, + }); + + this.#documentPickerModalContext?.addEventListener(UmbSelectedEvent.TYPE, async (event: UmbDeselectedEvent) => { + const selectedEvent = event as UmbSelectedEvent; + const unique = selectedEvent.unique; + if (!unique) return; + + const documentItem = await this.#requestDocumentItem(unique); + const result = await this.#selectEntityUserPermissionsForDocument(documentItem); + this.#documentPickerModalContext?.reject(); + + const permissionItem: UmbDocumentUserPermissionModel = { + $type: 'DocumentPermissionPresentationModel', + document: { id: unique }, + verbs: result, + }; + + this.permissions = [...this._permissions, permissionItem]; + this.dispatchEvent(new UmbChangeEvent()); + }); + } + + async #requestDocumentItem(unique: string) { + if (!unique) throw new Error('Could not open permissions modal, no unique was provided'); + + const { data } = await this.#documentItemRepository.requestItems([unique]); + + const documentItem = data?.[0]; + if (!documentItem) throw new Error('No document item found'); + return documentItem; + } + + async #selectEntityUserPermissionsForDocument(item: UmbDocumentItemModel, allowedVerbs: Array = []) { + // TODO: get correct variant name + const name = item.variants[0]?.name; + const headline = name ? `Permissions for ${name}` : 'Permissions'; + + this.#entityUserPermissionModalContext = this.#modalManagerContext?.open(UMB_ENTITY_USER_PERMISSION_MODAL, { + data: { + unique: item.unique, + entityType: item.entityType, + headline, + }, + value: { + allowedVerbs, + }, + }); + + try { + const value = await this.#entityUserPermissionModalContext?.onSubmit(); + return value?.allowedVerbs; + } catch (error) { + return allowedVerbs; + } + } + + #removeGranularPermission(item: UmbDocumentItemModel) { + const permission = this.#getPermissionForDocument(item.unique); + if (!permission) return; + + this.permissions = this._permissions.filter((v) => JSON.stringify(v) !== JSON.stringify(permission)); + this.dispatchEvent(new UmbChangeEvent()); + } + + render() { + return html`${this.#renderItems()} ${this.#renderAddButton()}`; + } + + #renderItems() { + if (!this._items) return; + return html` + ${repeat( + this._items, + (item) => item.unique, + (item) => this.#renderRef(item), + )} + `; + } + + #renderAddButton() { + return html``; + } + + #renderRef(item: UmbDocumentItemModel) { + if (!item.unique) return; + // TODO: get correct variant name + const name = item.variants[0]?.name; + const permissionNames = this.#getPermissionNamesForDocument(item.unique); + + return html` + + ${this.#renderIcon(item)} ${this.#renderIsTrashed(item)} + + ${this.#renderEditButton(item)} ${this.#renderRemoveButton(item)} + + + `; + } + + #renderIcon(item: UmbDocumentItemModel) { + if (!item.documentType.icon) return; + return html``; + } + + #renderIsTrashed(item: UmbDocumentItemModel) { + if (!item.isTrashed) return; + return html`Trashed`; + } + + #renderEditButton(item: UmbDocumentItemModel) { + return html` + this.#editGranularPermission(item)} + label=${this.localize.term('general_edit')}> + `; + } + + #renderRemoveButton(item: UmbDocumentItemModel) { + return html` this.#removeGranularPermission(item)} + label=${this.localize.term('general_remove')}>`; + } + + #getPermissionForDocument(unique: string) { + return this._permissions?.find((permission) => permission.document.id === unique); + } + + #getPermissionNamesForDocument(unique: string) { + const permission = this.#getPermissionForDocument(unique); + if (!permission) return; + + return umbExtensionsRegistry + .getAllExtensions() + .filter((manifest) => manifest.type === 'entityUserPermission') + .filter((manifest) => + (manifest as ManifestEntityUserPermission).meta.verbs.every((verb) => permission.verbs.includes(verb)), + ) + .map((m) => { + const manifest = m as ManifestEntityUserPermission; + + if (manifest.meta.labelKey) { + return this.localize.term(manifest.meta.labelKey); + } else if (manifest.meta.label) { + return manifest.meta.label; + } else { + return manifest.name; + } + }) + .join(', '); + } + + static styles = [ + css` + #add-button { + width: 100%; + } + `, + ]; +} + +export default UmbInputDocumentGranularUserPermissionElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-document-granular-user-permission': UmbInputDocumentGranularUserPermissionElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/manifests.ts index a52ddc411c..e4fdbff634 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/manifests.ts @@ -1,3 +1,4 @@ +import { UMB_DOCUMENT_ENTITY_TYPE } from '../entity.js'; import { UMB_USER_PERMISSION_DOCUMENT_READ, UMB_USER_PERMISSION_DOCUMENT_CREATE_BLUEPRINT, @@ -6,10 +7,9 @@ import { UMB_USER_PERMISSION_DOCUMENT_NOTIFICATIONS, UMB_USER_PERMISSION_DOCUMENT_PUBLISH, UMB_USER_PERMISSION_DOCUMENT_PERMISSIONS, - UMB_USER_PERMISSION_DOCUMENT_SEND_FOR_APPROVAL, UMB_USER_PERMISSION_DOCUMENT_UNPUBLISH, UMB_USER_PERMISSION_DOCUMENT_UPDATE, - UMB_USER_PERMISSION_DOCUMENT_COPY, + UMB_USER_PERMISSION_DOCUMENT_DUPLICATE, UMB_USER_PERMISSION_DOCUMENT_MOVE, UMB_USER_PERMISSION_DOCUMENT_SORT, UMB_USER_PERMISSION_DOCUMENT_CULTURE_AND_HOSTNAMES, @@ -18,172 +18,177 @@ import { } from './constants.js'; import { manifests as repositoryManifests } from './repository/manifests.js'; import type { - ManifestUserGranularPermission, - ManifestUserPermission, + ManifestGranularUserPermission, + ManifestEntityUserPermission, } from '@umbraco-cms/backoffice/extension-registry'; -const permissions: Array = [ +const permissions: Array = [ { - type: 'userPermission', + type: 'entityUserPermission', alias: UMB_USER_PERMISSION_DOCUMENT_READ, name: 'Read Document User Permission', meta: { - entityType: 'document', + entityType: UMB_DOCUMENT_ENTITY_TYPE, + verbs: ['Umb.Document.Read'], labelKey: 'actions_browse', descriptionKey: 'actionDescriptions_browse', }, }, { - type: 'userPermission', + type: 'entityUserPermission', alias: UMB_USER_PERMISSION_DOCUMENT_CREATE_BLUEPRINT, name: 'Create Document Blueprint User Permission', meta: { - entityType: 'document', + entityType: UMB_DOCUMENT_ENTITY_TYPE, + verbs: ['Umb.Document.CreateBlueprint'], labelKey: 'actions_createblueprint', descriptionKey: 'actionDescriptions_createblueprint', }, }, { - type: 'userPermission', + type: 'entityUserPermission', alias: UMB_USER_PERMISSION_DOCUMENT_DELETE, name: 'Delete Document User Permission', meta: { - entityType: 'document', + entityType: UMB_DOCUMENT_ENTITY_TYPE, + verbs: ['Umb.Document.Delete'], labelKey: 'actions_delete', descriptionKey: 'actionDescriptions_delete', }, }, { - type: 'userPermission', + type: 'entityUserPermission', alias: UMB_USER_PERMISSION_DOCUMENT_CREATE, name: 'Create Document User Permission', meta: { - entityType: 'document', + entityType: UMB_DOCUMENT_ENTITY_TYPE, + verbs: ['Umb.Document.Create'], labelKey: 'actions_create', descriptionKey: 'actionDescriptions_create', }, }, { - type: 'userPermission', + type: 'entityUserPermission', alias: UMB_USER_PERMISSION_DOCUMENT_NOTIFICATIONS, name: 'Document Notifications User Permission', meta: { - entityType: 'document', + entityType: UMB_DOCUMENT_ENTITY_TYPE, + verbs: ['Umb.Document.Notifications'], labelKey: 'actions_notify', descriptionKey: 'actionDescriptions_notify', }, }, { - type: 'userPermission', + type: 'entityUserPermission', alias: UMB_USER_PERMISSION_DOCUMENT_PUBLISH, name: 'Publish Document User Permission', meta: { - entityType: 'document', + entityType: UMB_DOCUMENT_ENTITY_TYPE, + verbs: ['Umb.Document.Publish'], labelKey: 'actions_publish', descriptionKey: 'actionDescriptions_publish', }, }, { - type: 'userPermission', + type: 'entityUserPermission', alias: UMB_USER_PERMISSION_DOCUMENT_PERMISSIONS, name: 'Document Permissions User Permission', meta: { - entityType: 'document', + entityType: UMB_DOCUMENT_ENTITY_TYPE, + verbs: ['Umb.Document.Permissions'], labelKey: 'actions_setPermissions', descriptionKey: 'actionDescriptions_rights', }, }, { - type: 'userPermission', - alias: UMB_USER_PERMISSION_DOCUMENT_SEND_FOR_APPROVAL, - name: 'Send Document For Approval User Permission', - meta: { - entityType: 'document', - labelKey: 'actions_sendtopublish', - descriptionKey: 'actionDescriptions_sendtopublish', - }, - }, - { - type: 'userPermission', + type: 'entityUserPermission', alias: UMB_USER_PERMISSION_DOCUMENT_UNPUBLISH, name: 'Unpublish Document User Permission', meta: { - entityType: 'document', + entityType: UMB_DOCUMENT_ENTITY_TYPE, + verbs: ['Umb.Document.Unpublish'], labelKey: 'actions_unpublish', descriptionKey: 'actionDescriptions_unpublish', }, }, { - type: 'userPermission', + type: 'entityUserPermission', alias: UMB_USER_PERMISSION_DOCUMENT_UPDATE, name: 'Update Document User Permission', meta: { - entityType: 'document', + entityType: UMB_DOCUMENT_ENTITY_TYPE, + verbs: ['Umb.Document.Update'], labelKey: 'actions_update', descriptionKey: 'actionDescriptions_update', }, }, { - type: 'userPermission', - alias: UMB_USER_PERMISSION_DOCUMENT_COPY, - name: 'Copy Document User Permission', + type: 'entityUserPermission', + alias: UMB_USER_PERMISSION_DOCUMENT_DUPLICATE, + name: 'Duplicate Document User Permission', meta: { - entityType: 'document', + entityType: UMB_DOCUMENT_ENTITY_TYPE, + verbs: ['Umb.Document.Duplicate'], labelKey: 'actions_copy', descriptionKey: 'actionDescriptions_copy', group: 'structure', }, }, { - type: 'userPermission', + type: 'entityUserPermission', alias: UMB_USER_PERMISSION_DOCUMENT_MOVE, name: 'Move Document User Permission', meta: { - entityType: 'document', + entityType: UMB_DOCUMENT_ENTITY_TYPE, + verbs: ['Umb.Document.Move'], labelKey: 'actions_move', descriptionKey: 'actionDescriptions_move', group: 'structure', }, }, { - type: 'userPermission', + type: 'entityUserPermission', alias: UMB_USER_PERMISSION_DOCUMENT_SORT, name: 'Sort Document User Permission', meta: { - entityType: 'document', + entityType: UMB_DOCUMENT_ENTITY_TYPE, + verbs: ['Umb.Document.Sort'], labelKey: 'actions_sort', descriptionKey: 'actionDescriptions_sort', group: 'structure', }, }, { - type: 'userPermission', + type: 'entityUserPermission', alias: UMB_USER_PERMISSION_DOCUMENT_CULTURE_AND_HOSTNAMES, name: 'Document Culture And Hostnames User Permission', meta: { - entityType: 'document', + entityType: UMB_DOCUMENT_ENTITY_TYPE, + verbs: ['Umb.Document.CultureAndHostnames'], labelKey: 'actions_assigndomain', descriptionKey: 'actionDescriptions_assignDomain', group: 'administration', }, }, { - type: 'userPermission', + type: 'entityUserPermission', alias: UMB_USER_PERMISSION_DOCUMENT_PUBLIC_ACCESS, name: 'Document Public Access User Permission', meta: { - entityType: 'document', + entityType: UMB_DOCUMENT_ENTITY_TYPE, + verbs: ['Umb.Document.PublicAccess'], labelKey: 'actions_protect', descriptionKey: 'actionDescriptions_protect', group: 'administration', }, }, { - type: 'userPermission', + type: 'entityUserPermission', alias: UMB_USER_PERMISSION_DOCUMENT_ROLLBACK, name: 'Document Rollback User Permission', meta: { - entityType: 'document', + entityType: UMB_DOCUMENT_ENTITY_TYPE, + verbs: ['Umb.Document.Rollback'], labelKey: 'actions_rollback', descriptionKey: 'actionDescriptions_rollback', group: 'administration', @@ -191,14 +196,17 @@ const permissions: Array = [ }, ]; -export const granularPermissions: Array = [ +export const granularPermissions: Array = [ { type: 'userGranularPermission', alias: 'Umb.UserGranularPermission.Document', name: 'Document Granular User Permission', - js: () => import('../components/input-document-granular-permission/input-document-granular-permission.element.js'), + element: () => + import('./input-document-granular-user-permission/input-document-granular-user-permission.element.js'), meta: { - entityType: 'document', + schemaType: 'DocumentPermissionPresentationModel', + label: 'Documents', + description: 'Assign permissions to specific documents', }, }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/types.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/types.ts new file mode 100644 index 0000000000..14feb94a9e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/types.ts @@ -0,0 +1,6 @@ +import type { UmbUserPermissionModel } from '@umbraco-cms/backoffice/user-permission'; + +export interface UmbDocumentUserPermissionModel extends UmbUserPermissionModel { + // TODO: this should be unique instead of an id, but we currently have now way to map a mixed server response. + document: { id: string }; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/index.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/index.ts index 0f2adb8fd6..00c3d17dda 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/index.ts @@ -1,12 +1,11 @@ import './components/index.js'; +export * from './repository/index.js'; +export * from './workspace/index.js'; +export * from './tracked-reference/index.js'; export * from './components/index.js'; export * from './entity.js'; -export * from './repository/index.js'; -export * from './tracked-reference/index.js'; -export * from './user-permissions/index.js'; export * from './utils/index.js'; -export * from './workspace/index.js'; export * from './conditions/index.js'; export { UMB_MEDIA_TREE_ALIAS } from './tree/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/manifests.ts index e0916a5e43..222c4f8347 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/manifests.ts @@ -7,7 +7,6 @@ import { manifests as propertyEditorsManifests } from './property-editors/manife import { manifests as repositoryManifests } from './repository/manifests.js'; import { manifests as sectionViewManifests } from './section-view/manifests.js'; import { manifests as treeManifests } from './tree/manifests.js'; -import { manifests as userPermissionManifests } from './user-permissions/manifests.js'; import { manifests as workspaceManifests } from './workspace/manifests.js'; export const manifests = [ @@ -20,6 +19,5 @@ export const manifests = [ ...repositoryManifests, ...sectionViewManifests, ...treeManifests, - ...userPermissionManifests, ...workspaceManifests, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/user-permissions/index.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/user-permissions/index.ts deleted file mode 100644 index 1e95b5d703..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/user-permissions/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './manifests.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/user-permissions/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/user-permissions/manifests.ts deleted file mode 100644 index 3ddbb9b526..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/user-permissions/manifests.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { ManifestUserPermission } from '@umbraco-cms/backoffice/extension-registry'; - -export const UMB_USER_PERMISSION_MEDIA_MOVE = 'Umb.UserPermission.Media.Move'; -export const UMB_USER_PERMISSION_MEDIA_COPY = 'Umb.UserPermission.Media.Copy'; - -const permissions: Array = [ - { - type: 'userPermission', - alias: UMB_USER_PERMISSION_MEDIA_MOVE, - name: 'Move Media Item User Permission', - meta: { - entityType: 'media', - label: 'Move', - description: 'Allow access to move media items', - group: 'structure', - }, - }, - { - type: 'userPermission', - alias: UMB_USER_PERMISSION_MEDIA_COPY, - name: 'Copy Media Item User Permission', - meta: { - entityType: 'media', - label: 'Copy', - description: 'Allow access to copy a media item', - group: 'structure', - }, - }, -]; - -export const manifests = [...permissions]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/current-user.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/current-user.server.data-source.ts index 50bae0fe8c..35e9bbb860 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/current-user.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/repository/current-user.server.data-source.ts @@ -40,6 +40,7 @@ export class UmbCurrentUserServerDataSource { avatarUrls: data.avatarUrls, languages: data.languages, hasAccessToAllLanguages: data.hasAccessToAllLanguages, + fallbackPermissions: data.fallbackPermissions, permissions: data.permissions, }; return { data: user }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/types.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/types.ts index 0cb54ad319..8d8aaec8d1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/types.ts @@ -1,3 +1,8 @@ +import type { + DocumentPermissionPresentationModel, + UnknownTypePermissionPresentationModel, +} from '@umbraco-cms/backoffice/external/backend-api'; + export interface UmbCurrentUserModel { unique: string; email: string; @@ -9,5 +14,6 @@ export interface UmbCurrentUserModel { avatarUrls: Array; languages: Array; hasAccessToAllLanguages: boolean; - permissions: Array; + fallbackPermissions: Array; + permissions: Array; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/utils/is-current-user.function.ts b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/utils/is-current-user.function.ts index c775883aed..820146e9a3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/current-user/utils/is-current-user.function.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/current-user/utils/is-current-user.function.ts @@ -4,8 +4,16 @@ import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; export const isCurrentUser = async (host: UmbControllerHost, userUnique: string) => { const ctrl = new UmbContextConsumerController(host, UMB_CURRENT_USER_CONTEXT); - const currentUserContext = await ctrl.asPromise(); + let currentUserContext = await ctrl.asPromise(); ctrl.destroy(); + const controller = new UmbContextConsumerController(host, UMB_CURRENT_USER_CONTEXT, (context) => { + currentUserContext = context; + }); + + await controller.asPromise(); + + controller.destroy(); + return await currentUserContext!.isUserCurrentUser(userUnique); }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/repository/user-group-collection.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/repository/user-group-collection.server.data-source.ts index bc4dedf5f7..35d0a587e3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/repository/user-group-collection.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/collection/repository/user-group-collection.server.data-source.ts @@ -45,6 +45,7 @@ export class UmbUserGroupCollectionServerDataSource implements UmbCollectionData documentRootAccess: item.documentRootAccess, mediaStartNode: item.mediaStartNode ? { unique: item.mediaStartNode.id } : null, mediaRootAccess: item.mediaRootAccess, + fallbackPermissions: item.fallbackPermissions, permissions: item.permissions, }; return userGroup; 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 index ad89f8484d..5a679c3bdb 100644 --- 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 @@ -1,7 +1,7 @@ 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 type { ManifestUserPermission } from '@umbraco-cms/backoffice/extension-registry'; +import type { ManifestEntityUserPermission } from '@umbraco-cms/backoffice/extension-registry'; import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; import { map } from '@umbraco-cms/backoffice/external/rxjs'; @@ -25,7 +25,7 @@ export class UmbUserGroupRefElement extends UmbElementMixin(UUIRefNodeElement) { async #observeUserPermissions(value: Array) { if (value) { this.observe( - umbExtensionsRegistry.byType('userPermission').pipe( + umbExtensionsRegistry.byType('entityUserPermission').pipe( map((manifests) => { return manifests.filter((manifest) => manifest.alias && value.includes(manifest.alias)); }), @@ -38,7 +38,7 @@ export class UmbUserGroupRefElement extends UmbElementMixin(UUIRefNodeElement) { } } - #setUserPermissionLabels(manifests: Array) { + #setUserPermissionLabels(manifests: Array) { this.#userPermissionLabels = manifests.map((manifest) => manifest.meta.labelKey ? this.localize.term(manifest.meta.labelKey) : manifest.meta.label ?? '', ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/repository/detail/user-group-detail.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/repository/detail/user-group-detail.server.data-source.ts index 3904964f9d..5a118326e4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/repository/detail/user-group-detail.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/repository/detail/user-group-detail.server.data-source.ts @@ -48,6 +48,7 @@ export class UmbUserGroupServerDataSource implements UmbDetailDataSource; + fallbackPermissions: Array; + // TODO: add type + permissions: Array; } 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-entity-user-permission-list.element.ts similarity index 52% rename from src/Umbraco.Web.UI.Client/src/packages/user/user-group/workspace/components/user-group-default-permission-list.element.ts rename to src/Umbraco.Web.UI.Client/src/packages/user/user-group/workspace/components/user-group-entity-user-permission-list.element.ts index 3c89528ec5..9b30e8c4fa 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-entity-user-permission-list.element.ts @@ -1,14 +1,14 @@ +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UMB_USER_GROUP_WORKSPACE_CONTEXT } from '../user-group-workspace.context.js'; import { html, customElement, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import type { 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 { +@customElement('umb-user-group-entity-user-permission-list') +export class UmbUserGroupEntityUserPermissionListElement extends UmbLitElement { @state() - private _userGroupDefaultPermissions?: Array; + private _fallBackPermissions?: Array; @state() private _entityTypes: Array = []; @@ -18,32 +18,36 @@ export class UmbUserGroupDefaultPermissionListElement extends UmbLitElement { constructor() { super(); - this.#observeUserPermissions(); + this.#observeEntityUserPermissions(); this.consumeContext(UMB_USER_GROUP_WORKSPACE_CONTEXT, (instance) => { this.#userGroupWorkspaceContext = instance; this.observe( - this.#userGroupWorkspaceContext.data, - (userGroup) => (this._userGroupDefaultPermissions = userGroup?.permissions), - 'umbUserGroupPermissionsObserver', + this.#userGroupWorkspaceContext.fallbackPermissions, + (fallbackPermissions) => { + this._fallBackPermissions = fallbackPermissions; + }, + 'umbUserGroupEntityUserPermissionsObserver', ); }); } - #observeUserPermissions() { + #observeEntityUserPermissions() { this.observe( - umbExtensionsRegistry.byType('userPermission'), - (userPermissionManifests) => { - this._entityTypes = [...new Set(userPermissionManifests.map((manifest) => manifest.meta.entityType))]; + umbExtensionsRegistry.byType('entityUserPermission'), + (manifests) => { + this._entityTypes = [...new Set(manifests.map((manifest) => manifest.meta.entityType))]; }, 'umbUserPermissionsObserver', ); } - #onSelectedUserPermission(event: UmbSelectionChangeEvent) { + #onPermissionChange(event: UmbSelectionChangeEvent) { + event.stopPropagation(); const target = event.target as any; - const selection = target.selectedPermissions; - this.#userGroupWorkspaceContext?.setDefaultPermissions(selection); + const verbs = target.allowedVerbs; + if (verbs === undefined || verbs === null) throw new Error('The verbs are not defined'); + this.#userGroupWorkspaceContext?.setFallbackPermissions(verbs); } render() { @@ -53,20 +57,20 @@ export class UmbUserGroupDefaultPermissionListElement extends UmbLitElement { #renderPermissionsByEntityType(entityType: string) { return html`

${entityType}

- + .allowedVerbs=${this._fallBackPermissions || []} + @change=${this.#onPermissionChange}> `; } static styles = [UmbTextStyles]; } -export default UmbUserGroupDefaultPermissionListElement; +export default UmbUserGroupEntityUserPermissionListElement; declare global { interface HTMLElementTagNameMap { - 'umb-user-group-default-permission-list': UmbUserGroupDefaultPermissionListElement; + 'umb-user-group-default-permission-list': UmbUserGroupEntityUserPermissionListElement; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/workspace/components/user-group-granular-permission-list.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/workspace/components/user-group-granular-permission-list.element.ts index 7a60ee742a..d367f19ed0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/workspace/components/user-group-granular-permission-list.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/workspace/components/user-group-granular-permission-list.element.ts @@ -1,12 +1,16 @@ import { UMB_USER_GROUP_WORKSPACE_CONTEXT } from '../user-group-workspace.context.js'; -import type { UmbUserGroupDetailModel } from '../../types.js'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; +import { createExtensionElement } from '@umbraco-cms/backoffice/extension-api'; +import type { ManifestGranularUserPermission } from '@umbraco-cms/backoffice/extension-registry'; +import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; import { html, customElement, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { filterFrozenArray } from '@umbraco-cms/backoffice/observable-api'; @customElement('umb-user-group-granular-permission-list') export class UmbUserGroupGranularPermissionListElement extends UmbLitElement { @state() - private _userGroup?: UmbUserGroupDetailModel; + _extensionElements: Array = []; #workspaceContext?: typeof UMB_USER_GROUP_WORKSPACE_CONTEXT.TYPE; @@ -15,12 +19,90 @@ export class UmbUserGroupGranularPermissionListElement extends UmbLitElement { this.consumeContext(UMB_USER_GROUP_WORKSPACE_CONTEXT, (instance) => { this.#workspaceContext = instance; - this.observe(this.#workspaceContext.data, (userGroup) => (this._userGroup = userGroup), 'umbUserGroupObserver'); + }); + + this.#observeExtensionRegistry(); + } + + #observeExtensionRegistry() { + this.observe(umbExtensionsRegistry.byType('userGranularPermission'), (manifests) => { + if (!manifests) { + this._extensionElements = []; + return; + } + + manifests.forEach(async (manifest) => this.#extensionElementSetup(manifest)); }); } + async #extensionElementSetup(manifest: ManifestGranularUserPermission) { + const element = (await createExtensionElement(manifest)) as any; + if (!element) throw new Error(`Failed to create extension element for manifest ${manifest.alias}`); + if (!this.#workspaceContext) throw new Error('User Group Workspace context is not available'); + + this.observe( + this.#workspaceContext.data, + (userGroup) => { + if (!userGroup) return; + + const schemaType = manifest.meta.schemaType; + const permissionsForSchemaType = + userGroup.permissions.filter((permission) => permission.$type === schemaType) || []; + + element.permissions = permissionsForSchemaType; + element.manifest = manifest; + element.addEventListener(UmbChangeEvent.TYPE, this.#onValueChange); + }, + 'umbUserGroupGranularPermissionObserver', + ); + + this._extensionElements.push(element); + this.requestUpdate('_extensionElements'); + } + + #onValueChange = (e: UmbChangeEvent) => { + e.stopPropagation(); + // TODO: make interface + const target = e.target as any; + const schemaType = target.manifest?.meta.schemaType; + if (!schemaType) throw new Error('Schema type is not available'); + + /* Remove all permissions of the same schema type from + the user group and append the new permissions. + We do it this way to support appends, updates and deletion without we know the + exact action but on the changed value */ + const storedValueWithoutSchemaTypeItems = filterFrozenArray( + this.#workspaceContext?.getPermissions() || [], + (x) => x.$type !== schemaType, + ); + + const permissions = target.permissions || []; + const newCombinedValue = [...storedValueWithoutSchemaTypeItems, ...permissions]; + + this.#workspaceContext?.setPermissions(newCombinedValue); + }; + render() { - return html``; + return html`${this._extensionElements.map((element) => this.#renderProperty(element))}`; + } + + #renderProperty(element: any) { + const manifest = element.manifest as ManifestGranularUserPermission; + const label = manifest.meta.labelKey ? this.localize.term(manifest.meta.labelKey) : manifest.meta.label; + const description = manifest.meta.descriptionKey + ? this.localize.term(manifest.meta.descriptionKey) + : manifest.meta.description; + + return html` + +
${element}
+
+ `; + } + + disconnectedCallback(): void { + this._extensionElements.forEach((element) => element.removeEventListener(UmbChangeEvent.TYPE, this.#onValueChange)); + super.disconnectedCallback(); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/workspace/user-group-workspace-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/workspace/user-group-workspace-editor.element.ts index a3a6b33c02..4f817ae4f3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/workspace/user-group-workspace-editor.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/workspace/user-group-workspace-editor.element.ts @@ -11,7 +11,7 @@ import type { UmbInputSectionElement } from '@umbraco-cms/backoffice/components' import type { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import type { UmbInputMediaElement } from '@umbraco-cms/backoffice/media'; -import './components/user-group-default-permission-list.element.js'; +import './components/user-group-entity-user-permission-list.element.js'; import './components/user-group-granular-permission-list.element.js'; @customElement('umb-user-group-workspace-editor') @@ -123,15 +123,16 @@ export class UmbUserGroupWorkspaceEditorElement extends UmbLitElement {
- + + + +
- `; } 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 cc745b5339..711a5be9d4 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 @@ -1,5 +1,6 @@ import { UmbUserGroupDetailRepository } from '../repository/detail/index.js'; import type { UmbUserGroupDetailModel } from '../types.js'; +import type { UmbUserPermissionModel } from '@umbraco-cms/backoffice/user-permission'; import type { UmbSaveableWorkspaceContextInterface } from '@umbraco-cms/backoffice/workspace'; import { UmbEditableWorkspaceContextBase } from '@umbraco-cms/backoffice/workspace'; import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; @@ -16,6 +17,18 @@ export class UmbUserGroupWorkspaceContext #data = new UmbObjectState(undefined); data = this.#data.asObservable(); + readonly name = this.#data.asObservablePart((data) => data?.name || ''); + readonly icon = this.#data.asObservablePart((data) => data?.icon || null); + readonly sections = this.#data.asObservablePart((data) => data?.sections || []); + readonly languages = this.#data.asObservablePart((data) => data?.languages || []); + readonly hasAccessToAllLanguages = this.#data.asObservablePart((data) => data?.hasAccessToAllLanguages || false); + readonly documentStartNode = this.#data.asObservablePart((data) => data?.documentStartNode || null); + readonly documentRootAccess = this.#data.asObservablePart((data) => data?.documentRootAccess || false); + readonly mediaStartNode = this.#data.asObservablePart((data) => data?.mediaStartNode || null); + readonly mediaRootAccess = this.#data.asObservablePart((data) => data?.mediaRootAccess || false); + readonly fallbackPermissions = this.#data.asObservablePart((data) => data?.fallbackPermissions || []); + readonly permissions = this.#data.asObservablePart((data) => data?.permissions || []); + constructor(host: UmbControllerHost) { super(host, 'Umb.Workspace.UserGroup'); } @@ -82,42 +95,37 @@ export class UmbUserGroupWorkspaceContext } /** - * Sets the user group default permissions. - * @param {Array} permissionAliases + * Gets the user group user permissions. * @memberof UmbUserGroupWorkspaceContext */ - setDefaultPermissions(permissionAliases: Array) { - this.#data.update({ permissions: permissionAliases }); - } - - /** - * Gets the user group default permissions. - * @memberof UmbUserGroupWorkspaceContext - */ - getDefaultPermissions() { + getPermissions() { return this.#data.getValue()?.permissions ?? []; } /** - * Allows a default permission on the user group. - * @param {string} permissionAlias + * Sets the user group user permissions. + * @param {Array} permissions * @memberof UmbUserGroupWorkspaceContext */ - allowDefaultPermission(permissionAlias: string) { - const permissions = this.#data.getValue()?.permissions ?? []; - const newValue = [...permissions, permissionAlias]; - this.#data.update({ permissions: newValue }); + setPermissions(permissions: Array) { + this.#data.update({ permissions: permissions }); } /** - * Disallows a default permission on the user group. - * @param {string} permissionAlias + * Gets the user group fallback permissions. * @memberof UmbUserGroupWorkspaceContext */ - disallowDefaultPermission(permissionAlias: string) { - const permissions = this.#data.getValue()?.permissions ?? []; - const newValue = permissions.filter((alias) => alias !== permissionAlias); - this.#data.update({ permissions: newValue }); + getFallbackPermissions() { + return this.#data.getValue()?.fallbackPermissions ?? []; + } + + /** + * Sets the user group fallback permissions. + * @param {Array} fallbackPermissions + * @memberof UmbUserGroupWorkspaceContext + */ + setFallbackPermissions(fallbackPermissions: Array) { + this.#data.update({ fallbackPermissions }); } } 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 deleted file mode 100644 index cea2f4d471..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user-permission/components/entity-user-permission-settings-list/entity-user-permission-settings-list.element.ts +++ /dev/null @@ -1,110 +0,0 @@ -import type { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; -import { UmbSelectionChangeEvent } from '@umbraco-cms/backoffice/event'; -import type { ManifestUserPermission } from '@umbraco-cms/backoffice/extension-registry'; -import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; -import { html, customElement, property, state, nothing, ifDefined } from '@umbraco-cms/backoffice/external/lit'; -import type { UmbObserverController } from '@umbraco-cms/backoffice/observable-api'; -import type { UmbUserPermissionSettingElement } from '@umbraco-cms/backoffice/user'; -import { UmbLitElement } from '@umbraco-cms/backoffice/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.byType('userPermission'), - (userPermissionManifests) => { - this._manifests = userPermissionManifests.filter((manifest) => manifest.meta.entityType === this.entityType); - }, - 'umbUserPermissionManifestsObserver', - ); - } - - #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) { - // TODO: groupBy is not known by TS yet - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - const groupedPermissions = Object.groupBy( - permissionManifests, - (manifest: ManifestUserPermission) => manifest.meta.group, - ) as Record>; - 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(); - } -} - -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 index 782bc56df3..70730115a2 100644 --- 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 @@ -1,3 +1,5 @@ -import './entity-user-permission-settings-list/entity-user-permission-settings-list.element.js'; +import './input-entity-user-permission/input-entity-user-permission.element.js'; +import './input-user-permission-verb/input-user-permission-verb.element.js'; -export * from './entity-user-permission-settings-list/entity-user-permission-settings-list.element.js'; +export * from './input-entity-user-permission/input-entity-user-permission.element.js'; +export * from './input-user-permission-verb/input-user-permission-verb.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-permission/components/input-entity-user-permission/input-entity-user-permission.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-permission/components/input-entity-user-permission/input-entity-user-permission.element.ts new file mode 100644 index 0000000000..75697d1a24 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-permission/components/input-entity-user-permission/input-entity-user-permission.element.ts @@ -0,0 +1,116 @@ +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; +import type { ManifestEntityUserPermission } from '@umbraco-cms/backoffice/extension-registry'; +import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; +import { html, customElement, property, state, nothing, ifDefined } from '@umbraco-cms/backoffice/external/lit'; +import type { UmbObserverController } from '@umbraco-cms/backoffice/observable-api'; +import type { UmbUserPermissionVerbElement } from '@umbraco-cms/backoffice/user'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui'; + +@customElement('umb-input-entity-user-permission') +export class UmbInputEntityUserPermissionElement extends FormControlMixin(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.#observeEntityUserPermissions(); + } + private _entityType: string = ''; + + @property({ attribute: false }) + allowedVerbs: Array = []; + + @state() + private _manifests: Array = []; + + #manifestObserver?: UmbObserverController>; + + protected getFormElement() { + return undefined; + } + + #isAllowed(permissionVerbs: Array) { + return permissionVerbs.every((verb) => this.allowedVerbs.includes(verb)); + } + + #observeEntityUserPermissions() { + this.#manifestObserver?.destroy(); + + this.#manifestObserver = this.observe( + umbExtensionsRegistry.byType('entityUserPermission'), + (userPermissionManifests) => { + this._manifests = userPermissionManifests.filter((manifest) => manifest.meta.entityType === this.entityType); + }, + 'umbUserPermissionManifestsObserver', + ); + } + + #onChangeUserPermission(event: UmbChangeEvent, permissionVerbs: Array) { + event.stopPropagation(); + const target = event.target as UmbUserPermissionVerbElement; + target.allowed ? this.#addUserPermission(permissionVerbs) : this.#removeUserPermission(permissionVerbs); + } + + #addUserPermission(permissionVerbs: Array) { + const verbs = [...this.allowedVerbs, ...permissionVerbs]; + // ensure we only have unique verbs + this.allowedVerbs = [...new Set(verbs)]; + this.dispatchEvent(new UmbChangeEvent()); + } + + #removeUserPermission(permissionVerbs: Array) { + this.allowedVerbs = this.allowedVerbs.filter((p) => !permissionVerbs.includes(p)); + this.dispatchEvent(new UmbChangeEvent()); + } + + render() { + return html`${this.#renderGroupedPermissions(this._manifests)} `; + } + + #renderGroupedPermissions(permissionManifests: Array) { + // TODO: groupBy is not known by TS yet + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + const groupedPermissions = Object.groupBy( + permissionManifests, + (manifest: ManifestEntityUserPermission) => manifest.meta.group, + ) as Record>; + return html` + ${Object.entries(groupedPermissions).map( + ([group, manifests]) => html` + ${group !== 'undefined' + ? html`
${group}
` + : nothing} + ${manifests.map((manifest) => html` ${this.#renderPermission(manifest)} `)} + `, + )} + `; + } + + #renderPermission(manifest: ManifestEntityUserPermission) { + return html` + this.#onChangeUserPermission(event, manifest.meta.verbs)}>`; + } + + disconnectedCallback() { + super.disconnectedCallback(); + this.#manifestObserver?.destroy(); + } +} + +export default UmbInputEntityUserPermissionElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-input-entity-user-permission': UmbInputEntityUserPermissionElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/components/user-permission-setting/user-permission-setting.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-permission/components/input-user-permission-verb/input-user-permission-verb.element.ts similarity index 86% rename from src/Umbraco.Web.UI.Client/src/packages/user/user/components/user-permission-setting/user-permission-setting.element.ts rename to src/Umbraco.Web.UI.Client/src/packages/user/user-permission/components/input-user-permission-verb/input-user-permission-verb.element.ts index 01c173edbc..a0c7c4c24c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/components/user-permission-setting/user-permission-setting.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-permission/components/input-user-permission-verb/input-user-permission-verb.element.ts @@ -1,11 +1,11 @@ +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import type { UUIBooleanInputEvent } from '@umbraco-cms/backoffice/external/uui'; import { css, html, customElement, property } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; -@customElement('umb-user-permission-setting') -export class UmbUserPermissionSettingElement extends UmbLitElement { +@customElement('umb-input-user-permission-verb') +export class UmbUserPermissionVerbElement extends UmbLitElement { @property({ type: String, attribute: true }) label: string = ''; @@ -54,10 +54,10 @@ export class UmbUserPermissionSettingElement extends UmbLitElement { ]; } -export default UmbUserPermissionSettingElement; +export default UmbUserPermissionVerbElement; declare global { interface HTMLElementTagNameMap { - 'umb-user-permission-setting': UmbUserPermissionSettingElement; + 'umb-input-user-permission-verb': UmbUserPermissionVerbElement; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-permission/conditions/user-permission.condition.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-permission/conditions/user-permission.condition.ts index b5329783c5..ab10e73edc 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user-permission/conditions/user-permission.condition.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-permission/conditions/user-permission.condition.ts @@ -21,7 +21,7 @@ export class UmbUserPermissionCondition extends UmbBaseController implements Umb this.observe( context.currentUser, (currentUser) => { - this.permitted = currentUser?.permissions?.includes(this.config.match) || false; + //this.permitted = currentUser?.permissions?.includes(this.config.match) || false; this.#onChange(); }, 'umbUserPermissionConditionObserver', 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 index 8eed47ad34..33baabfff8 100644 --- 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 @@ -1,15 +1,4 @@ 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; -}; +export type { UmbUserPermissionModel } from './types.js'; 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 index 0eb6f69665..9192d0dce4 100644 --- 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 @@ -1,11 +1,10 @@ -import { html, customElement, css, nothing, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { html, customElement, css, nothing, state } from '@umbraco-cms/backoffice/external/lit'; import type { UmbEntityUserPermissionSettingsModalData, - UmbEntityUserPermissionSettingsModalValue} from '@umbraco-cms/backoffice/modal'; -import { - UmbModalBaseElement, + UmbEntityUserPermissionSettingsModalValue, } from '@umbraco-cms/backoffice/modal'; +import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; import type { UmbSelectionChangeEvent } from '@umbraco-cms/backoffice/event'; @customElement('umb-entity-user-permission-settings-modal') @@ -16,7 +15,6 @@ export class UmbEntityUserPermissionSettingsModalElement extends UmbModalBaseEle set data(data: UmbEntityUserPermissionSettingsModalData | undefined) { super.data = data; this._entityType = data?.entityType; - this._allowedPermissions = data?.allowedPermissions ?? []; this._headline = data?.headline ?? this._headline; } @@ -26,17 +24,9 @@ export class UmbEntityUserPermissionSettingsModalElement extends UmbModalBaseEle @state() _entityType?: string; - @state() - _allowedPermissions: Array = []; - - private _handleConfirm() { - this.value = { allowedPermissions: this._allowedPermissions }; - this.modalContext?.submit(); - } - - #onSelectedUserPermission(event: UmbSelectionChangeEvent) { + #onPermissionChange(event: UmbSelectionChangeEvent) { const target = event.target as any; - this._allowedPermissions = target.selectedPermissions; + this.updateValue({ allowedVerbs: target.allowedVerbs }); } render() { @@ -44,10 +34,10 @@ export class UmbEntityUserPermissionSettingsModalElement extends UmbModalBaseEle ${this._entityType - ? html` ` + .allowedVerbs=${this.value?.allowedVerbs ?? []} + @change=${this.#onPermissionChange}>` : nothing} @@ -58,7 +48,7 @@ export class UmbEntityUserPermissionSettingsModalElement extends UmbModalBaseEle color="positive" look="primary" label="Confirm" - @click=${this._handleConfirm}> + @click=${this._submitModal}> `; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-permission/types.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-permission/types.ts new file mode 100644 index 0000000000..e84687e8bb --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-permission/types.ts @@ -0,0 +1,4 @@ +export interface UmbUserPermissionModel { + $type: string; + verbs: Array; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/collection/repository/user-collection.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/collection/repository/user-collection.server.data-source.ts index 3775ebad50..aeeda6459f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/collection/repository/user-collection.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/collection/repository/user-collection.server.data-source.ts @@ -34,40 +34,36 @@ export class UmbUserCollectionServerDataSource implements UmbCollectionDataSourc async getCollection(filter: UmbUserCollectionFilterModel) { const { data, error } = await tryExecuteAndNotify(this.#host, UserResource.getFilterUser(filter)); - if (error) { - return { error }; + if (data) { + const { items, total } = data; + + const mappedItems: Array = items.map((item: UserResponseModel) => { + const userDetail: UmbUserDetailModel = { + entityType: UMB_USER_ENTITY_TYPE, + email: item.email, + userName: item.userName, + name: item.name, + userGroupUniques: item.userGroupIds, + unique: item.id, + languageIsoCode: item.languageIsoCode || null, + documentStartNodeUniques: item.documentStartNodeIds, + mediaStartNodeUniques: item.mediaStartNodeIds, + avatarUrls: item.avatarUrls, + state: item.state, + failedLoginAttempts: item.failedLoginAttempts, + createDate: item.createDate, + updateDate: item.updateDate, + lastLoginDate: item.lastLoginDate || null, + lastLockoutDate: item.lastLockoutDate || null, + lastPasswordChangeDate: item.lastPasswordChangeDate || null, + }; + + return userDetail; + }); + + return { data: { items: mappedItems, total } }; } - if (!data) { - return { data: { items: [], total: 0 } }; - } - - const { items, total } = data; - - const mappedItems: Array = items.map((item: UserResponseModel) => { - const userDetail: UmbUserDetailModel = { - entityType: UMB_USER_ENTITY_TYPE, - email: item.email, - userName: item.userName, - name: item.name, - userGroupUniques: item.userGroupIds, - unique: item.id, - languageIsoCode: item.languageIsoCode || null, - documentStartNodeUniques: item.documentStartNodeIds, - mediaStartNodeUniques: item.mediaStartNodeIds, - avatarUrls: item.avatarUrls, - state: item.state, - failedLoginAttempts: item.failedLoginAttempts, - createDate: item.createDate, - updateDate: item.updateDate, - lastLoginDate: item.lastLoginDate || null, - lastLockoutDate: item.lastLockoutDate || null, - lastPasswordChangeDate: item.lastPasswordChangeDate || null, - }; - - return userDetail; - }); - - return { data: { items: mappedItems, total } }; + return { error }; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/components/index.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/components/index.ts index 59a54593ec..416c579290 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/components/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/components/index.ts @@ -1,7 +1,6 @@ import './user-input/user-input.element.js'; -import './user-permission-setting/user-permission-setting.element.js'; import './user-document-start-node/user-document-start-node.element.js'; import './user-media-start-node/user-media-start-node.element.js'; export * from './user-input/user-input.element.js'; -export * from './user-permission-setting/user-permission-setting.element.js'; +export * from '../../user-permission/components/input-user-permission-verb/input-user-permission-verb.element.js';