Merge remote-tracking branch 'origin/main' into bugfix/media-picker-type

This commit is contained in:
Jacob Overgaard
2024-03-05 18:36:30 +01:00
7 changed files with 596 additions and 115 deletions

View File

@@ -0,0 +1,13 @@
import { UMB_MEMBER_ENTITY_TYPE } from '../entity.js';
import type { UmbMemberPropertyDataContext } from './member-property-dataset-context.js';
import type { UmbPropertyDatasetContext } from '@umbraco-cms/backoffice/property';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
export const IsMemberVariantContext = (context: UmbPropertyDatasetContext): context is UmbMemberPropertyDataContext =>
context.getEntityType() === UMB_MEMBER_ENTITY_TYPE;
export const UMB_MEMBER_VARIANT_CONTEXT = new UmbContextToken<UmbPropertyDatasetContext, UmbMemberPropertyDataContext>(
'UmbVariantContext',
undefined,
IsMemberVariantContext,
);

View File

@@ -0,0 +1,118 @@
import type { UmbMemberWorkspaceContext } from '../workspace/member-workspace.context.js';
import type { UmbNameablePropertyDatasetContext, UmbPropertyDatasetContext } from '@umbraco-cms/backoffice/property';
import { UMB_PROPERTY_DATASET_CONTEXT } from '@umbraco-cms/backoffice/property';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
import { map } from '@umbraco-cms/backoffice/external/rxjs';
import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api';
import type { UmbVariantModel } from '@umbraco-cms/backoffice/variant';
import { UmbVariantId } from '@umbraco-cms/backoffice/variant';
import type { UmbPropertyTypeModel } from '@umbraco-cms/backoffice/content-type';
// TODO: This code can be split into a UmbContentTypePropertyDatasetContext, leaving just the publishing state and methods to this class.
export class UmbMemberPropertyDataContext
extends UmbContextBase<UmbPropertyDatasetContext>
implements UmbPropertyDatasetContext, UmbNameablePropertyDatasetContext
{
#workspace: UmbMemberWorkspaceContext;
#variantId: UmbVariantId;
public getVariantId() {
return this.#variantId;
}
#currentVariant = new UmbObjectState<UmbVariantModel | undefined>(undefined);
currentVariant = this.#currentVariant.asObservable();
name = this.#currentVariant.asObservablePart((x) => x?.name);
culture = this.#currentVariant.asObservablePart((x) => x?.culture);
segment = this.#currentVariant.asObservablePart((x) => x?.segment);
// TODO: Refactor: Make a properties observable. (with such I think i mean a property value object array.. array with object with properties, alias, value, culture and segment)
// TO make such happen I think we need to maintain all properties and their value of this object.
// This will actually make it simpler if multiple are watching the same property.
// But it will also mean that we wil watch all properties and their structure, for variantID, all the time for all of the properties.
getEntityType(): string {
return this.#workspace.getEntityType();
}
getUnique(): string | undefined {
return this.#workspace.getUnique();
}
getName(): string | undefined {
return this.#workspace.getName(this.#variantId);
}
setName(name: string) {
this.#workspace.setName(name, this.#variantId);
}
getVariantInfo() {
return this.#workspace.getVariant(this.#variantId);
}
constructor(host: UmbControllerHost, workspace: UmbMemberWorkspaceContext, variantId?: UmbVariantId) {
// The controller alias, is a very generic name cause we want only one of these for this controller host.
super(host, UMB_PROPERTY_DATASET_CONTEXT);
this.#workspace = workspace;
this.#variantId = variantId ?? UmbVariantId.CreateInvariant();
this.observe(
this.#workspace.variantById(this.#variantId),
async (variantInfo) => {
if (!variantInfo) return;
this.#currentVariant.setValue(variantInfo);
},
'_observeActiveVariant',
);
}
#createPropertyVariantId(property: UmbPropertyTypeModel) {
return UmbVariantId.Create({
culture: property.variesByCulture ? this.#variantId.culture : null,
segment: property.variesBySegment ? this.#variantId.segment : null,
});
}
/**
* TODO: Write proper JSDocs here.
* Ideally do not use these methods, its better to communicate directly with the workspace, but if you do not know the property variant id, then this will figure it out for you. So good for externals to set or get values of a property.
*/
async propertyVariantId(propertyAlias: string) {
return (await this.#workspace.structure.propertyStructureByAlias(propertyAlias)).pipe(
map((property) => (property ? this.#createPropertyVariantId(property) : undefined)),
);
}
/**
* TODO: Write proper JSDocs here.
* Ideally do not use these methods, its better to communicate directly with the workspace, but if you do not know the property variant id, then this will figure it out for you. So good for externals to set or get values of a property.
*/
async propertyValueByAlias<ReturnType = unknown>(propertyAlias: string) {
await this.#workspace.isLoaded();
return (await this.#workspace.structure.propertyStructureByAlias(propertyAlias)).pipe(
map((property) =>
property?.alias
? this.#workspace.getPropertyValue<ReturnType>(property.alias, this.#createPropertyVariantId(property))
: undefined,
),
);
}
// TODO: Refactor: Not used currently, but should investigate if we can implement this, to spare some energy.
async propertyValueByAliasAndCulture<ReturnType = unknown>(propertyAlias: string, propertyVariantId: UmbVariantId) {
return this.#workspace.propertyValueByAlias<ReturnType>(propertyAlias, propertyVariantId);
}
/**
* TODO: Write proper JSDocs here.
* Ideally do not use these methods, its better to communicate directly with the workspace, but if you do not know the property variant id, then this will figure it out for you. So good for externals to set or get values of a property.
*/
async setPropertyValue(propertyAlias: string, value: unknown) {
// This is not reacting to if the property variant settings changes while running.
const property = await this.#workspace.structure.getPropertyStructureByAlias(propertyAlias);
if (property) {
const variantId = this.#createPropertyVariantId(property);
// This is not reacting to if the property variant settings changes while running.
this.#workspace.setPropertyValue(propertyAlias, value, variantId);
}
}
}

View File

@@ -1,5 +1,5 @@
import type { UmbMemberEntityType } from './entity.js';
import type { UmbVariantModel } from '@umbraco-cms/backoffice/variant';
import type { UmbVariantModel, UmbVariantOptionModel } from '@umbraco-cms/backoffice/variant';
export interface UmbMemberDetailModel {
email: string;
@@ -21,9 +21,13 @@ export interface UmbMemberDetailModel {
variants: Array<UmbVariantModel>;
}
export interface UmbMemberVariantModel extends UmbVariantModel {}
export interface UmbMemberValueModel<ValueType = unknown> {
culture: string | null;
segment: string | null;
alias: string;
value: ValueType;
}
export interface UmbMemberVariantOptionModel extends UmbVariantOptionModel<UmbMemberVariantModel> {}

View File

@@ -1,70 +1,111 @@
import type { UmbMemberVariantOptionModel } from '../types.js';
import { UMB_MEMBER_WORKSPACE_CONTEXT } from './member-workspace.context.js';
import { UmbMemberWorkspaceSplitViewElement } from './member-workspace-split-view.element.js';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { css, html, customElement, property, state, nothing } from '@umbraco-cms/backoffice/external/lit';
import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import type { ManifestWorkspace } from '@umbraco-cms/backoffice/extension-registry';
import type { UUIInputElement } from '@umbraco-cms/backoffice/external/uui';
import { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui';
import type { UmbRoute, UmbRouterSlotInitEvent } from '@umbraco-cms/backoffice/router';
@customElement('umb-member-workspace-editor')
export class UmbMemberWorkspaceEditorElement extends UmbLitElement {
@property({ attribute: false })
manifest?: ManifestWorkspace;
//
// TODO: Refactor: when having a split view/variants context token, we can rename the split view/variants component to a generic and make this component generic as well. [NL]
private splitViewElement = new UmbMemberWorkspaceSplitViewElement();
#workspaceContext?: typeof UMB_MEMBER_WORKSPACE_CONTEXT.TYPE;
@state()
private _name: string = '';
@state()
private _unique?: string;
_routes?: Array<UmbRoute>;
constructor() {
super();
this.consumeContext(UMB_MEMBER_WORKSPACE_CONTEXT, (workspaceContext) => {
this.#workspaceContext = workspaceContext;
if (!this.#workspaceContext) return;
this.observe(this.#workspaceContext.name, (name) => (this._name = name ?? ''));
this.observe(this.#workspaceContext.unique, (unique) => (this._unique = unique));
this.consumeContext(UMB_MEMBER_WORKSPACE_CONTEXT, (instance) => {
this.#workspaceContext = instance;
this.#observeVariants();
});
}
#onInput(event: UUIInputEvent) {
if (event instanceof UUIInputEvent) {
const target = event.composedPath()[0] as UUIInputElement;
#observeVariants() {
if (!this.#workspaceContext) return;
// TODO: the variantOptions observable is like too broad as this will be triggered then there is any change in the variant options, we need to only update routes when there is a relevant change to them. [NL]
this.observe(this.#workspaceContext.variantOptions, (options) => this._generateRoutes(options), '_observeVariants');
}
if (typeof target?.value === 'string') {
this.#workspaceContext?.setName(target.value);
}
private _handleVariantFolderPart(index: number, folderPart: string) {
const variantSplit = folderPart.split('_');
const culture = variantSplit[0];
const segment = variantSplit[1];
this.#workspaceContext?.splitView.setActiveVariant(index, culture, segment);
}
private async _generateRoutes(options: Array<UmbMemberVariantOptionModel>) {
if (!options || options.length === 0) return;
// Generate split view routes for all available routes
const routes: Array<UmbRoute> = [];
// Split view routes:
options.forEach((variantA) => {
options.forEach((variantB) => {
routes.push({
// TODO: When implementing Segments, be aware if using the unique is URL Safe... [NL]
path: variantA.unique + '_&_' + variantB.unique,
component: this.splitViewElement,
setup: (_component, info) => {
// Set split view/active info..
const variantSplit = info.match.fragments.consumed.split('_&_');
variantSplit.forEach((part, index) => {
this._handleVariantFolderPart(index, part);
});
},
});
});
});
// Single view:
options.forEach((variant) => {
routes.push({
// TODO: When implementing Segments, be aware if using the unique is URL Safe... [NL]
path: variant.unique,
component: this.splitViewElement,
setup: (_component, info) => {
// cause we might come from a split-view, we need to reset index 1.
this.#workspaceContext?.splitView.removeActiveVariant(1);
this._handleVariantFolderPart(0, info.match.fragments.consumed);
},
});
});
if (routes.length !== 0) {
// Using first single view as the default route for now (hence the math below):
routes.push({
path: '',
redirectTo: routes[options.length * options.length]?.path,
});
}
const oldValue = this._routes;
// is there any differences in the amount ot the paths? [NL]
// TODO: if we make a memorization function as the observer, we can avoid this check and avoid the whole build of routes. [NL]
if (oldValue && oldValue.length === routes.length) {
// is there any differences in the paths? [NL]
const hasDifferences = oldValue.some((route, index) => route.path !== routes[index].path);
if (!hasDifferences) return;
}
this._routes = routes;
this.requestUpdate('_routes', oldValue);
}
#renderBackButton() {
return html`
<uui-button compact href="/section/member-management/view/members">
<uui-icon name="icon-arrow-left"> </uui-icon>
</uui-button>
`;
}
#renderActions() {
// Actions only works if we have a valid unique.
if (!this._unique || this.#workspaceContext?.getIsNew()) return nothing;
return html`<umb-workspace-entity-action-menu slot="action-menu"></umb-workspace-entity-action-menu>`;
}
private _gotWorkspaceRoute = (e: UmbRouterSlotInitEvent) => {
this.#workspaceContext?.splitView.setWorkspaceRoute(e.target.absoluteRouterPath);
};
render() {
return html`
<umb-workspace-editor alias="Umb.Workspace.Member">
${this.#renderActions()}
<div id="header" slot="header">
${this.#renderBackButton()}
<uui-input id="nameInput" .value=${this._name} @input="${this.#onInput}"></uui-input>
</div>
</umb-workspace-editor>
`;
return this._routes && this._routes.length > 0
? html`<umb-router-slot .routes=${this._routes} @init=${this._gotWorkspaceRoute}></umb-router-slot>`
: '';
}
static styles = [
@@ -75,15 +116,6 @@ export class UmbMemberWorkspaceEditorElement extends UmbLitElement {
width: 100%;
height: 100%;
}
#header {
display: flex;
gap: var(--uui-size-space-4);
align-items: center;
width: 100%;
}
uui-input {
width: 100%;
}
`,
];
}

View File

@@ -0,0 +1,87 @@
import { UMB_MEMBER_WORKSPACE_CONTEXT } from './member-workspace.context.js';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { css, html, nothing, customElement, state, repeat } from '@umbraco-cms/backoffice/external/lit';
import type { ActiveVariant } from '@umbraco-cms/backoffice/workspace';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
@customElement('umb-member-workspace-split-view')
export class UmbMemberWorkspaceSplitViewElement extends UmbLitElement {
// TODO: Refactor: use the split view context token:
private _workspaceContext?: typeof UMB_MEMBER_WORKSPACE_CONTEXT.TYPE;
@state()
_variants?: Array<ActiveVariant>;
constructor() {
super();
// TODO: Refactor: use a split view workspace context token:
this.consumeContext(UMB_MEMBER_WORKSPACE_CONTEXT, (context) => {
this._workspaceContext = context;
this._observeActiveVariantInfo();
});
}
private _observeActiveVariantInfo() {
if (!this._workspaceContext) return;
this.observe(
this._workspaceContext.splitView.activeVariantsInfo,
(variants) => {
this._variants = variants;
},
'_observeActiveVariantsInfo',
);
}
render() {
return this._variants
? html`<div id="splitViews">
${repeat(
this._variants,
(view) =>
view.index + '_' + (view.culture ?? '') + '_' + (view.segment ?? '') + '_' + this._variants!.length,
(view) => html`
<umb-workspace-split-view
alias="Umb.Workspace.Member"
.splitViewIndex=${view.index}
.displayNavigation=${view.index === this._variants!.length - 1}></umb-workspace-split-view>
`,
)}
</div>
<umb-workspace-footer alias="Umb.Workspace.Member"></umb-workspace-footer>`
: nothing;
}
static styles = [
UmbTextStyles,
css`
:host {
width: 100%;
height: 100%;
display: flex;
flex: 1;
flex-direction: column;
}
#splitViews {
display: flex;
width: 100%;
height: calc(100% - var(--umb-footer-layout-height));
}
#breadcrumbs {
margin: 0 var(--uui-size-layout-1);
}
`,
];
}
export default UmbMemberWorkspaceSplitViewElement;
declare global {
interface HTMLElementTagNameMap {
'umb-member-workspace-split-view': UmbMemberWorkspaceSplitViewElement;
}
}

View File

@@ -1,42 +1,102 @@
import { UmbMemberDetailRepository } from '../repository/index.js';
import type { UmbMemberDetailModel } from '../types.js';
import type { UmbMemberDetailModel, UmbMemberVariantModel, UmbMemberVariantOptionModel } from '../types.js';
import { UmbMemberPropertyDataContext } from '../property-dataset-context/member-property-dataset-context.js';
import { UMB_MEMBER_WORKSPACE_ALIAS } from './manifests.js';
import { UmbMemberTypeDetailRepository, type UmbMemberTypeDetailModel } from '@umbraco-cms/backoffice/member-type';
import {
type UmbSaveableWorkspaceContextInterface,
UmbEditableWorkspaceContextBase,
import { UmbMemberTypeDetailRepository } from '@umbraco-cms/backoffice/member-type';
import { UmbEditableWorkspaceContextBase, UmbWorkspaceSplitViewManager } from '@umbraco-cms/backoffice/workspace';
import type {
UmbVariantableWorkspaceContextInterface,
UmbSaveableWorkspaceContextInterface,
} from '@umbraco-cms/backoffice/workspace';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
import { UmbObjectState, partialUpdateFrozenArray } from '@umbraco-cms/backoffice/observable-api';
import {
UmbArrayState,
UmbObjectState,
appendToFrozenArray,
mergeObservables,
} from '@umbraco-cms/backoffice/observable-api';
import { UmbContentTypePropertyStructureManager } from '@umbraco-cms/backoffice/content-type';
import type { UmbVariantId } from '@umbraco-cms/backoffice/variant';
import { UMB_INVARIANT_CULTURE, UmbVariantId } from '@umbraco-cms/backoffice/variant';
import type { UmbLanguageDetailModel } from '@umbraco-cms/backoffice/language';
import { UmbLanguageCollectionRepository } from '@umbraco-cms/backoffice/language';
type EntityType = UmbMemberDetailModel;
export class UmbMemberWorkspaceContext
extends UmbEditableWorkspaceContextBase<EntityType>
implements UmbSaveableWorkspaceContextInterface
implements UmbVariantableWorkspaceContextInterface<UmbMemberVariantModel>
{
public readonly repository = new UmbMemberDetailRepository(this);
#persistedData = new UmbObjectState<EntityType | undefined>(undefined);
#currentData = new UmbObjectState<EntityType | undefined>(undefined);
#getDataPromise?: Promise<any>;
// TODo: Optimize this so it uses either a App Language Context? [NL]
#languageRepository = new UmbLanguageCollectionRepository(this);
#languages = new UmbArrayState<UmbLanguageDetailModel>([], (x) => x.unique);
public readonly languages = this.#languages.asObservable();
public isLoaded() {
return this.#getDataPromise;
}
readonly data = this.#currentData.asObservable();
readonly name = this.#currentData.asObservablePart((data) => data?.variants[0].name);
readonly createDate = this.#currentData.asObservablePart((data) => data?.variants[0].createDate);
readonly updateDate = this.#currentData.asObservablePart((data) => data?.variants[0].updateDate);
readonly contentTypeUnique = this.#currentData.asObservablePart((data) => data?.memberType.unique);
readonly structure = new UmbContentTypePropertyStructureManager<UmbMemberTypeDetailModel>(
this,
new UmbMemberTypeDetailRepository(this),
readonly structure = new UmbContentTypePropertyStructureManager(this, new UmbMemberTypeDetailRepository(this));
readonly varies = this.structure.ownerContentTypePart((x) =>
x ? x.variesByCulture || x.variesBySegment : undefined,
);
#varies?: boolean;
readonly variants = this.#currentData.asObservablePart((data) => data?.variants ?? []);
readonly unique = this.#currentData.asObservablePart((data) => data?.unique);
readonly splitView = new UmbWorkspaceSplitViewManager();
readonly variantOptions = mergeObservables(
[this.varies, this.variants, this.languages],
([varies, variants, languages]) => {
// TODO: When including segments, when be aware about the case of segment varying when not culture varying. [NL]
if (varies === true) {
return languages.map((language) => {
return {
variant: variants.find((x) => x.culture === language.unique),
language,
// TODO: When including segments, this object should be updated to include a object for the segment. [NL]
// TODO: When including segments, the unique should be updated to include the segment as well. [NL]
unique: language.unique, // This must be a variantId string!
culture: language.unique,
segment: null,
} as UmbMemberVariantOptionModel;
});
} else if (varies === false) {
return [
{
variant: variants.find((x) => x.culture === null),
language: languages.find((x) => x.isDefault),
culture: null,
segment: null,
unique: UMB_INVARIANT_CULTURE, // This must be a variantId string!
} as UmbMemberVariantOptionModel,
];
}
return [] as Array<UmbMemberVariantOptionModel>;
},
);
constructor(host: UmbControllerHost) {
super(host, UMB_MEMBER_WORKSPACE_ALIAS);
this.observe(this.contentTypeUnique, (unique) => this.structure.loadType(unique));
this.observe(this.varies, (varies) => (this.#varies = varies));
this.loadLanguages();
}
resetState() {
@@ -45,36 +105,185 @@ export class UmbMemberWorkspaceContext
this.#currentData.setValue(undefined);
}
set<PropertyName extends keyof UmbMemberDetailModel>(
propertyName: PropertyName,
value: UmbMemberDetailModel[PropertyName],
) {
this.#currentData.update({ [propertyName]: value });
async loadLanguages() {
// TODO: If we don't end up having a Global Context for languages, then we should at least change this into using a asObservable which should be returned from the repository. [Nl]
const { data } = await this.#languageRepository.requestCollection({});
this.#languages.setValue(data?.items ?? []);
}
async load(unique: string) {
this.resetState();
const { data } = await this.repository.requestByUnique(unique);
if (data) {
this.setIsNew(false);
this.#persistedData.setValue(data);
this.#currentData.setValue(data);
}
this.#getDataPromise = this.repository.requestByUnique(unique);
const { data } = await this.#getDataPromise;
if (!data) return undefined;
this.setIsNew(false);
this.#persistedData.setValue(data);
this.#currentData.setValue(data);
return data || undefined;
}
async create(memberTypeUnique: string) {
this.resetState();
const { data } = await this.repository.createScaffold({
memberType: { unique: memberTypeUnique },
this.#getDataPromise = this.repository.createScaffold({
memberType: {
unique: memberTypeUnique,
},
});
const { data } = await this.#getDataPromise;
if (!data) return undefined;
if (data) {
this.setIsNew(true);
this.#persistedData.setValue(data);
this.#currentData.setValue(data);
this.setIsNew(true);
this.#persistedData.setValue(undefined);
this.#currentData.setValue(data);
return data;
}
getData() {
return this.#currentData.getValue();
}
getUnique() {
return this.getData()?.unique || '';
}
getEntityType() {
return 'member';
}
getContentTypeId() {
return this.getData()?.memberType.unique;
}
// TODO: Check if this is used:
getVaries() {
return this.#varies;
}
variantById(variantId: UmbVariantId) {
return this.#currentData.asObservablePart((data) => data?.variants?.find((x) => variantId.compare(x)));
}
getVariant(variantId: UmbVariantId) {
return this.#currentData.getValue()?.variants?.find((x) => variantId.compare(x));
}
getName(variantId?: UmbVariantId) {
const variants = this.#currentData.getValue()?.variants;
if (!variants) return;
if (variantId) {
return variants.find((x) => variantId.compare(x))?.name;
} else {
return variants[0]?.name;
}
}
return { data };
setName(name: string, variantId?: UmbVariantId) {
/*
const oldVariants = this.#currentData.getValue()?.variants || [];
const variants = partialUpdateFrozenArray(
oldVariants,
{ name },
variantId ? (x) => variantId.compare(x) : () => true,
);
this.#currentData.update({ variants });
*/
// TODO: We should move this type of logic to the act of saving [NL]
this.#updateVariantData(variantId ?? UmbVariantId.CreateInvariant(), { name });
}
async propertyStructureById(propertyId: string) {
return this.structure.propertyStructureById(propertyId);
}
async propertyValueByAlias<PropertyValueType = unknown>(propertyAlias: string, variantId?: UmbVariantId) {
return this.#currentData.asObservablePart(
(data) =>
data?.values?.find((x) => x?.alias === propertyAlias && (variantId ? variantId.compare(x) : true))
?.value as PropertyValueType,
);
}
/**
* Get the current value of the property with the given alias and variantId.
* @param alias
* @param variantId
* @returns The value or undefined if not set or found.
*/
getPropertyValue<ReturnType = unknown>(alias: string, variantId?: UmbVariantId) {
const currentData = this.getData();
if (currentData) {
const newDataSet = currentData.values?.find(
(x) => x.alias === alias && (variantId ? variantId.compare(x) : true),
);
return newDataSet?.value as ReturnType;
}
return undefined;
}
async setPropertyValue<UmbMemberValueModel = unknown>(
alias: string,
value: UmbMemberValueModel,
variantId?: UmbVariantId,
) {
variantId ??= UmbVariantId.CreateInvariant();
const entry = { ...variantId.toObject(), alias, value };
const currentData = this.getData();
if (currentData) {
const values = appendToFrozenArray(
currentData.values || [],
entry,
(x) => x.alias === alias && (variantId ? variantId.compare(x) : true),
);
this.#currentData.update({ values });
// TODO: We should move this type of logic to the act of saving [NL]
this.#updateVariantData(variantId);
}
}
#updateVariantData(variantId: UmbVariantId, update?: Partial<UmbMemberVariantModel>) {
const currentData = this.getData();
if (!currentData) throw new Error('Data is missing');
if (this.#varies === true) {
// If variant Id is invariant, we don't to have the variant appended to our data.
if (variantId.isInvariant()) return;
const variant = currentData.variants.find((x) => variantId.compare(x));
const newVariants = appendToFrozenArray(
currentData.variants,
{
name: '',
createDate: null,
updateDate: null,
...variantId.toObject(),
...variant,
...update,
},
(x) => variantId.compare(x),
);
this.#currentData.update({ variants: newVariants });
} else if (this.#varies === false) {
// TODO: Beware about segments, in this case we need to also consider segments, if its allowed to vary by segments.
const invariantVariantId = UmbVariantId.CreateInvariant();
const variant = currentData.variants.find((x) => invariantVariantId.compare(x));
// Cause we are invariant, we will just overwrite all variants with this one:
const newVariants = [
{
state: null,
name: '',
publishDate: null,
createDate: null,
updateDate: null,
...invariantVariantId.toObject(),
...variant,
...update,
},
];
this.#currentData.update({ variants: newVariants });
} else {
throw new Error('Varies by culture is missing');
}
}
async save() {
@@ -91,6 +300,31 @@ export class UmbMemberWorkspaceContext
this.workspaceComplete(data);
}
async delete() {
const id = this.getUnique();
if (id) {
await this.repository.delete(id);
}
}
public createPropertyDatasetContext(host: UmbControllerHost, variantId: UmbVariantId) {
return new UmbMemberPropertyDataContext(host, this, variantId);
}
public destroy(): void {
this.#currentData.destroy();
super.destroy();
this.#persistedData.destroy();
this.#currentData.destroy();
}
set<PropertyName extends keyof UmbMemberDetailModel>(
propertyName: PropertyName,
value: UmbMemberDetailModel[PropertyName],
) {
this.#currentData.update({ [propertyName]: value });
}
// Only for CRUD demonstration purposes
updateData(data: Partial<EntityType>) {
const currentData = this.#currentData.getValue();
@@ -98,28 +332,6 @@ export class UmbMemberWorkspaceContext
this.#currentData.setValue({ ...currentData, ...data });
}
getData() {
return this.#currentData.getValue();
}
getUnique() {
return this.getData()?.unique || '';
}
getEntityType() {
return 'member';
}
setName(name: string, variantId?: UmbVariantId) {
const oldVariants = this.#currentData.getValue()?.variants || [];
const variants = partialUpdateFrozenArray(
oldVariants,
{ name },
variantId ? (x) => variantId.compare(x) : () => true,
);
this.#currentData.update({ variants });
}
get email() {
return this.#get('email') || '';
}
@@ -159,15 +371,12 @@ export class UmbMemberWorkspaceContext
return new Date(date).toLocaleString();
}
#get<PropertyName extends keyof UmbMemberDetailModel>(propertyName: PropertyName) {
return this.#currentData.getValue()?.[propertyName];
get memberGroups() {
return this.#get('groups') || [];
}
public destroy(): void {
this.#currentData.destroy();
super.destroy();
this.#persistedData.destroy();
this.#currentData.destroy();
#get<PropertyName extends keyof UmbMemberDetailModel>(propertyName: PropertyName) {
return this.#currentData.getValue()?.[propertyName];
}
}

View File

@@ -1,13 +1,14 @@
// import { UMB_COMPOSITION_PICKER_MODAL, type UmbCompositionPickerModalData } from '../../../modals/index.js';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { UMB_MEMBER_WORKSPACE_CONTEXT } from '../../member-workspace.context.js';
import type { UmbMemberDetailModel } from '../../../types.js';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { css, html, customElement, state, when } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import type { UmbWorkspaceViewElement } from '@umbraco-cms/backoffice/extension-registry';
import type { UUIBooleanInputEvent } from '@umbraco-cms/backoffice/external/uui';
import './member-workspace-view-member-info.element.js';
import type { UmbInputMemberGroupElement } from '@umbraco-cms/backoffice/member-group';
@customElement('umb-member-workspace-view-member')
export class UmbMemberWorkspaceViewMemberElement extends UmbLitElement implements UmbWorkspaceViewElement {
@@ -37,9 +38,17 @@ export class UmbMemberWorkspaceViewMemberElement extends UmbLitElement implement
#onChange(propertyName: keyof UmbMemberDetailModel, value: UmbMemberDetailModel[keyof UmbMemberDetailModel]) {
if (!this._workspaceContext) return;
console.log('Setting', propertyName, value);
this._workspaceContext.set(propertyName, value);
}
#onGroupsUpdated(event: CustomEvent) {
const uniques = (event.target as UmbInputMemberGroupElement).selectedIds;
this._workspaceContext?.set('groups', uniques);
}
#onPasswordUpdate = () => {
const newPassword = this.shadowRoot?.querySelector<HTMLInputElement>('uui-input[name="newPassword"]')?.value;
const confirmPassword = this.shadowRoot?.querySelector<HTMLInputElement>('uui-input[name="confirmPassword"]')
@@ -147,7 +156,10 @@ export class UmbMemberWorkspaceViewMemberElement extends UmbLitElement implement
${this.#renderPasswordInput()}
<umb-property-layout label="Member Group">
<div slot="editor">MEMBER GROUP PICKER</div>
<umb-input-member-group
slot="editor"
@change=${this.#onGroupsUpdated}
.selectedIds=${this._workspaceContext.memberGroups}></umb-input-member-group>
</umb-property-layout>
<umb-property-layout label="Approved">
@@ -160,6 +172,7 @@ export class UmbMemberWorkspaceViewMemberElement extends UmbLitElement implement
<umb-property-layout label="Locked out">
<uui-toggle
?disabled=${this._isNew || !this._workspaceContext.isLockedOut}
slot="editor"
.checked=${this._workspaceContext.isLockedOut}
@change=${(e: UUIBooleanInputEvent) => this.#onChange('isLockedOut', e.target.checked)}>
@@ -167,7 +180,12 @@ export class UmbMemberWorkspaceViewMemberElement extends UmbLitElement implement
</umb-property-layout>
<umb-property-layout label="Two-Factor authentication">
<uui-toggle slot="editor"></uui-toggle>
<uui-toggle
?disabled=${this._isNew || !this._workspaceContext.isTwoFactorEnabled}
slot="editor"
.checked=${this._workspaceContext.isTwoFactorEnabled}
@change=${(e: UUIBooleanInputEvent) => this.#onChange('isTwoFactorEnabled', e.target.checked)}>
</uui-toggle>
</umb-property-layout>
</uui-box>
</div>`;