diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/index.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/index.ts index a25927e33b..24085f0259 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/index.ts @@ -1 +1,2 @@ +export * from './public-access/index.js'; export * from './culture-and-hostnames/index.js'; 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 19ee0b9fb0..b0b5322c9e 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 @@ -2,11 +2,11 @@ import { UMB_DOCUMENT_DETAIL_REPOSITORY_ALIAS } from '../repository/index.js'; import { UMB_DOCUMENT_ENTITY_TYPE, UMB_DOCUMENT_ROOT_ENTITY_TYPE } from '../entity.js'; import { UmbPublishDocumentEntityAction } from './publish.action.js'; import { UmbCreateDocumentBlueprintEntityAction } from './create-blueprint.action.js'; -import { UmbDocumentPublicAccessEntityAction } from './public-access.action.js'; import { UmbUnpublishDocumentEntityAction } from './unpublish.action.js'; import { UmbRollbackDocumentEntityAction } from './rollback.action.js'; import { manifests as createManifests } from './create/manifests.js'; import { manifests as permissionManifests } from './permissions/manifests.js'; +import { manifests as publicAccessManifests } from './public-access/manifests.js'; import { manifests as cultureAndHostnamesManifests } from './culture-and-hostnames/manifests.js'; import { UmbCopyEntityAction, @@ -18,6 +18,7 @@ import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; const entityActions: Array = [ ...createManifests, ...permissionManifests, + ...publicAccessManifests, ...cultureAndHostnamesManifests, { type: 'entityAction', @@ -71,18 +72,6 @@ const entityActions: Array = [ entityTypes: [UMB_DOCUMENT_ROOT_ENTITY_TYPE, UMB_DOCUMENT_ENTITY_TYPE], }, }, - { - type: 'entityAction', - alias: 'Umb.EntityAction.Document.PublicAccess', - name: 'Document Permissions Entity Action', - api: UmbDocumentPublicAccessEntityAction, - meta: { - icon: 'icon-lock', - label: 'Public Access (TBD)', - repositoryAlias: UMB_DOCUMENT_DETAIL_REPOSITORY_ALIAS, - entityTypes: [UMB_DOCUMENT_ENTITY_TYPE], - }, - }, { type: 'entityAction', alias: 'Umb.EntityAction.Document.Publish', diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/public-access.action.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/public-access.action.ts deleted file mode 100644 index 6aa5793d62..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/public-access.action.ts +++ /dev/null @@ -1,14 +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'; - -export class UmbDocumentPublicAccessEntityAction extends UmbEntityActionBase { - constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string, entityType: string) { - super(host, repositoryAlias, unique, entityType); - } - - async execute() { - console.log(`execute for: ${this.unique}`); - //await this.repository?.setPublicAccess(); - } -} diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/public-access/index.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/public-access/index.ts new file mode 100644 index 0000000000..28d9f38fba --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/public-access/index.ts @@ -0,0 +1 @@ +export * from './modal/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/public-access/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/public-access/manifests.ts new file mode 100644 index 0000000000..bb94e23f2d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/public-access/manifests.ts @@ -0,0 +1,31 @@ +//import { UMB_DOCUMENT_REPOSITORY_ALIAS } from '../../repository/manifests.js'; +import { UMB_DOCUMENT_ENTITY_TYPE } from '../../entity.js'; +import { UMB_DOCUMENT_PUBLIC_ACCESS_REPOSITORY_ALIAS } from './repository/manifests.js'; +import { UmbDocumentPublicAccessEntityAction } from './public-access.action.js'; +import type { ManifestEntityAction, ManifestModal } from '@umbraco-cms/backoffice/extension-registry'; + +const entityActions: Array = [ + { + type: 'entityAction', + alias: 'Umb.EntityAction.Document.PublicAccess', + name: 'Document Permissions Entity Action', + api: UmbDocumentPublicAccessEntityAction, + meta: { + icon: 'icon-lock', + label: 'Restrict Public Access', + repositoryAlias: UMB_DOCUMENT_PUBLIC_ACCESS_REPOSITORY_ALIAS, + entityTypes: [UMB_DOCUMENT_ENTITY_TYPE], + }, + }, +]; + +const manifestModals: Array = [ + { + type: 'modal', + alias: 'Umb.Modal.PublicAccess', + name: 'Public Access Modal', + js: () => import('./modal/public-access-modal.element.js'), + }, +]; + +export const manifests = [...entityActions, ...manifestModals]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/public-access/modal/index.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/public-access/modal/index.ts new file mode 100644 index 0000000000..a7ac646fe2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/public-access/modal/index.ts @@ -0,0 +1,2 @@ +export * from './public-access-modal.token.js'; +export * from './public-access-modal.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/public-access/modal/public-access-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/public-access/modal/public-access-modal.element.ts new file mode 100644 index 0000000000..1438373137 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/public-access/modal/public-access-modal.element.ts @@ -0,0 +1,283 @@ +import { UmbDocumentPublicAccessRepository } from '../repository/public-access.repository.js'; +import { html, customElement, state, css, nothing } from '@umbraco-cms/backoffice/external/lit'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; +import { + UmbDocumentDetailRepository, + type UmbInputDocumentElement, + type UmbPublicAccessModalData, + type UmbPublicAccessModalValue, +} from '@umbraco-cms/backoffice/document'; +import type { UUIRadioEvent } from '@umbraco-cms/backoffice/external/uui'; +import type { PublicAccessRequestModel } from '@umbraco-cms/backoffice/backend-api'; +import type { UmbInputMemberTypeElement } from '@umbraco-cms/backoffice/member-type'; +import type { UmbInputMemberElement } from '@umbraco-cms/backoffice/member'; + +@customElement('umb-public-access-modal') +export class UmbPublicAccessModalElement extends UmbModalBaseElement< + UmbPublicAccessModalData, + UmbPublicAccessModalValue +> { + #publicAccessRepository = new UmbDocumentPublicAccessRepository(this); + #unique?: string; + #isNew: boolean = true; + + @state() + private _documentName = ''; + + @state() + private _specific?: boolean; + + @state() + private _startPage = true; + + @state() + private _selectedIds: Array = []; + + @state() + private _loginPageId?: string; + + @state() + private _errorPageId?: string; + + // Init + + firstUpdated() { + this.#unique = this.data?.unique; + this.#getDocumentName(); + this.#getPublicAccessModel(); + } + + async #getDocumentName() { + if (!this.#unique) return; + // Should this be done here or in the action file? + const { data } = await new UmbDocumentDetailRepository(this).requestByUnique(this.#unique); + if (!data) return; + //TODO How do we ensure we get the correct variant? + this._documentName = data.variants[0]?.name; + } + + async #getPublicAccessModel() { + if (!this.#unique) return; + //const { data } = (await this.#publicAccessRepository.read(this.#unique)); + // TODO Currently returning "void". Remove mock data when API is ready. Will it be Response or Request model? + const data: any = undefined; + /*const data: PublicAccessResponseModel = { + members: [{ name: 'Agent', id: '007' }], + groups: [], + loginPageId: '123', + errorPageId: '456', + };*/ + + if (!data) return; + this.#isNew = false; + this._startPage = false; + + // Specific or Groups + this._specific = data.members.length > 0 ? true : false; + + //SelectedIds + if (data.members.length > 0) { + this._selectedIds = data.members.map((m: any) => m.id); + } else if (data.groups.length > 0) { + this._selectedIds = data.groups.map((g: any) => g.id); + } + + this._loginPageId = data.loginPageId; + this._errorPageId = data.errorPageId; + } + + // Modal events + + #handleNext() { + this._startPage = false; + } + + async #handleSave() { + if (!this._loginPageId || !this._errorPageId || !this.#unique) return; + + const groups = this._specific ? [] : this._selectedIds; + const members = this._specific ? this._selectedIds : []; + + const requestBody: PublicAccessRequestModel = { + memberGroupNames: groups, + memberUserNames: members, + loginPageId: this._loginPageId, + errorPageId: this._errorPageId, + }; + + if (this.#isNew) { + this.#publicAccessRepository.create(this.#unique, requestBody); + } else { + this.#publicAccessRepository.update(this.#unique, requestBody); + } + + this.modalContext?.submit(); + } + + #handleDelete() { + if (!this.#unique) return; + this.#publicAccessRepository.delete(this.#unique); + this.modalContext?.submit(); + } + + #handleCancel() { + this.modalContext?.reject(); + } + + // Change Events + + #onChangeLoginPage(e: CustomEvent) { + this._loginPageId = (e.target as UmbInputDocumentElement).selectedIds[0]; + } + + #onChangeErrorPage(e: CustomEvent) { + this._errorPageId = (e.target as UmbInputDocumentElement).selectedIds[0]; + } + + #onChangeGroup(e: CustomEvent) { + this._selectedIds = (e.target as UmbInputMemberTypeElement).selectedIds; + } + + #onChangeMember(e: CustomEvent) { + this._selectedIds = (e.target as UmbInputMemberElement).selectedIds; + } + + // Renders + + render() { + return html` + + ${this._startPage ? this.renderSelectGroup() : this.renderEditPage()} ${this.renderActions()} + + `; + } + + // First page when no Restricting Public Access is set. + renderSelectGroup() { + return html` + Choose how you want to restrict public access to the page '${this._documentName}'. + + + e.target.value === 'members' ? (this._specific = true) : (this._specific = false)}> + + ${this.localize.term('publicAccess_paMembers')}
+ ${this.localize.term('publicAccess_paMembersHelp')} +
+ + ${this.localize.term('publicAccess_paGroups')}
+ ${this.localize.term('publicAccess_paGroupsHelp')} +
+
`; + } + + // Second page when editing Restricting Public Access + renderEditPage() { + return html`${this.renderMemberType()} +

+ + Select the pages that contain login form and error messages + +

+
+ Login Page + + Choose the page that contains the login form + + +
+
+
+ Error Page + + + Used when people are logged on, but do not have access + + + +
`; + } + + renderMemberType() { + return this._specific + ? html` + Select the members who have access to the page ${this._documentName} + + ` + : html` + Select the groups who have access to the page ${this._documentName} + + `; + } + + // Action buttons + renderActions() { + // Check for Save or Next button + const confirm = !this._startPage + ? html`` + : html``; + // Check for Remove button + const remove = !this.#isNew + ? html`` + : nothing; + //Render the buttons + return html` ${remove}${confirm}`; + } + + static styles = [ + UmbTextStyles, + css` + uui-box, + uui-radio-group { + display: flex; + flex-direction: column; + gap: var(--uui-size-4); + } + uui-radio-group { + margin-top: var(--uui-size-4); + } + + p { + margin: var(--uui-size-6) 0 var(--uui-size-2); + } + small { + display: block; + } + `, + ]; +} + +export default UmbPublicAccessModalElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-public-access-modal': UmbPublicAccessModalElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/public-access/modal/public-access-modal.token.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/public-access/modal/public-access-modal.token.ts new file mode 100644 index 0000000000..92bc0ca089 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/public-access/modal/public-access-modal.token.ts @@ -0,0 +1,17 @@ +import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; + +export interface UmbPublicAccessModalData { + unique: string; +} + +export interface UmbPublicAccessModalValue {} + +export const UMB_PUBLIC_ACCESS_MODAL = new UmbModalToken( + 'Umb.Modal.PublicAccess', + { + modal: { + type: 'sidebar', + size: 'medium', + }, + }, +); diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/public-access/public-access.action.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/public-access/public-access.action.ts new file mode 100644 index 0000000000..2a15deefea --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/public-access/public-access.action.ts @@ -0,0 +1,20 @@ +import { UMB_PUBLIC_ACCESS_MODAL } from './modal/public-access-modal.token.js'; +import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action'; +import { UMB_MODAL_MANAGER_CONTEXT, type UmbModalManagerContext } from '@umbraco-cms/backoffice/modal'; +import type { UmbDocumentDetailRepository } from '@umbraco-cms/backoffice/document'; +import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; + +export class UmbDocumentPublicAccessEntityAction extends UmbEntityActionBase { + #modalContext?: UmbModalManagerContext; + + constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string, entityType: string) { + super(host, repositoryAlias, unique, entityType); + this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => { + this.#modalContext = instance as UmbModalManagerContext; + }); + } + + async execute() { + this.#modalContext?.open(UMB_PUBLIC_ACCESS_MODAL, { data: { unique: this.unique } }); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/public-access/repository/index.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/public-access/repository/index.ts new file mode 100644 index 0000000000..fbf2223dfe --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/public-access/repository/index.ts @@ -0,0 +1,2 @@ +export { UmbDocumentPublicAccessRepository } from './public-access.repository.js'; +export { UMB_DOCUMENT_PUBLIC_ACCESS_REPOSITORY_ALIAS } from './manifests.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/public-access/repository/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/public-access/repository/manifests.ts new file mode 100644 index 0000000000..7f4bdcf839 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/public-access/repository/manifests.ts @@ -0,0 +1,13 @@ +import { UmbDocumentPublicAccessRepository } from '../repository/index.js'; +import type { ManifestRepository } from '@umbraco-cms/backoffice/extension-registry'; + +export const UMB_DOCUMENT_PUBLIC_ACCESS_REPOSITORY_ALIAS = 'Umb.Repository.Document.PublicAccess'; + +const repository: ManifestRepository = { + type: 'repository', + alias: UMB_DOCUMENT_PUBLIC_ACCESS_REPOSITORY_ALIAS, + name: 'Document Public Access Repository', + api: UmbDocumentPublicAccessRepository, +}; + +export const manifests = [repository]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/public-access/repository/public-access.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/public-access/repository/public-access.repository.ts new file mode 100644 index 0000000000..7f19626210 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/public-access/repository/public-access.repository.ts @@ -0,0 +1,65 @@ +import { UmbDocumentPublicAccessServerDataSource } from './public-access.server.data.js'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { UmbBaseController } from '@umbraco-cms/backoffice/class-api'; +import type { UmbNotificationContext } from '@umbraco-cms/backoffice/notification'; +import { UMB_NOTIFICATION_CONTEXT } from '@umbraco-cms/backoffice/notification'; +import type { UmbApi } from '@umbraco-cms/backoffice/extension-api'; +import type { PublicAccessRequestModel } from '@umbraco-cms/backoffice/backend-api'; + +export class UmbDocumentPublicAccessRepository extends UmbBaseController implements UmbApi { + #dataSource: UmbDocumentPublicAccessServerDataSource; + + #notificationContext?: UmbNotificationContext; + + constructor(host: UmbControllerHost) { + super(host); + + this.#dataSource = new UmbDocumentPublicAccessServerDataSource(this); + + this.consumeContext(UMB_NOTIFICATION_CONTEXT, (instance) => { + this.#notificationContext = instance as UmbNotificationContext; + }); + } + + async create(unique: string, data: PublicAccessRequestModel) { + if (!unique) throw new Error('unique is missing'); + if (!data) throw new Error('Data is missing'); + + const { error } = await this.#dataSource.update(unique, data); + if (!error) { + const notification = { data: { message: `Public acccess setting created` } }; + this.#notificationContext?.peek('positive', notification); + } + return { error }; + } + + async read(unique: string) { + if (!unique) throw new Error('unique is missing'); + + const { data, error } = await this.#dataSource.read(unique); + return { data, error }; + } + + async update(unique: string, data: PublicAccessRequestModel) { + if (!unique) throw new Error('unique is missing'); + if (!data) throw new Error('Data is missing'); + + const { error } = await this.#dataSource.update(unique, data); + if (!error) { + const notification = { data: { message: `Public acccess setting updated` } }; + this.#notificationContext?.peek('positive', notification); + } + return { error }; + } + + async delete(unique: string) { + if (!unique) throw new Error('unique is missing'); + + const { error } = await this.#dataSource.delete(unique); + if (!error) { + const notification = { data: { message: `Public acccess setting deleted` } }; + this.#notificationContext?.peek('positive', notification); + } + return { error }; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/public-access/repository/public-access.server.data.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/public-access/repository/public-access.server.data.ts new file mode 100644 index 0000000000..c139e7cbd8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/public-access/repository/public-access.server.data.ts @@ -0,0 +1,68 @@ +import { DocumentResource } from '@umbraco-cms/backoffice/backend-api'; +import type { PublicAccessRequestModel } from '@umbraco-cms/backoffice/backend-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; + +/** + * A data source for the Document Public Access that fetches data from the server + * @export + * @class UmbDocumentPublicAccessServerDataSource + * @implements {RepositoryDetailDataSource} + */ +export class UmbDocumentPublicAccessServerDataSource { + #host: UmbControllerHost; + + /** + * Creates an instance of UmbDocumentPublicAccessServerDataSource. + * @param {UmbControllerHost} host + * @memberof UmbDocumentPublicAccessServerDataSource + */ + constructor(host: UmbControllerHost) { + this.#host = host; + } + + /** + * Creates the Public Access for the given Document unique + * @param {string} unique + * @param {PublicAccessRequestModel} data + * @memberof UmbDocumentPublicAccessServerDataSource + */ + async create(unique: string, data: PublicAccessRequestModel) { + if (!unique) throw new Error('unique is missing'); + return tryExecuteAndNotify( + this.#host, + DocumentResource.postDocumentByIdPublicAccess({ id: unique, requestBody: data }), + ); + } + + /** + * Fetches the Public Access for the given Document unique + * @param {string} unique + * @memberof UmbDocumentPublicAccessServerDataSource + */ + async read(unique: string) { + if (!unique) throw new Error('unique is missing'); + return tryExecuteAndNotify(this.#host, DocumentResource.getDocumentByIdPublicAccess({ id: unique })); + } + + /** + * Updates Public Access for the given Document unique + * @param {string} unique + * @param {PublicAccessRequestModel} data + * @memberof UmbDocumentPublicAccessServerDataSource + */ + async update(unique: string, requestBody: PublicAccessRequestModel) { + if (!unique) throw new Error('unique is missing'); + return tryExecuteAndNotify(this.#host, DocumentResource.putDocumentByIdPublicAccess({ id: unique, requestBody })); + } + + /** + * Deletes Public Access for the given Document unique + * @param {string} unique + * @memberof UmbDocumentPublicAccessServerDataSource + */ + async delete(unique: string) { + if (!unique) throw new Error('unique is missing'); + return tryExecuteAndNotify(this.#host, DocumentResource.deleteDocumentByIdPublicAccess({ id: unique })); + } +}