Feature: Content Workspace Icon (#19292)

* add slot for icon

* expose icon data

* render icon

* load type for scaffold

* rename

* render icon for media

* add observable for content type icon

* request data in data source

* wire up document scaffolding

* remove unused

* export server data source

* render icon for member

* rename data source to align with other detail sources

* rename data source

* remove unused styling

* remove console log

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* remove console log

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* remove console log

* Update detail-repository-base.ts

* Update document-workspace-split-view.element.ts

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Mads Rasmussen
2025-05-20 11:12:19 +02:00
committed by GitHub
parent ec6a38e052
commit 7572ea350b
21 changed files with 190 additions and 140 deletions

View File

@@ -1,5 +1,6 @@
import type { UmbDataSourceResponse } from '../data-source-response.interface.js';
import type { UmbReadDetailDataSource } from './read/index.js';
import type { UmbDeepPartialObject } from '@umbraco-cms/backoffice/utils';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
export interface UmbDetailDataSourceConstructor<
@@ -10,7 +11,7 @@ export interface UmbDetailDataSourceConstructor<
}
export interface UmbDetailDataSource<DetailType> extends UmbReadDetailDataSource<DetailType> {
createScaffold(preset?: Partial<DetailType>): Promise<UmbDataSourceResponse<DetailType>>;
createScaffold(preset?: UmbDeepPartialObject<DetailType>): Promise<UmbDataSourceResponse<DetailType>>;
create(data: DetailType, parentUnique: string | null): Promise<UmbDataSourceResponse<DetailType>>;
update(data: DetailType): Promise<UmbDataSourceResponse<DetailType>>;
delete(unique: string): Promise<UmbDataSourceResponse<unknown>>;

View File

@@ -7,6 +7,7 @@ import type { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
import type { UmbDetailStore } from '@umbraco-cms/backoffice/store';
import type { UmbApi } from '@umbraco-cms/backoffice/extension-api';
import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity';
import type { UmbDeepPartialObject } from '@umbraco-cms/backoffice/utils';
export abstract class UmbDetailRepositoryBase<
DetailModelType extends UmbEntityModel,
@@ -44,11 +45,13 @@ export abstract class UmbDetailRepositoryBase<
/**
* Creates a scaffold
* @param {Partial<DetailModelType>} [preset]
* @param {UmbDeepPartialObject<DetailModelType>} [preset]
* @returns {*}
* @memberof UmbDetailRepositoryBase
*/
async createScaffold(preset?: Partial<DetailModelType>): Promise<UmbRepositoryResponse<DetailModelType>> {
async createScaffold(
preset?: UmbDeepPartialObject<DetailModelType>,
): Promise<UmbRepositoryResponse<DetailModelType>> {
return this.detailDataSource.createScaffold(preset);
}

View File

@@ -67,6 +67,7 @@ export class UmbWorkspaceSplitViewElement extends UmbLitElement {
back-path=${ifDefined(this.backPath)}
.hideNavigation=${!this.displayNavigation}
.enforceNoFooter=${true}>
<slot id="icon" name="icon" slot="header"></slot>
<slot id="header" name="variant-selector" slot="header" @slotchange=${this.#onVariantSelectorSlotChanged}>
${when(
!this._variantSelectorSlotHasContent,
@@ -106,6 +107,12 @@ export class UmbWorkspaceSplitViewElement extends UmbLitElement {
flex: 1 1 auto;
display: block;
}
#icon {
display: inline-block;
font-size: var(--uui-size-6);
margin-right: var(--uui-size-space-4);
}
`,
];
}

View File

@@ -1,4 +1,5 @@
import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity';
import type { UmbDeepPartialObject } from '@umbraco-cms/backoffice/utils';
export interface UmbEntityDetailWorkspaceContextArgs {
entityType: string;
@@ -13,5 +14,5 @@ export type UmbEntityWorkspaceContextArgs = UmbEntityDetailWorkspaceContextArgs;
export interface UmbEntityDetailWorkspaceContextCreateArgs<DetailModelType> {
parent: UmbEntityModel;
preset?: Partial<DetailModelType>;
preset?: UmbDeepPartialObject<DetailModelType>;
}

View File

@@ -1 +1,2 @@
export { UmbDocumentTypeDetailRepository } from './document-type-detail.repository.js';
export * from './document-type-detail.server.data-source.js';

View File

@@ -7,69 +7,59 @@ import type {
UpdateDocumentRequestModel,
} from '@umbraco-cms/backoffice/external/backend-api';
import { DocumentService } from '@umbraco-cms/backoffice/external/backend-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { tryExecute } from '@umbraco-cms/backoffice/resources';
import { umbDeepMerge, type UmbDeepPartialObject } from '@umbraco-cms/backoffice/utils';
import type { UmbReferenceByUnique } from '@umbraco-cms/backoffice/models';
import { UmbDocumentTypeDetailServerDataSource } from '@umbraco-cms/backoffice/document-type';
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
/**
* A data source for the Document that fetches data from the server
* @class UmbDocumentServerDataSource
* @implements {RepositoryDetailDataSource}
*/
export class UmbDocumentServerDataSource implements UmbDetailDataSource<UmbDocumentDetailModel> {
#host: UmbControllerHost;
/**
* Creates an instance of UmbDocumentServerDataSource.
* @param {UmbControllerHost} host - The controller host for this controller to be appended to
* @memberof UmbDocumentServerDataSource
*/
constructor(host: UmbControllerHost) {
this.#host = host;
}
export class UmbDocumentServerDataSource
extends UmbControllerBase
implements UmbDetailDataSource<UmbDocumentDetailModel>
{
/**
* Creates a new Document scaffold
* @param preset
* @returns { UmbDocumentDetailModel }
* @memberof UmbDocumentServerDataSource
*/
async createScaffold(preset: Partial<UmbDocumentDetailModel> = {}) {
const data: UmbDocumentDetailModel = {
async createScaffold(preset: UmbDeepPartialObject<UmbDocumentDetailModel> = {}) {
let documentTypeIcon: string | null = null;
let documentTypeCollection: UmbReferenceByUnique | null = null;
const documentTypeUnique = preset.documentType?.unique;
if (!documentTypeUnique) {
throw new Error('Document type unique is missing');
}
const { data } = await new UmbDocumentTypeDetailServerDataSource(this).read(documentTypeUnique);
documentTypeIcon = data?.icon ?? null;
documentTypeCollection = data?.collection ?? null;
const defaultData: UmbDocumentDetailModel = {
entityType: UMB_DOCUMENT_ENTITY_TYPE,
unique: UmbId.new(),
template: null,
documentType: {
unique: '',
collection: null,
icon: null,
unique: documentTypeUnique,
collection: documentTypeCollection,
icon: documentTypeIcon,
},
isTrashed: false,
values: [],
variants: [],
...preset,
};
return { data };
}
const scaffold = umbDeepMerge(defaultData, preset) as UmbDocumentDetailModel;
/**
* Creates a new variant scaffold.
* @returns A new variant scaffold.
*/
/*
// TDOD: remove if not used
createVariantScaffold(): UmbDocumentVariantModel {
return {
state: null,
culture: null,
segment: null,
name: '',
publishDate: null,
createDate: null,
updateDate: null,
};
return { data: scaffold };
}
*/
/**
* Fetches a Document with the given id from the server
@@ -80,7 +70,7 @@ export class UmbDocumentServerDataSource implements UmbDetailDataSource<UmbDocum
async read(unique: string) {
if (!unique) throw new Error('Unique is missing');
const { data, error } = await tryExecute(this.#host, DocumentService.getDocumentById({ path: { id: unique } }));
const { data, error } = await tryExecute(this, DocumentService.getDocumentById({ path: { id: unique } }));
if (error || !data) {
return { error };
@@ -147,7 +137,7 @@ export class UmbDocumentServerDataSource implements UmbDetailDataSource<UmbDocum
};
const { data, error } = await tryExecute(
this.#host,
this,
DocumentService.postDocument({
body: body,
}),
@@ -177,7 +167,7 @@ export class UmbDocumentServerDataSource implements UmbDetailDataSource<UmbDocum
};
const { error } = await tryExecute(
this.#host,
this,
DocumentService.putDocumentById({
path: { id: model.unique },
body: body,
@@ -199,6 +189,6 @@ export class UmbDocumentServerDataSource implements UmbDetailDataSource<UmbDocum
*/
async delete(unique: string) {
if (!unique) throw new Error('Unique is missing');
return tryExecute(this.#host, DocumentService.deleteDocumentById({ path: { id: unique } }));
return tryExecute(this, DocumentService.deleteDocumentById({ path: { id: unique } }));
}
}

View File

@@ -1,6 +1,6 @@
import { UMB_DOCUMENT_WORKSPACE_CONTEXT } from './document-workspace.context-token.js';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { css, html, nothing, customElement, state, repeat } from '@umbraco-cms/backoffice/external/lit';
import { css, html, nothing, customElement, state, repeat, ifDefined } from '@umbraco-cms/backoffice/external/lit';
import type { ActiveVariant } from '@umbraco-cms/backoffice/workspace';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
@@ -14,20 +14,23 @@ export class UmbDocumentWorkspaceSplitViewElement extends UmbLitElement {
@state()
_variants?: Array<ActiveVariant>;
@state()
_icon?: string;
constructor() {
super();
// TODO: Refactor: use a split view workspace context token: [NL]
this.consumeContext(UMB_DOCUMENT_WORKSPACE_CONTEXT, (context) => {
this._workspaceContext = context;
this._observeActiveVariantInfo();
this.#observeActiveVariantInfo();
this.#observeIcon();
});
}
private _observeActiveVariantInfo() {
if (!this._workspaceContext) return;
#observeActiveVariantInfo() {
this.observe(
this._workspaceContext.splitView.activeVariantsInfo,
this._workspaceContext?.splitView.activeVariantsInfo,
(variants) => {
this._variants = variants;
},
@@ -35,6 +38,12 @@ export class UmbDocumentWorkspaceSplitViewElement extends UmbLitElement {
);
}
#observeIcon() {
this.observe(this._workspaceContext?.contentTypeIcon, (icon) => {
this._icon = icon ?? undefined;
});
}
override render() {
return this._variants
? html`<div id="splitViews">
@@ -46,6 +55,7 @@ export class UmbDocumentWorkspaceSplitViewElement extends UmbLitElement {
<umb-workspace-split-view
.splitViewIndex=${view.index}
.displayNavigation=${view.index === this._variants!.length - 1}>
<umb-icon slot="icon" name=${ifDefined(this._icon)}></umb-icon>
<umb-document-workspace-split-view-variant-selector
slot="variant-selector"></umb-document-workspace-split-view-variant-selector>
</umb-workspace-split-view>

View File

@@ -67,6 +67,8 @@ export class UmbDocumentWorkspaceContext
readonly contentTypeHasCollection = this._data.createObservablePartOfCurrent(
(data) => !!data?.documentType.collection,
);
readonly contentTypeIcon = this._data.createObservablePartOfCurrent((data) => data?.documentType.icon || null);
readonly templateId = this._data.createObservablePartOfCurrent((data) => data?.template?.unique || null);
#isTrashedContext = new UmbIsTrashedEntityContext(this);
@@ -303,7 +305,6 @@ export class UmbDocumentWorkspaceContext
preset: {
documentType: {
unique: documentTypeUnique,
collection: null,
},
},
});

View File

@@ -1 +1,2 @@
export { UmbMediaTypeDetailRepository } from './media-type-detail.repository.js';
export * from './media-type-detail.server.data-source.js';

View File

@@ -1,11 +1,11 @@
import type { UmbMediaTypeDetailModel } from '../../types.js';
import { UmbMediaTypeServerDataSource } from './media-type-detail.server.data-source.js';
import { UmbMediaTypeDetailServerDataSource } from './media-type-detail.server.data-source.js';
import { UMB_MEDIA_TYPE_DETAIL_STORE_CONTEXT } from './media-type-detail.store.context-token.js';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbDetailRepositoryBase } from '@umbraco-cms/backoffice/repository';
export class UmbMediaTypeDetailRepository extends UmbDetailRepositoryBase<UmbMediaTypeDetailModel> {
constructor(host: UmbControllerHost) {
super(host, UmbMediaTypeServerDataSource, UMB_MEDIA_TYPE_DETAIL_STORE_CONTEXT);
super(host, UmbMediaTypeDetailServerDataSource, UMB_MEDIA_TYPE_DETAIL_STORE_CONTEXT);
}
}

View File

@@ -13,16 +13,16 @@ import type { UmbPropertyContainerTypes } from '@umbraco-cms/backoffice/content-
/**
* A data source for the Media Type that fetches data from the server
* @class UmbMediaTypeServerDataSource
* @class UmbMediaTypeDetailServerDataSource
* @implements {RepositoryDetailDataSource}
*/
export class UmbMediaTypeServerDataSource implements UmbDetailDataSource<UmbMediaTypeDetailModel> {
export class UmbMediaTypeDetailServerDataSource implements UmbDetailDataSource<UmbMediaTypeDetailModel> {
#host: UmbControllerHost;
/**
* Creates an instance of UmbMediaTypeServerDataSource.
* Creates an instance of UmbMediaTypeDetailServerDataSource.
* @param {UmbControllerHost} host - The controller host for this controller to be appended to
* @memberof UmbMediaTypeServerDataSource
* @memberof UmbMediaTypeDetailServerDataSource
*/
constructor(host: UmbControllerHost) {
this.#host = host;
@@ -32,7 +32,7 @@ export class UmbMediaTypeServerDataSource implements UmbDetailDataSource<UmbMedi
* Creates a new Media Type scaffold
* @param {Partial<UmbMediaTypeDetailModel>} [preset]
* @returns { CreateMediaTypeRequestModel }
* @memberof UmbMediaTypeServerDataSource
* @memberof UmbMediaTypeDetailServerDataSource
*/
async createScaffold(preset: Partial<UmbMediaTypeDetailModel> = {}) {
const data: UmbMediaTypeDetailModel = {
@@ -61,7 +61,7 @@ export class UmbMediaTypeServerDataSource implements UmbDetailDataSource<UmbMedi
* Fetches a Media Type with the given id from the server
* @param {string} unique
* @returns {*}
* @memberof UmbMediaTypeServerDataSource
* @memberof UmbMediaTypeDetailServerDataSource
*/
async read(unique: string) {
if (!unique) throw new Error('Unique is missing');
@@ -132,7 +132,7 @@ export class UmbMediaTypeServerDataSource implements UmbDetailDataSource<UmbMedi
* @param {UmbMediaTypeDetailModel} model
* @param parentUnique
* @returns {*}
* @memberof UmbMediaTypeServerDataSource
* @memberof UmbMediaTypeDetailServerDataSource
*/
async create(model: UmbMediaTypeDetailModel, parentUnique: string | null = null) {
if (!model) throw new Error('Media Type is missing');
@@ -200,7 +200,7 @@ export class UmbMediaTypeServerDataSource implements UmbDetailDataSource<UmbMedi
* @param {UmbMediaTypeDetailModel} MediaType
* @param model
* @returns {*}
* @memberof UmbMediaTypeServerDataSource
* @memberof UmbMediaTypeDetailServerDataSource
*/
async update(model: UmbMediaTypeDetailModel) {
if (!model.unique) throw new Error('Unique is missing');
@@ -265,7 +265,7 @@ export class UmbMediaTypeServerDataSource implements UmbDetailDataSource<UmbMedi
* Deletes a Media Type on the server
* @param {string} unique
* @returns {*}
* @memberof UmbMediaTypeServerDataSource
* @memberof UmbMediaTypeDetailServerDataSource
*/
async delete(unique: string) {
if (!unique) throw new Error('Unique is missing');

View File

@@ -4,40 +4,45 @@ import { UmbId } from '@umbraco-cms/backoffice/id';
import type { UmbDetailDataSource } from '@umbraco-cms/backoffice/repository';
import type { CreateMediaRequestModel, UpdateMediaRequestModel } from '@umbraco-cms/backoffice/external/backend-api';
import { MediaService } from '@umbraco-cms/backoffice/external/backend-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { tryExecute } from '@umbraco-cms/backoffice/resources';
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
import { UmbMediaTypeDetailServerDataSource } from '@umbraco-cms/backoffice/media-type';
import type { UmbReferenceByUnique } from '@umbraco-cms/backoffice/models';
import { umbDeepMerge, type UmbDeepPartialObject } from '@umbraco-cms/backoffice/utils';
/**
* A data source for the Media that fetches data from the server
* @class UmbMediaServerDataSource
* @implements {RepositoryDetailDataSource}
*/
export class UmbMediaServerDataSource implements UmbDetailDataSource<UmbMediaDetailModel> {
#host: UmbControllerHost;
/**
* Creates an instance of UmbMediaServerDataSource.
* @param {UmbControllerHost} host - The controller host for this controller to be appended to
* @memberof UmbMediaServerDataSource
*/
constructor(host: UmbControllerHost) {
this.#host = host;
}
export class UmbMediaServerDataSource extends UmbControllerBase implements UmbDetailDataSource<UmbMediaDetailModel> {
/**
* Creates a new Media scaffold
* @param {Partial<UmbMediaDetailModel>} [preset]
* @param {UmbDeepPartialObject<UmbMediaDetailModel>} [preset]
* @returns { UmbMediaDetailModel }
* @memberof UmbMediaServerDataSource
*/
async createScaffold(preset: Partial<UmbMediaDetailModel> = {}) {
const data: UmbMediaDetailModel = {
async createScaffold(preset: UmbDeepPartialObject<UmbMediaDetailModel> = {}) {
let mediaTypeIcon: string | null = null;
let mediaTypeCollection: UmbReferenceByUnique | null = null;
const mediaTypeUnique = preset.mediaType?.unique;
if (!mediaTypeUnique) {
throw new Error('Media type unique is missing');
}
const { data } = await new UmbMediaTypeDetailServerDataSource(this).read(mediaTypeUnique);
mediaTypeIcon = data?.icon ?? null;
mediaTypeCollection = data?.collection ?? null;
const defaultData: UmbMediaDetailModel = {
entityType: UMB_MEDIA_ENTITY_TYPE,
unique: UmbId.new(),
mediaType: {
unique: '',
collection: null,
icon: null,
unique: mediaTypeUnique,
collection: mediaTypeCollection,
icon: mediaTypeIcon,
},
isTrashed: false,
values: [],
@@ -50,10 +55,11 @@ export class UmbMediaServerDataSource implements UmbDetailDataSource<UmbMediaDet
updateDate: null,
},
],
...preset,
};
return { data };
const scaffold = umbDeepMerge(defaultData, preset) as UmbMediaDetailModel;
return { data: scaffold };
}
/**
@@ -65,7 +71,7 @@ export class UmbMediaServerDataSource implements UmbDetailDataSource<UmbMediaDet
async read(unique: string) {
if (!unique) throw new Error('Unique is missing');
const { data, error } = await tryExecute(this.#host, MediaService.getMediaById({ path: { id: unique } }));
const { data, error } = await tryExecute(this, MediaService.getMediaById({ path: { id: unique } }));
if (error || !data) {
return { error };
@@ -122,7 +128,7 @@ export class UmbMediaServerDataSource implements UmbDetailDataSource<UmbMediaDet
};
const { data, error } = await tryExecute(
this.#host,
this,
MediaService.postMedia({
body,
}),
@@ -152,7 +158,7 @@ export class UmbMediaServerDataSource implements UmbDetailDataSource<UmbMediaDet
};
const { error } = await tryExecute(
this.#host,
this,
MediaService.putMediaById({
path: { id: model.unique },
body,
@@ -175,6 +181,6 @@ export class UmbMediaServerDataSource implements UmbDetailDataSource<UmbMediaDet
async delete(unique: string) {
if (!unique) throw new Error('Unique is missing');
return tryExecute(this.#host, MediaService.deleteMediaById({ path: { id: unique } }));
return tryExecute(this, MediaService.deleteMediaById({ path: { id: unique } }));
}
}

View File

@@ -1,6 +1,6 @@
import { UMB_MEDIA_WORKSPACE_CONTEXT } from './media-workspace.context-token.js';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { css, html, nothing, customElement, state, repeat } from '@umbraco-cms/backoffice/external/lit';
import { css, html, nothing, customElement, state, repeat, ifDefined } from '@umbraco-cms/backoffice/external/lit';
import type { ActiveVariant } from '@umbraco-cms/backoffice/workspace';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
@@ -12,17 +12,21 @@ export class UmbMediaWorkspaceSplitViewElement extends UmbLitElement {
@state()
_variants?: Array<ActiveVariant>;
@state()
_icon?: string;
constructor() {
super();
// TODO: Refactor: use a split view workspace context token:
this.consumeContext(UMB_MEDIA_WORKSPACE_CONTEXT, (context) => {
this._workspaceContext = context;
this._observeActiveVariantInfo();
this.#observeActiveVariantInfo();
this.#observeIcon();
});
}
private _observeActiveVariantInfo() {
#observeActiveVariantInfo() {
if (!this._workspaceContext) return;
this.observe(
this._workspaceContext.splitView.activeVariantsInfo,
@@ -33,6 +37,13 @@ export class UmbMediaWorkspaceSplitViewElement extends UmbLitElement {
);
}
#observeIcon() {
if (!this._workspaceContext) return;
this.observe(this._workspaceContext.contentTypeIcon, (icon) => {
this._icon = icon ?? undefined;
});
}
override render() {
return this._variants
? html`<div id="splitViews">
@@ -43,7 +54,9 @@ export class UmbMediaWorkspaceSplitViewElement extends UmbLitElement {
(view) => html`
<umb-workspace-split-view
.splitViewIndex=${view.index}
.displayNavigation=${view.index === this._variants!.length - 1}></umb-workspace-split-view>
.displayNavigation=${view.index === this._variants!.length - 1}>
<umb-icon slot="icon" name=${ifDefined(this._icon)}></umb-icon>
</umb-workspace-split-view>
`,
)}
</div>

View File

@@ -38,6 +38,7 @@ export class UmbMediaWorkspaceContext
{
readonly contentTypeUnique = this._data.createObservablePartOfCurrent((data) => data?.mediaType.unique);
readonly contentTypeHasCollection = this._data.createObservablePartOfCurrent((data) => !!data?.mediaType.collection);
readonly contentTypeIcon = this._data.createObservablePartOfCurrent((data) => data?.mediaType.icon);
#isTrashedContext = new UmbIsTrashedEntityContext(this);
@@ -117,11 +118,10 @@ export class UmbMediaWorkspaceContext
* @deprecated Use `createScaffold` instead.
*/
public async create(parent: { entityType: string; unique: string | null }, mediaTypeUnique: string) {
const args = {
return this.createScaffold({
parent,
preset: { mediaType: { unique: mediaTypeUnique, collection: null } },
};
return this.createScaffold(args);
preset: { mediaType: { unique: mediaTypeUnique } },
});
}
public getCollectionAlias() {

View File

@@ -1 +1,2 @@
export { UmbMemberTypeDetailRepository } from './member-type-detail.repository.js';
export * from './member-type-detail.server.data-source.js';

View File

@@ -1,5 +1,5 @@
import type { UmbMemberTypeDetailModel } from '../../types.js';
import { UmbMemberTypeServerDataSource } from './member-type-detail.server.data-source.js';
import { UmbMemberTypeDetailServerDataSource } from './member-type-detail.server.data-source.js';
import { UMB_MEMBER_TYPE_DETAIL_STORE_CONTEXT } from './member-type-detail.store.context-token.js';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbDetailRepositoryBase } from '@umbraco-cms/backoffice/repository';
@@ -16,7 +16,7 @@ export class UmbMemberTypeDetailRepository extends UmbDetailRepositoryBase<UmbMe
* @memberof UmbMemberTypeDetailRepository
*/
constructor(host: UmbControllerHost) {
super(host, UmbMemberTypeServerDataSource, UMB_MEMBER_TYPE_DETAIL_STORE_CONTEXT);
super(host, UmbMemberTypeDetailServerDataSource, UMB_MEMBER_TYPE_DETAIL_STORE_CONTEXT);
}
}

View File

@@ -13,16 +13,16 @@ import type { UmbPropertyContainerTypes } from '@umbraco-cms/backoffice/content-
/**
* A data source for the Member Type that fetches data from the server
* @class UmbMemberTypeServerDataSource
* @implements {RepositoryDetailDataSource}
* @class UmbMemberTypeDetailServerDataSource
* @implements {UmbDetailDataSource<UmbMemberTypeDetailModel>}
*/
export class UmbMemberTypeServerDataSource implements UmbDetailDataSource<UmbMemberTypeDetailModel> {
export class UmbMemberTypeDetailServerDataSource implements UmbDetailDataSource<UmbMemberTypeDetailModel> {
#host: UmbControllerHost;
/**
* Creates an instance of UmbMemberTypeServerDataSource.
* Creates an instance of UmbMemberTypeDetailServerDataSource.
* @param {UmbControllerHost} host - The controller host for this controller to be appended to
* @memberof UmbMemberTypeServerDataSource
* @memberof UmbMemberTypeDetailServerDataSource
*/
constructor(host: UmbControllerHost) {
this.#host = host;
@@ -30,9 +30,9 @@ export class UmbMemberTypeServerDataSource implements UmbDetailDataSource<UmbMem
/**
* Creates a new Member Type scaffold
* @param {Partial<UmbMemberTypeDetailModel>} [preset]
* @param {Partial<UmbMemberTypeDetailModel>} [preset] - Optional preset data to merge with the scaffold
* @returns { CreateMemberTypeRequestModel }
* @memberof UmbMemberTypeServerDataSource
* @memberof UmbMemberTypeDetailServerDataSource
*/
async createScaffold(preset: Partial<UmbMemberTypeDetailModel> = {}) {
const data: UmbMemberTypeDetailModel = {
@@ -59,9 +59,9 @@ export class UmbMemberTypeServerDataSource implements UmbDetailDataSource<UmbMem
/**
* Fetches a Member Type with the given id from the server
* @param {string} unique
* @param {string} unique - The unique identifier of the Member Type to fetch
* @returns {*}
* @memberof UmbMemberTypeServerDataSource
* @memberof UmbMemberTypeDetailServerDataSource
*/
async read(unique: string) {
if (!unique) throw new Error('Unique is missing');
@@ -126,9 +126,9 @@ export class UmbMemberTypeServerDataSource implements UmbDetailDataSource<UmbMem
/**
* Inserts a new Member Type on the server
* @param {UmbMemberTypeDetailModel} model
* @param {UmbMemberTypeDetailModel} model - Member Type Model
* @returns {*}
* @memberof UmbMemberTypeServerDataSource
* @memberof UmbMemberTypeDetailServerDataSource
*/
async create(model: UmbMemberTypeDetailModel) {
if (!model) throw new Error('Member Type is missing');
@@ -186,10 +186,9 @@ export class UmbMemberTypeServerDataSource implements UmbDetailDataSource<UmbMem
/**
* Updates a MemberType on the server
* @param {UmbMemberTypeDetailModel} MemberType
* @param model
* @param { UmbMemberTypeDetailModel } model - Member Type Model
* @returns {*}
* @memberof UmbMemberTypeServerDataSource
* @memberof UmbMemberTypeDetailServerDataSource
*/
async update(model: UmbMemberTypeDetailModel) {
if (!model.unique) throw new Error('Unique is missing');
@@ -247,9 +246,9 @@ export class UmbMemberTypeServerDataSource implements UmbDetailDataSource<UmbMem
/**
* Deletes a Member Type on the server
* @param {string} unique
* @param {string} unique - The unique identifier of the Member Type to delete
* @returns {*}
* @memberof UmbMemberTypeServerDataSource
* @memberof UmbMemberTypeDetailServerDataSource
*/
async delete(unique: string) {
if (!unique) throw new Error('Unique is missing');

View File

@@ -1,2 +1,2 @@
export { UmbMemberTypeDetailRepository } from './detail/index.js';
export * from './detail/index.js';
export { UmbMemberTypeItemRepository } from './item/index.js';

View File

@@ -5,26 +5,17 @@ import { UmbId } from '@umbraco-cms/backoffice/id';
import type { UmbDetailDataSource } from '@umbraco-cms/backoffice/repository';
import type { CreateMemberRequestModel, UpdateMemberRequestModel } from '@umbraco-cms/backoffice/external/backend-api';
import { MemberService } from '@umbraco-cms/backoffice/external/backend-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { tryExecute } from '@umbraco-cms/backoffice/resources';
import { umbDeepMerge } from '@umbraco-cms/backoffice/utils';
import { UmbMemberTypeDetailServerDataSource } from '@umbraco-cms/backoffice/member-type';
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
/**
* A data source for the Member that fetches data from the server
* @class UmbMemberServerDataSource
* @implements {RepositoryDetailDataSource}
*/
export class UmbMemberServerDataSource implements UmbDetailDataSource<UmbMemberDetailModel> {
#host: UmbControllerHost;
/**
* Creates an instance of UmbMemberServerDataSource.
* @param {UmbControllerHost} host - The controller host for this controller to be appended to
* @memberof UmbMemberServerDataSource
*/
constructor(host: UmbControllerHost) {
this.#host = host;
}
export class UmbMemberServerDataSource extends UmbControllerBase implements UmbDetailDataSource<UmbMemberDetailModel> {
/**
* Creates a new Member scaffold
* @param {Partial<UmbMemberDetailModel>} [preset]
@@ -32,14 +23,25 @@ export class UmbMemberServerDataSource implements UmbDetailDataSource<UmbMemberD
* @memberof UmbMemberServerDataSource
*/
async createScaffold(preset: Partial<UmbMemberDetailModel> = {}) {
const data: UmbMemberDetailModel = {
let memberTypeIcon = '';
const memberTypeUnique = preset.memberType?.unique;
if (!memberTypeUnique) {
throw new Error('Document type unique is missing');
}
const { data } = await new UmbMemberTypeDetailServerDataSource(this).read(memberTypeUnique);
memberTypeIcon = data?.icon ?? '';
const defaultData: UmbMemberDetailModel = {
entityType: UMB_MEMBER_ENTITY_TYPE,
unique: UmbId.new(),
email: '',
username: '',
memberType: {
unique: '',
icon: '',
unique: memberTypeUnique,
icon: memberTypeIcon,
},
isApproved: false,
isLockedOut: false,
@@ -60,10 +62,11 @@ export class UmbMemberServerDataSource implements UmbDetailDataSource<UmbMemberD
updateDate: new Date().toISOString(),
},
],
...preset,
};
return { data };
const scaffold = umbDeepMerge(defaultData, preset) as UmbMemberDetailModel;
return { data: scaffold };
}
/**
@@ -75,7 +78,7 @@ export class UmbMemberServerDataSource implements UmbDetailDataSource<UmbMemberD
async read(unique: string) {
if (!unique) throw new Error('Unique is missing');
const { data, error } = await tryExecute(this.#host, MemberService.getMemberById({ path: { id: unique } }));
const { data, error } = await tryExecute(this, MemberService.getMemberById({ path: { id: unique } }));
if (error || !data) {
return { error };
@@ -147,7 +150,7 @@ export class UmbMemberServerDataSource implements UmbDetailDataSource<UmbMemberD
};
const { data, error } = await tryExecute(
this.#host,
this,
MemberService.postMember({
body,
}),
@@ -185,7 +188,7 @@ export class UmbMemberServerDataSource implements UmbDetailDataSource<UmbMemberD
};
const { error } = await tryExecute(
this.#host,
this,
MemberService.putMemberById({
path: { id: model.unique },
body,
@@ -209,7 +212,7 @@ export class UmbMemberServerDataSource implements UmbDetailDataSource<UmbMemberD
if (!unique) throw new Error('Unique is missing');
return tryExecute(
this.#host,
this,
MemberService.deleteMemberById({
path: { id: unique },
}),

View File

@@ -1,7 +1,7 @@
import { UMB_MEMBER_ROOT_WORKSPACE_PATH } from '../../paths.js';
import { UMB_MEMBER_WORKSPACE_CONTEXT } from './member-workspace.context-token.js';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { css, html, nothing, customElement, state, repeat } from '@umbraco-cms/backoffice/external/lit';
import { css, html, nothing, customElement, state, repeat, ifDefined } from '@umbraco-cms/backoffice/external/lit';
import type { ActiveVariant } from '@umbraco-cms/backoffice/workspace';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
@@ -13,17 +13,21 @@ export class UmbMemberWorkspaceSplitViewElement extends UmbLitElement {
@state()
_variants?: Array<ActiveVariant>;
@state()
_icon?: string;
constructor() {
super();
// TODO: Refactor: use a split view workspace context token:
this.consumeContext(UMB_MEMBER_WORKSPACE_CONTEXT, (context) => {
this._workspaceContext = context;
this._observeActiveVariantInfo();
this.#observeActiveVariantInfo();
this.#observeIcon();
});
}
private _observeActiveVariantInfo() {
#observeActiveVariantInfo() {
if (!this._workspaceContext) return;
this.observe(
this._workspaceContext.splitView.activeVariantsInfo,
@@ -34,6 +38,13 @@ export class UmbMemberWorkspaceSplitViewElement extends UmbLitElement {
);
}
#observeIcon() {
if (!this._workspaceContext) return;
this.observe(this._workspaceContext.contentTypeIcon, (icon) => {
this._icon = icon ?? undefined;
});
}
override render() {
return this._variants
? html`<div id="splitViews">
@@ -46,6 +57,7 @@ export class UmbMemberWorkspaceSplitViewElement extends UmbLitElement {
back-path=${UMB_MEMBER_ROOT_WORKSPACE_PATH}
.splitViewIndex=${view.index}
.displayNavigation=${view.index === this._variants!.length - 1}>
<umb-icon slot="icon" name=${ifDefined(this._icon)}></umb-icon>
</umb-workspace-split-view>
`,
)}

View File

@@ -28,6 +28,7 @@ export class UmbMemberWorkspaceContext
implements UmbContentWorkspaceContext<ContentModel, ContentTypeModel, UmbMemberVariantModel>
{
readonly contentTypeUnique = this._data.createObservablePartOfCurrent((data) => data?.memberType.unique);
readonly contentTypeIcon = this._data.createObservablePartOfCurrent((data) => data?.memberType.icon);
readonly kind = this._data.createObservablePartOfCurrent((data) => data?.kind);
readonly createDate = this._data.createObservablePartOfCurrent((data) => data?.variants[0].createDate);
readonly updateDate = this._data.createObservablePartOfCurrent((data) => data?.variants[0].updateDate);