refactoring of property -> context communication

This commit is contained in:
Niels Lyngsø
2023-01-06 20:14:54 +01:00
parent 6c9f17c2a4
commit 0453bd293b
13 changed files with 111 additions and 96 deletions

View File

@@ -31,7 +31,7 @@ export class UmbContentPropertyElement extends UmbLitElement {
}
@property()
value?: object;
value?: object | string;
@state()
private _propertyEditorUIAlias?: string;

View File

@@ -84,6 +84,9 @@ export class UmbPropertyElement extends UmbLitElement {
* @default ''
*/
@property({ type: String })
public get alias(): string {
return this._propertyContext.getAlias() || '';
}
public set alias(alias: string) {
this._propertyContext.setAlias(alias);
}
@@ -108,11 +111,14 @@ export class UmbPropertyElement extends UmbLitElement {
/**
* Property Editor UI Alias. Render the Property Editor UI registered for this alias.
* @public
* @type {object}
* @type {object | string}
* @attr
* @default undefined
*/
@property({ type: Object, attribute: false })
@property({ attribute: false })
public get value() {
return this._propertyContext.getValue() as any;
}
public set value(value: object | string) {
this._propertyContext.setValue(value);
}
@@ -144,7 +150,6 @@ export class UmbPropertyElement extends UmbLitElement {
this.observe(this._propertyContext.label, (label) => {
console.log("_propertyContext replied with label", label)
this._label = label;
});
this.observe(this._propertyContext.label, (description) => {
@@ -152,7 +157,7 @@ export class UmbPropertyElement extends UmbLitElement {
});
// TODO: move event to context. maybe rename to property-editor-value-change.
this.addEventListener('property-editor-change', this._onPropertyEditorChange as any as EventListener);
this.addEventListener('property-editor-value-change', this._onPropertyEditorChange as any as EventListener);
}
@@ -200,9 +205,9 @@ export class UmbPropertyElement extends UmbLitElement {
const target = e.composedPath()[0] as any;
this.value = target.value;
// TODO: update context.
//TODO: Property-Context: Figure out the requirements for this. Cause currently the alias-prop(getter) is required, but its not obvious.
// TODO: Confusing with the little detail of the event name that changed here..
this.dispatchEvent(new CustomEvent('property-value-change', { bubbles: true, composed: true }));
e.stopPropagation();
};

View File

@@ -81,6 +81,9 @@ export class UmbWorkspacePropertyContext<ValueType> {
public setAlias(alias: WorkspacePropertyData<ValueType>['alias']) {
this.update({alias: alias});
}
public getAlias() {
return this._data.getValue().alias;
}
public setLabel(label: WorkspacePropertyData<ValueType>['label']) {
this.update({label: label});
}
@@ -90,6 +93,9 @@ export class UmbWorkspacePropertyContext<ValueType> {
public setValue(value: WorkspacePropertyData<ValueType>['value']) {
this.update({value: value});
}
public getValue() {
return this._data.getValue().value;
}
public setConfig(config: WorkspacePropertyData<ValueType>['config']) {
this.update({config: config});
}

View File

@@ -3,16 +3,16 @@ import { UmbNotificationService } from '../../../../../core/notification';
import { UmbNotificationDefaultData } from '../../../../../core/notification/layouts/default';
import { UmbWorkspaceContext } from '../workspace-context/workspace.context';
import { UmbNodeStoreBase } from '@umbraco-cms/stores/store';
import { ContentTreeItem } from '@umbraco-cms/backend-api';
import { UmbControllerHostInterface } from 'src/core/controller/controller-host.mixin';
import { UmbContextConsumerController } from 'src/core/context-api/consume/context-consumer.controller';
import { UmbObserverController } from '@umbraco-cms/observable-api';
import { UmbContextProviderController } from 'src/core/context-api/provide/context-provider.controller';
import type { ContentDetails } from '@umbraco-cms/models';
// TODO: Consider if its right to have this many class-inheritance of WorkspaceContext
// TODO: Could we extract this code into a 'Manager' of its own, which will be instantiated by the concrete Workspace Context. This will be more transparent and 'reuseable'
export class UmbWorkspaceContentContext<
ContentTypeType extends ContentTreeItem = ContentTreeItem,
ContentTypeType extends ContentDetails = ContentDetails,
StoreType extends UmbNodeStoreBase<ContentTypeType> = UmbNodeStoreBase<ContentTypeType>
> extends UmbWorkspaceContext<ContentTypeType> {
@@ -34,6 +34,10 @@ export class UmbWorkspaceContentContext<
) {
super(host, defaultData);
this.entityType = entityType;
host.addEventListener('property-value-change', this._onPropertyValueChange);
new UmbContextConsumerController(
host,
'umbNotificationService',
@@ -42,15 +46,13 @@ export class UmbWorkspaceContentContext<
}
);
this.entityType = entityType;
new UmbContextConsumerController(host, storeAlias, (_instance: StoreType) => {
this._store = _instance;
if (!this._store) {
// TODO: make sure to break the application in a good way.
return;
}
this._readyToLoad();
this._observeStore();
// TODO: first provide when we have umbNotificationService as well.
new UmbContextProviderController(this._host, 'umbWorkspaceContext', this);
@@ -60,7 +62,7 @@ export class UmbWorkspaceContentContext<
load(entityKey: string) {
this.#isNew = false;
this.entityKey = entityKey;
this._readyToLoad();
this._observeStore();
}
create(parentKey: string | null) {
@@ -69,7 +71,7 @@ export class UmbWorkspaceContentContext<
console.log("I'm new, and I will be created under ", parentKey)
}
protected _readyToLoad(): void {
protected _observeStore(): void {
if(!this._store || !this.entityKey) {
return;
}
@@ -88,6 +90,43 @@ export class UmbWorkspaceContentContext<
return this._store;
}
//TODO: Property-Context: I would like ot investigate how this would work as methods. That do require that a property-context gets the workspace context. But this connection would be more safe.
private _onPropertyValueChange = (e: Event) => {
const target = e.composedPath()[0] as any;
console.log("_onPropertyValueChange context", target.alias, target);
const property = this.getData().data.find((x) => x.alias === target.alias);
if (property) {
this._setPropertyValue(property.alias, target.value);
} else {
console.error('property was not found', target.alias);
}
// We need to stop the event, so it does not bubble up to parent workspace contexts.
e.stopPropagation();
};
private _setPropertyValue(alias: string, value: unknown) {
console.log("about to change prop", this.getData());
const newDataSet = this.getData().data.map((entry) => {
if (entry.alias === alias) {
return {alias: alias, value: value};
}
return entry;
});
const part = {data: newDataSet};
console.log("result", part)
this.update(part as Partial<ContentTypeType>);
}
public save(): Promise<void> {
if(!this._store) {
// TODO: more beautiful error:

View File

@@ -49,54 +49,6 @@ export class UmbWorkspaceContentElement extends UmbLitElement {
@property()
alias!: string;
// TODO: use a NodeDetails type here:
@state()
_content?: ContentTypeTypes;
private _workspaceContext?: UmbWorkspaceContentContext<ContentTypeTypes, UmbNodeStoreBase<ContentTypeTypes>>;
constructor() {
super();
this.consumeContext('umbWorkspaceContext', (instance) => {
this._workspaceContext = instance;
this._observeWorkspace();
});
this.addEventListener('property-value-change', this._onPropertyValueChange);
}
private async _observeWorkspace() {
if (!this._workspaceContext) return;
this.observe(this._workspaceContext.data.pipe(distinctUntilChanged()), (data) => {
this._content = data;
});
}
private _onPropertyValueChange = (e: Event) => {
const target = e.composedPath()[0] as any;
console.log("_onPropertyValueChange", target.alias, target);
// TODO: Set value.
const property = this._content?.properties.find((x) => x.alias === target.alias);
if (property) {
this._setPropertyValue(property.alias, target.value);
} else {
console.error('property was not found', target.alias);
}
};
// TODO: How do we ensure this is a change of this document and not nested documents? Should the event be stopped at this spot at avoid such.
private _setPropertyValue(alias: string, value: unknown) {
this._content?.data.forEach((data) => {
if (data.alias === alias) {
data.value = value;
}
});
}
render() {
return html`
<umb-workspace-layout alias=${this.alias}>

View File

@@ -29,7 +29,7 @@ export class UmbPropertyActionClearElement extends UmbLitElement implements UmbP
private _clearValue() {
// TODO: how do we want to update the value? Testing an event based approach. We need to test an api based approach too.
this.value = '';
this.dispatchEvent(new CustomEvent('property-editor-change', { bubbles: true, composed: true }));
this.dispatchEvent(new CustomEvent('property-editor-value-change', { bubbles: true, composed: true }));
}
render() {

View File

@@ -1,20 +1,22 @@
import { Observable, ReplaySubject } from 'rxjs';
import { BehaviorSubject } from 'rxjs';
import { UmbContextProviderController } from 'src/core/context-api/provide/context-provider.controller';
import { UmbControllerHostInterface } from 'src/core/controller/controller-host.mixin';
import type { UmbControllerHostInterface } from 'src/core/controller/controller-host.mixin';
export class UmbPropertyActionMenuContext {
private _isOpen: ReplaySubject<boolean> = new ReplaySubject(1);
public readonly isOpen: Observable<boolean> = this._isOpen.asObservable();
private _isOpen = new BehaviorSubject(false);
public readonly isOpen = this._isOpen.asObservable();
constructor(host: UmbControllerHostInterface) {
new UmbContextProviderController(host, 'umbPropertyActionMenu', this);
}
toggle() {
this._isOpen.next(!this._isOpen.getValue());
}
open() {
this._isOpen.next(true);
}
close() {
this._isOpen.next(false);
}

View File

@@ -8,6 +8,7 @@ import { umbExtensionsRegistry } from '@umbraco-cms/extensions-registry';
import '../property-action/property-action.element';
import { UmbLitElement } from '@umbraco-cms/element';
import { UmbObserverController } from '@umbraco-cms/observable-api';
@customElement('umb-property-action-menu')
export class UmbPropertyActionMenuElement extends UmbLitElement {
@@ -40,13 +41,19 @@ export class UmbPropertyActionMenuElement extends UmbLitElement {
`,
];
@property()
public propertyEditorUIAlias = '';
// TODO: we need to investigate context api vs values props and events
@property()
public value?: string;
@property()
set propertyEditorUIAlias(alias: string) {
this._observeActions(alias);
}
private _actionsObserver?: UmbObserverController<ManifestPropertyAction[]>;
@state()
private _actions: Array<ManifestPropertyAction> = [];
@@ -55,22 +62,25 @@ export class UmbPropertyActionMenuElement extends UmbLitElement {
private _propertyActionMenuContext = new UmbPropertyActionMenuContext(this);
connectedCallback(): void {
super.connectedCallback();
constructor() {
super();
this._observePropertyActions();
this._observePropertyActionMenuOpenState();
this.observe(this._propertyActionMenuContext.isOpen, (value) => {
this._open = value;
});
}
private _observePropertyActions() {
this.observe(
private _observeActions(alias: string) {
this._actionsObserver?.destroy();
this._actionsObserver = this.observe(
umbExtensionsRegistry
.extensionsOfType('propertyAction')
.pipe(
map((propertyActions) =>
propertyActions.filter((propertyAction) =>
propertyAction.meta.propertyEditors.includes(this.propertyEditorUIAlias)
map((propertyActions) => {
return propertyActions.filter((propertyAction) =>
propertyAction.meta.propertyEditors.includes(alias)
)
}
)
),
(manifests) => {
@@ -79,23 +89,17 @@ export class UmbPropertyActionMenuElement extends UmbLitElement {
);
}
private _observePropertyActionMenuOpenState() {
this.observe(this._propertyActionMenuContext.isOpen, (value) => {
this._open = value;
});
}
private _toggleMenu() {
this._open ? this._propertyActionMenuContext.close() : this._propertyActionMenuContext.open();
this._propertyActionMenuContext.toggle();
}
private _handleClose(event: CustomEvent) {
this._open = false;
this._propertyActionMenuContext.close();
event.stopPropagation();
}
render() {
if (this._actions.length > 0) {
return (this._actions.length > 0) ?
html`
<uui-popover id="popover" placement="bottom-start" .open=${this._open} @close="${this._handleClose}">
<uui-button
@@ -116,8 +120,7 @@ export class UmbPropertyActionMenuElement extends UmbLitElement {
)}
</div>
</uui-popover>
`;
}
return '';
`
: '';
}
}

View File

@@ -90,7 +90,7 @@ export class UmbPropertyEditorUIContentPickerElement extends UmbLitElement {
private _setValue(newValue: Array<string>) {
this.value = newValue;
this._observePickedDocuments();
this.dispatchEvent(new CustomEvent('property-editor-change', { bubbles: true, composed: true }));
this.dispatchEvent(new CustomEvent('property-editor-value-change', { bubbles: true, composed: true }));
}
private _renderItem(item: FolderTreeItem) {

View File

@@ -21,7 +21,7 @@ export class UmbPropertyEditorUINumberElement extends LitElement {
private onInput(e: InputEvent) {
this.value = (e.target as HTMLInputElement).value;
this.dispatchEvent(new CustomEvent('property-editor-change', { bubbles: true, composed: true }));
this.dispatchEvent(new CustomEvent('property-editor-value-change', { bubbles: true, composed: true }));
}
render() {

View File

@@ -21,7 +21,7 @@ export class UmbPropertyEditorUITextBoxElement extends LitElement {
private onInput(e: InputEvent) {
this.value = (e.target as HTMLInputElement).value;
this.dispatchEvent(new CustomEvent('property-editor-change', { bubbles: true, composed: true }));
this.dispatchEvent(new CustomEvent('property-editor-value-change', { bubbles: true, composed: true }));
}
render() {

View File

@@ -36,7 +36,7 @@ export class UmbPropertyEditorUITextareaElement extends UmbLitElement {
private onInput(e: InputEvent) {
this.value = (e.target as HTMLInputElement).value;
this.dispatchEvent(new CustomEvent('property-editor-change', { bubbles: true, composed: true }));
this.dispatchEvent(new CustomEvent('property-editor-value-change', { bubbles: true, composed: true }));
}
render() {

View File

@@ -24,6 +24,14 @@ export interface Entity {
parentKey: string;
}
export interface ContentDetails {
key: string; // TODO: Remove this when the backend is fixed
isTrashed: boolean; // TODO: remove only temp part of refactor
properties: Array<ContentProperty>;
data: Array<ContentPropertyData>;
//layout?: any; // TODO: define layout type - make it non-optional
}
export interface UserEntity extends Entity {
type: 'user';
}
@@ -95,7 +103,7 @@ export interface ContentPropertyData {
}
// Documents
export interface DocumentDetails extends DocumentTreeItem {
export interface DocumentDetails extends DocumentTreeItem, ContentDetails {
key: string; // TODO: Remove this when the backend is fixed
isTrashed: boolean; // TODO: remove only temp part of refactor
properties: Array<ContentProperty>;