Merge branch 'main' into feature/sort-children-of-entity-action

This commit is contained in:
Mads Rasmussen
2024-04-02 13:51:14 +02:00
committed by GitHub
49 changed files with 422 additions and 281 deletions

View File

@@ -28,7 +28,7 @@ export const UmbClassMixin = <T extends ClassConstructor<EventTarget>>(superClas
super();
this._host = host;
this._controllerAlias = controllerAlias ?? Symbol(); // This will fallback to a Symbol, ensuring that this class is only appended to the controller host once.
this._host.addController(this);
this._host.addUmbController(this);
}
getHostElement(): Element {
@@ -68,7 +68,7 @@ export const UmbClassMixin = <T extends ClassConstructor<EventTarget>>(superClas
) as unknown as SpecificR;
} else {
callback(undefined as SpecificT);
this.removeControllerByAlias(controllerAlias);
this.removeUmbControllerByAlias(controllerAlias);
return undefined as SpecificR;
}
}
@@ -104,7 +104,7 @@ export const UmbClassMixin = <T extends ClassConstructor<EventTarget>>(superClas
public destroy(): void {
if (this._host) {
this._host.removeController(this);
this._host.removeUmbController(this);
this._host = undefined as never;
}
super.destroy();

View File

@@ -21,11 +21,11 @@ export class UmbContextConsumerController<BaseType = unknown, ResultType extends
) {
super(host.getHostElement(), contextAlias, callback);
this.#host = host;
host.addController(this);
host.addUmbController(this);
}
public destroy(): void {
this.#host?.removeController(this);
this.#host?.removeUmbController(this);
(this.#host as any) = undefined;
super.destroy();
}

View File

@@ -60,7 +60,7 @@ describe('UmbContextProviderController', () => {
}).to.throw();
// Still has the initial controller:
expect(element.hasController(provider)).to.be.true;
expect(element.hasUmbController(provider)).to.be.true;
// The secondCtrl was never set as a result of the creation failing:
expect(secondCtrl).to.be.undefined;
});

View File

@@ -29,7 +29,7 @@ export class UmbContextProviderController<
this.#controllerAlias = contextAlias.toString() + '_' + (instance as any).constructor?.name;
// If this API is already provided with this alias? Then we do not want to register this controller:
const existingControllers = host.getControllers((x) => x.controllerAlias === this.controllerAlias);
const existingControllers = host.getUmbControllers((x) => x.controllerAlias === this.controllerAlias);
if (
existingControllers.length > 0 &&
(existingControllers[0] as UmbContextProviderController).providerInstance?.() === instance
@@ -42,13 +42,13 @@ export class UmbContextProviderController<
}' is already provided by another Context Provider Controller.`,
);
} else {
host.addController(this);
host.addUmbController(this);
}
}
public destroy(): void {
if (this.#host) {
this.#host.removeController(this);
this.#host.removeUmbController(this);
(this.#host as any) = undefined;
}
super.destroy();

View File

@@ -3,11 +3,11 @@ import type { UmbControllerHost } from './controller-host.interface.js';
import type { UmbController } from './controller.interface.js';
export interface UmbControllerHostElement extends HTMLElement, UmbControllerHost {
hasController(controller: UmbController): boolean;
getControllers(filterMethod: (ctrl: UmbController) => boolean): UmbController[];
addController(controller: UmbController): void;
removeControllerByAlias(alias: UmbControllerAlias): void;
removeController(controller: UmbController): void;
hasUmbController(controller: UmbController): boolean;
getUmbControllers(filterMethod: (ctrl: UmbController) => boolean): UmbController[];
addUmbController(controller: UmbController): void;
removeUmbControllerByAlias(alias: UmbControllerAlias): void;
removeUmbController(controller: UmbController): void;
getHostElement(): Element;
destroy(): void;

View File

@@ -1,10 +1,10 @@
import type { UmbController } from './controller.interface.js';
export interface UmbControllerHost {
hasController(controller: UmbController): boolean;
getControllers(filterMethod: (ctrl: UmbController) => boolean): UmbController[];
addController(controller: UmbController): void;
removeControllerByAlias(unique: UmbController['controllerAlias']): void;
removeController(controller: UmbController): void;
hasUmbController(controller: UmbController): boolean;
getUmbControllers(filterMethod: (ctrl: UmbController) => boolean): UmbController[];
addUmbController(controller: UmbController): void;
removeUmbControllerByAlias(unique: UmbController['controllerAlias']): void;
removeUmbController(controller: UmbController): void;
getHostElement(): Element;
}

View File

@@ -29,7 +29,7 @@ export const UmbControllerHostMixin = <T extends ClassConstructor>(superClass: T
* Tests if a controller is assigned to this element.
* @param {UmbController} ctrl
*/
hasController(ctrl: UmbController): boolean {
hasUmbController(ctrl: UmbController): boolean {
return this.#controllers.indexOf(ctrl) !== -1;
}
@@ -37,7 +37,7 @@ export const UmbControllerHostMixin = <T extends ClassConstructor>(superClass: T
* Retrieve controllers matching a filter of this element.
* @param {method} filterMethod
*/
getControllers(filterMethod: (ctrl: UmbController) => boolean): UmbController[] {
getUmbControllers(filterMethod: (ctrl: UmbController) => boolean): UmbController[] {
return this.#controllers.filter(filterMethod);
}
@@ -45,14 +45,14 @@ export const UmbControllerHostMixin = <T extends ClassConstructor>(superClass: T
* Append a controller to this element.
* @param {UmbController} ctrl
*/
addController(ctrl: UmbController): void {
addUmbController(ctrl: UmbController): void {
// If this specific class is already added, then skip out.
if (this.#controllers.indexOf(ctrl) !== -1) {
return;
}
// Check if there is one already with same unique
this.removeControllerByAlias(ctrl.controllerAlias);
this.removeUmbControllerByAlias(ctrl.controllerAlias);
this.#controllers.push(ctrl);
if (this.#attached) {
@@ -71,7 +71,7 @@ export const UmbControllerHostMixin = <T extends ClassConstructor>(superClass: T
* Notice this will also destroy the controller.
* @param {UmbController} ctrl
*/
removeController(ctrl: UmbController): void {
removeUmbController(ctrl: UmbController): void {
const index = this.#controllers.indexOf(ctrl);
if (index !== -1) {
this.#controllers.splice(index, 1);
@@ -87,11 +87,11 @@ export const UmbControllerHostMixin = <T extends ClassConstructor>(superClass: T
* Notice this will also destroy the controller.
* @param {string | symbol} controllerAlias
*/
removeControllerByAlias(controllerAlias: UmbController['controllerAlias']): void {
removeUmbControllerByAlias(controllerAlias: UmbController['controllerAlias']): void {
if (controllerAlias) {
this.#controllers.forEach((x) => {
if (x.controllerAlias === controllerAlias) {
this.removeController(x);
this.removeUmbController(x);
}
});
}

View File

@@ -20,7 +20,7 @@ class UmbTestControllerImplementation extends UmbControllerHostMixin(class {}) {
super();
this._host = host;
this.controllerAlias = controllerAlias ?? Symbol(); // This will fallback to a Symbol, ensuring that this class is only appended to the controller host once.
this._host.addController(this);
this._host.addUmbController(this);
}
getHostElement() {
@@ -39,7 +39,7 @@ class UmbTestControllerImplementation extends UmbControllerHostMixin(class {}) {
public destroy(): void {
if (this._host) {
this._host.removeController(this);
this._host.removeUmbController(this);
this._host = undefined as any;
}
super.destroy();
@@ -56,20 +56,20 @@ describe('UmbController', () => {
describe('Controller Host Public API', () => {
describe('methods', () => {
it('has an hasController method', () => {
expect(hostElement).to.have.property('hasController').that.is.a('function');
it('has an hasUmbController method', () => {
expect(hostElement).to.have.property('hasUmbController').that.is.a('function');
});
it('has an getControllers method', () => {
expect(hostElement).to.have.property('getControllers').that.is.a('function');
it('has an getUmbControllers method', () => {
expect(hostElement).to.have.property('getUmbControllers').that.is.a('function');
});
it('has an addController method', () => {
expect(hostElement).to.have.property('addController').that.is.a('function');
it('has an addUmbController method', () => {
expect(hostElement).to.have.property('addUmbController').that.is.a('function');
});
it('has an removeControllerByAlias method', () => {
expect(hostElement).to.have.property('removeControllerByAlias').that.is.a('function');
it('has an removeUmbControllerByAlias method', () => {
expect(hostElement).to.have.property('removeUmbControllerByAlias').that.is.a('function');
});
it('has an removeController method', () => {
expect(hostElement).to.have.property('removeController').that.is.a('function');
it('has an removeUmbController method', () => {
expect(hostElement).to.have.property('removeUmbController').that.is.a('function');
});
it('has an hostConnected method', () => {
expect(hostElement).to.have.property('hostConnected').that.is.a('function');
@@ -94,20 +94,20 @@ describe('UmbController', () => {
expect(controller).to.have.property('controllerAlias').that.is.a('string');
expect(controller.controllerAlias).to.be.equal('my-test-controller-alias');
});
it('has an hasController method', () => {
expect(controller).to.have.property('hasController').that.is.a('function');
it('has an hasUmbController method', () => {
expect(controller).to.have.property('hasUmbController').that.is.a('function');
});
it('has an getControllers method', () => {
expect(controller).to.have.property('getControllers').that.is.a('function');
it('has an getUmbControllers method', () => {
expect(controller).to.have.property('getUmbControllers').that.is.a('function');
});
it('has an addController method', () => {
expect(controller).to.have.property('addController').that.is.a('function');
it('has an addUmbController method', () => {
expect(controller).to.have.property('addUmbController').that.is.a('function');
});
it('has an removeControllerByAlias method', () => {
expect(controller).to.have.property('removeControllerByAlias').that.is.a('function');
it('has an removeUmbControllerByAlias method', () => {
expect(controller).to.have.property('removeUmbControllerByAlias').that.is.a('function');
});
it('has an removeController method', () => {
expect(controller).to.have.property('removeController').that.is.a('function');
it('has an removeUmbController method', () => {
expect(controller).to.have.property('removeUmbController').that.is.a('function');
});
it('has an hostConnected method', () => {
expect(controller).to.have.property('hostConnected').that.is.a('function');
@@ -126,14 +126,14 @@ describe('UmbController', () => {
const ctrl = new UmbTestControllerImplementation(hostElement);
// The host does own a reference to its controller:
expect(hostElement.hasController(ctrl)).to.be.true;
expect(hostElement.hasUmbController(ctrl)).to.be.true;
// The controller does own a reference to its host:
expect(hostElement.getHostElement()).to.be.equal(hostElement);
ctrl.destroy();
// The relation is removed:
expect(hostElement.hasController(ctrl)).to.be.false;
expect(hostElement.hasUmbController(ctrl)).to.be.false;
expect(ctrl.getHostElement()).to.be.undefined;
expect(ctrl.testIsConnected).to.be.false;
expect(ctrl.testIsDestroyed).to.be.true;
@@ -143,15 +143,15 @@ describe('UmbController', () => {
const ctrl = new UmbTestControllerImplementation(hostElement);
const subCtrl = new UmbTestControllerImplementation(ctrl);
expect(hostElement.hasController(ctrl)).to.be.true;
expect(ctrl.hasController(subCtrl)).to.be.true;
expect(hostElement.hasUmbController(ctrl)).to.be.true;
expect(ctrl.hasUmbController(subCtrl)).to.be.true;
ctrl.destroy();
expect(hostElement.hasController(ctrl)).to.be.false;
expect(hostElement.hasUmbController(ctrl)).to.be.false;
expect(ctrl.testIsConnected).to.be.false;
expect(ctrl.testIsDestroyed).to.be.true;
expect(ctrl.hasController(subCtrl)).to.be.false;
expect(ctrl.hasUmbController(subCtrl)).to.be.false;
expect(subCtrl.testIsConnected).to.be.false;
expect(subCtrl.testIsDestroyed).to.be.true;
});
@@ -162,15 +162,15 @@ describe('UmbController', () => {
expect(ctrl.testIsDestroyed).to.be.false;
expect(subCtrl.testIsDestroyed).to.be.false;
expect(hostElement.hasController(ctrl)).to.be.true;
expect(ctrl.hasController(subCtrl)).to.be.true;
expect(hostElement.hasUmbController(ctrl)).to.be.true;
expect(ctrl.hasUmbController(subCtrl)).to.be.true;
hostElement.removeController(ctrl);
hostElement.removeUmbController(ctrl);
expect(ctrl.testIsDestroyed).to.be.true;
expect(hostElement.hasController(ctrl)).to.be.false;
expect(hostElement.hasUmbController(ctrl)).to.be.false;
expect(subCtrl.testIsDestroyed).to.be.true;
expect(ctrl.hasController(subCtrl)).to.be.false;
expect(ctrl.hasUmbController(subCtrl)).to.be.false;
});
it('all controllers are destroyed when the hosting controller gets destroyed', () => {
@@ -181,40 +181,40 @@ describe('UmbController', () => {
const subSubCtrl2 = new UmbTestControllerImplementation(subCtrl);
expect(ctrl.testIsDestroyed).to.be.false;
expect(hostElement.hasController(ctrl)).to.be.true;
expect(hostElement.hasUmbController(ctrl)).to.be.true;
// Subs:
expect(subCtrl.testIsDestroyed).to.be.false;
expect(subCtrl2.testIsDestroyed).to.be.false;
expect(ctrl.hasController(subCtrl)).to.be.true;
expect(ctrl.hasController(subCtrl2)).to.be.true;
expect(ctrl.hasUmbController(subCtrl)).to.be.true;
expect(ctrl.hasUmbController(subCtrl2)).to.be.true;
// Sub subs:
expect(subSubCtrl1.testIsDestroyed).to.be.false;
expect(subSubCtrl2.testIsDestroyed).to.be.false;
expect(subCtrl.hasController(subSubCtrl1)).to.be.true;
expect(subCtrl.hasController(subSubCtrl2)).to.be.true;
expect(subCtrl.hasUmbController(subSubCtrl1)).to.be.true;
expect(subCtrl.hasUmbController(subSubCtrl2)).to.be.true;
ctrl.destroy();
expect(ctrl.testIsDestroyed).to.be.true;
expect(hostElement.hasController(ctrl)).to.be.false;
expect(hostElement.hasUmbController(ctrl)).to.be.false;
// Subs:
expect(subCtrl.testIsDestroyed).to.be.true;
expect(subCtrl2.testIsDestroyed).to.be.true;
expect(ctrl.hasController(subCtrl)).to.be.false;
expect(ctrl.hasController(subCtrl2)).to.be.false;
expect(ctrl.hasUmbController(subCtrl)).to.be.false;
expect(ctrl.hasUmbController(subCtrl2)).to.be.false;
// Sub subs:
expect(subSubCtrl1.testIsDestroyed).to.be.true;
expect(subSubCtrl2.testIsDestroyed).to.be.true;
expect(subCtrl.hasController(subSubCtrl1)).to.be.false;
expect(subCtrl.hasController(subSubCtrl2)).to.be.false;
expect(subCtrl.hasUmbController(subSubCtrl1)).to.be.false;
expect(subCtrl.hasUmbController(subSubCtrl2)).to.be.false;
});
it('hostConnected & hostDisconnected is triggered accordingly to the state of the controller host.', () => {
const ctrl = new UmbTestControllerImplementation(hostElement);
const subCtrl = new UmbTestControllerImplementation(ctrl);
expect(hostElement.hasController(ctrl)).to.be.true;
expect(ctrl.hasController(subCtrl)).to.be.true;
expect(hostElement.hasUmbController(ctrl)).to.be.true;
expect(ctrl.hasUmbController(subCtrl)).to.be.true;
expect(ctrl.testIsConnected).to.be.false;
expect(subCtrl.testIsConnected).to.be.false;
@@ -235,8 +235,8 @@ describe('UmbController', () => {
const ctrl = new UmbTestControllerImplementation(hostElement);
const subCtrl = new UmbTestControllerImplementation(ctrl);
expect(hostElement.hasController(ctrl)).to.be.true;
expect(ctrl.hasController(subCtrl)).to.be.true;
expect(hostElement.hasUmbController(ctrl)).to.be.true;
expect(ctrl.hasUmbController(subCtrl)).to.be.true;
expect(ctrl.testIsConnected).to.be.false;
expect(subCtrl.testIsConnected).to.be.false;
@@ -258,8 +258,8 @@ describe('UmbController', () => {
const firstCtrl = new UmbTestControllerImplementation(hostElement, 'my-test-alias');
const secondCtrl = new UmbTestControllerImplementation(hostElement, 'my-test-alias');
expect(hostElement.hasController(firstCtrl)).to.be.false;
expect(hostElement.hasController(secondCtrl)).to.be.true;
expect(hostElement.hasUmbController(firstCtrl)).to.be.false;
expect(hostElement.hasUmbController(secondCtrl)).to.be.true;
});
it('controller is replaced by another controller using the the same symbol as controller-alias', () => {
@@ -267,16 +267,16 @@ describe('UmbController', () => {
const firstCtrl = new UmbTestControllerImplementation(hostElement, mySymbol);
const secondCtrl = new UmbTestControllerImplementation(hostElement, mySymbol);
expect(hostElement.hasController(firstCtrl)).to.be.false;
expect(hostElement.hasController(secondCtrl)).to.be.true;
expect(hostElement.hasUmbController(firstCtrl)).to.be.false;
expect(hostElement.hasUmbController(secondCtrl)).to.be.true;
});
it('controller is not replacing another controller when using the undefined as controller-alias', () => {
const firstCtrl = new UmbTestControllerImplementation(hostElement, undefined);
const secondCtrl = new UmbTestControllerImplementation(hostElement, undefined);
expect(hostElement.hasController(firstCtrl)).to.be.true;
expect(hostElement.hasController(secondCtrl)).to.be.true;
expect(hostElement.hasUmbController(firstCtrl)).to.be.true;
expect(hostElement.hasUmbController(secondCtrl)).to.be.true;
});
it('sub controllers is not replacing sub controllers of another host when using the same controller-alias', () => {
@@ -288,8 +288,8 @@ describe('UmbController', () => {
const firstSubCtrl = new UmbTestControllerImplementation(firstCtrl, mySymbol);
const secondSubCtrl = new UmbTestControllerImplementation(secondCtrl, mySymbol);
expect(firstCtrl.hasController(firstSubCtrl)).to.be.true;
expect(secondCtrl.hasController(secondSubCtrl)).to.be.true;
expect(firstCtrl.hasUmbController(firstSubCtrl)).to.be.true;
expect(secondCtrl.hasUmbController(secondSubCtrl)).to.be.true;
});
});
});

View File

@@ -41,7 +41,7 @@ export const UmbElementMixin = <T extends HTMLElementConstructor>(superClass: T)
) as unknown as SpecificR;
} else {
callback(undefined as SpecificT);
this.removeControllerByAlias(controllerAlias);
this.removeUmbControllerByAlias(controllerAlias);
return undefined as SpecificR;
}
}

View File

@@ -15,20 +15,20 @@ describe('UmbElementMixin', () => {
describe('Element general controller API', () => {
describe('methods', () => {
it('has an hasController method', () => {
expect(hostElement).to.have.property('hasController').that.is.a('function');
it('has an hasUmbController method', () => {
expect(hostElement).to.have.property('hasUmbController').that.is.a('function');
});
it('has an getControllers method', () => {
expect(hostElement).to.have.property('getControllers').that.is.a('function');
it('has an getUmbControllers method', () => {
expect(hostElement).to.have.property('getUmbControllers').that.is.a('function');
});
it('has an addController method', () => {
expect(hostElement).to.have.property('addController').that.is.a('function');
it('has an addUmbController method', () => {
expect(hostElement).to.have.property('addUmbController').that.is.a('function');
});
it('has an removeControllerByAlias method', () => {
expect(hostElement).to.have.property('removeControllerByAlias').that.is.a('function');
it('has an removeUmbControllerByAlias method', () => {
expect(hostElement).to.have.property('removeUmbControllerByAlias').that.is.a('function');
});
it('has an removeController method', () => {
expect(hostElement).to.have.property('removeController').that.is.a('function');
it('has an removeUmbController method', () => {
expect(hostElement).to.have.property('removeUmbController').that.is.a('function');
});
it('has an destroy method', () => {
expect(hostElement).to.have.property('destroy').that.is.a('function');
@@ -38,10 +38,10 @@ describe('UmbElementMixin', () => {
describe('Element helper methods API', () => {
describe('methods', () => {
it('has an hasController method', () => {
it('has an hasUmbController method', () => {
expect(hostElement).to.have.property('getHostElement').that.is.a('function');
});
it('has an hasController should return it self', () => {
it('has an hasUmbController should return it self', () => {
expect(hostElement.getHostElement()).to.be.equal(hostElement);
});
it('has an observe method', () => {
@@ -70,12 +70,12 @@ describe('UmbElementMixin', () => {
const ctrl = hostElement.observe(myObservable, () => {}, 'observer');
// The controller is now added to the host:
expect(hostElement.hasController(ctrl)).to.be.true;
expect(hostElement.hasUmbController(ctrl)).to.be.true;
ctrl.destroy();
// The controller is removed from the host:
expect(hostElement.hasController(ctrl)).to.be.false;
expect(hostElement.hasUmbController(ctrl)).to.be.false;
});
it('observe is destroyed then removed', () => {
@@ -85,12 +85,12 @@ describe('UmbElementMixin', () => {
const ctrl = hostElement.observe(myObservable, () => {}, 'observer');
// The controller is now added to the host:
expect(hostElement.hasController(ctrl)).to.be.true;
expect(hostElement.hasUmbController(ctrl)).to.be.true;
hostElement.removeController(ctrl);
hostElement.removeUmbController(ctrl);
// The controller is removed from the host:
expect(hostElement.hasController(ctrl)).to.be.false;
expect(hostElement.hasUmbController(ctrl)).to.be.false;
});
it('observe is destroyed then removed via alias', () => {
@@ -100,12 +100,12 @@ describe('UmbElementMixin', () => {
const ctrl = hostElement.observe(myObservable, () => {}, 'observer');
// The controller is now added to the host:
expect(hostElement.hasController(ctrl)).to.be.true;
expect(hostElement.hasUmbController(ctrl)).to.be.true;
hostElement.removeControllerByAlias('observer');
hostElement.removeUmbControllerByAlias('observer');
// The controller is removed from the host:
expect(hostElement.hasController(ctrl)).to.be.false;
expect(hostElement.hasUmbController(ctrl)).to.be.false;
});
it('observe is removed when replaced with alias', () => {
@@ -115,14 +115,14 @@ describe('UmbElementMixin', () => {
const ctrl = hostElement.observe(myObservable, () => {}, 'observer');
// The controller is now added to the host:
expect(hostElement.hasController(ctrl)).to.be.true;
expect(hostElement.hasUmbController(ctrl)).to.be.true;
const ctrl2 = hostElement.observe(myObservable, () => {}, 'observer');
// The controller is removed from the host:
expect(hostElement.hasController(ctrl)).to.be.false;
expect(hostElement.hasUmbController(ctrl)).to.be.false;
// The controller is new one is there instead:
expect(hostElement.hasController(ctrl2)).to.be.true;
expect(hostElement.hasUmbController(ctrl2)).to.be.true;
});
it('observe is removed when replaced with alias made of hash of callback method', () => {
@@ -132,14 +132,14 @@ describe('UmbElementMixin', () => {
const ctrl = hostElement.observe(myObservable, () => {});
// The controller is now added to the host:
expect(hostElement.hasController(ctrl)).to.be.true;
expect(hostElement.hasUmbController(ctrl)).to.be.true;
const ctrl2 = hostElement.observe(myObservable, () => {});
// The controller is removed from the host:
expect(hostElement.hasController(ctrl)).to.be.false;
expect(hostElement.hasUmbController(ctrl)).to.be.false;
// The controller is new one is there instead:
expect(hostElement.hasController(ctrl2)).to.be.true;
expect(hostElement.hasUmbController(ctrl2)).to.be.true;
});
it('observe is NOT removed when controller alias does not align', () => {
@@ -149,15 +149,15 @@ describe('UmbElementMixin', () => {
const ctrl = hostElement.observe(myObservable, () => {});
// The controller is now added to the host:
expect(hostElement.hasController(ctrl)).to.be.true;
expect(hostElement.hasUmbController(ctrl)).to.be.true;
const ctrl2 = hostElement.observe(myObservable, (value) => {
const a = value + 'bla';
});
// The controller is not removed from the host:
expect(hostElement.hasController(ctrl)).to.be.true;
expect(hostElement.hasController(ctrl2)).to.be.true;
expect(hostElement.hasUmbController(ctrl)).to.be.true;
expect(hostElement.hasUmbController(ctrl2)).to.be.true;
});
it('observe is removed when observer is undefined and using the same alias', () => {
@@ -167,7 +167,7 @@ describe('UmbElementMixin', () => {
const ctrl = hostElement.observe(myObservable, () => {}, 'observer');
// The controller is now added to the host:
expect(hostElement.hasController(ctrl)).to.be.true;
expect(hostElement.hasUmbController(ctrl)).to.be.true;
const ctrl2 = hostElement.observe(
undefined,
@@ -178,7 +178,7 @@ describe('UmbElementMixin', () => {
);
// The controller is removed from the host, and the new one was NOT added:
expect(hostElement.hasController(ctrl)).to.be.false;
expect(hostElement.hasUmbController(ctrl)).to.be.false;
expect(ctrl2).to.be.undefined;
});
@@ -189,12 +189,12 @@ describe('UmbElementMixin', () => {
const ctrl = hostElement.observe(myObservable, () => {});
// The controller is now added to the host:
expect(hostElement.hasController(ctrl)).to.be.true;
expect(hostElement.hasUmbController(ctrl)).to.be.true;
const ctrl2 = hostElement.observe(undefined, () => {});
// The controller is removed from the host, and the new one was NOT added:
expect(hostElement.hasController(ctrl)).to.be.false;
expect(hostElement.hasUmbController(ctrl)).to.be.false;
expect(ctrl2).to.be.undefined;
});
@@ -227,7 +227,7 @@ describe('UmbElementMixin', () => {
const check: CheckType<typeof ctrl, UmbObserverController<string>> = ctrl;
const check2: ReverseCheckType<typeof ctrl, UmbObserverController<undefined>> = ctrl;
expect(hostElement.hasController(check)).to.be.true;
expect(hostElement.hasUmbController(check)).to.be.true;
expect(check === check2).to.be.true; // Just to use the const for something.
});
@@ -244,7 +244,7 @@ describe('UmbElementMixin', () => {
const check2: ReverseCheckType<typeof ctrl, UmbObserverController<undefined>> = ctrl;
const check3: ReverseCheckType<typeof ctrl, UmbObserverController<string>> = ctrl;
expect(hostElement.hasController(check)).to.be.true;
expect(hostElement.hasUmbController(check)).to.be.true;
expect(check2 === check3).to.be.true; // Just to use the const for something.
});
@@ -269,7 +269,7 @@ describe('UmbElementMixin', () => {
const check3: ReverseCheckType<typeof ctrl, UmbObserverController<string>> = ctrl;
if (ctrl) {
expect(hostElement.hasController(ctrl)).to.be.true;
expect(hostElement.hasUmbController(ctrl)).to.be.true;
} else {
expect(ctrl).to.be.undefined;
}
@@ -300,7 +300,7 @@ describe('UmbElementMixin', () => {
const check4: ReverseCheckType<typeof ctrl, UmbObserverController<undefined>> = ctrl;
if (ctrl) {
expect(hostElement.hasController(ctrl)).to.be.true;
expect(hostElement.hasUmbController(ctrl)).to.be.true;
} else {
expect(ctrl).to.be.undefined;
}

View File

@@ -601,16 +601,16 @@ describe('UmbBaseExtensionController', () => {
expect(isPermitted).to.be.true;
expect(extensionController?.permitted).to.be.true;
// Hack to double check that its two conditions that make up the state:
expect(extensionController.getControllers((controller) => (controller as any).permitted).length).to.equal(
2,
);
expect(
extensionController.getUmbControllers((controller) => (controller as any).permitted).length,
).to.equal(2);
} else if (count === 2) {
expect(isPermitted).to.be.false;
expect(extensionController?.permitted).to.be.false;
// Hack to double check that its two conditions that make up the state, in this case its one, cause we already got the callback when one of the conditions changed. meaning in this split second one is still good:
expect(extensionController.getControllers((controller) => (controller as any).permitted).length).to.equal(
1,
);
expect(
extensionController.getUmbControllers((controller) => (controller as any).permitted).length,
).to.equal(1);
// Then we are done:
extensionController.destroy(); // End this test.

View File

@@ -105,7 +105,7 @@ export abstract class UmbBaseExtensionInitializer<
if (this.#conditionControllers === undefined || this.#conditionControllers.length === 0) return;
this.#conditionControllers.forEach((controller) => controller.destroy());
this.#conditionControllers = [];
this.removeControllerByAlias('_observeConditions');
this.removeUmbControllerByAlias('_observeConditions');
}
#gotManifest() {
@@ -149,7 +149,7 @@ export abstract class UmbBaseExtensionInitializer<
'_observeConditions',
);
} else {
this.removeControllerByAlias('_observeConditions');
this.removeUmbControllerByAlias('_observeConditions');
}
if (noChangeInConditions) {

View File

@@ -86,11 +86,11 @@ describe('UmbLocalizeController', () => {
await aTimeout(0);
const host = {
getHostElement: () => document.createElement('div'),
addController: () => {},
removeController: () => {},
hasController: () => false,
getControllers: () => [],
removeControllerByAlias: () => {},
addUmbController: () => {},
removeUmbController: () => {},
hasUmbController: () => false,
getUmbControllers: () => [],
removeUmbControllerByAlias: () => {},
} satisfies UmbControllerHost;
controller = new UmbLocalizationController(host);
});

View File

@@ -51,7 +51,7 @@ export class UmbLocalizationController<LocalizationSetType extends UmbLocalizati
constructor(host: UmbControllerHost) {
this.#host = host;
this.#hostEl = host.getHostElement() as HTMLElement;
this.#host.addController(this);
this.#host.addUmbController(this);
}
hostConnected(): void {
@@ -63,7 +63,7 @@ export class UmbLocalizationController<LocalizationSetType extends UmbLocalizati
}
destroy(): void {
this.#host?.removeController(this);
this.#host?.removeUmbController(this);
this.#hostEl = undefined as any;
this.#usedKeys.length = 0;
}

View File

@@ -25,8 +25,8 @@ describe('UmbObserverController', () => {
const firstCtrl = new UmbObserverController(hostElement, observable, callbackMethod, 'my-test-alias');
const secondCtrl = new UmbObserverController(hostElement, observable, callbackMethod, 'my-test-alias');
expect(hostElement.hasController(firstCtrl)).to.be.false;
expect(hostElement.hasController(secondCtrl)).to.be.true;
expect(hostElement.hasUmbController(firstCtrl)).to.be.false;
expect(hostElement.hasUmbController(secondCtrl)).to.be.true;
});
it('controller is replaced by another controller using the the same symbol as controller-alias', () => {
@@ -39,8 +39,8 @@ describe('UmbObserverController', () => {
const firstCtrl = new UmbObserverController(hostElement, observable, callbackMethod, mySymbol);
const secondCtrl = new UmbObserverController(hostElement, observable, callbackMethod, mySymbol);
expect(hostElement.hasController(firstCtrl)).to.be.false;
expect(hostElement.hasController(secondCtrl)).to.be.true;
expect(hostElement.hasUmbController(firstCtrl)).to.be.false;
expect(hostElement.hasUmbController(secondCtrl)).to.be.true;
});
it('controller is NOT replacing another controller when using a null for controller-alias', () => {
@@ -59,8 +59,8 @@ describe('UmbObserverController', () => {
controllerAlias2 ??= controllerAlias2 === undefined ? simpleHashCode(callbackMethod.toString()) : undefined;
const secondCtrl = new UmbObserverController(hostElement, observable, callbackMethod, controllerAlias2);
expect(hostElement.hasController(firstCtrl)).to.be.true;
expect(hostElement.hasController(secondCtrl)).to.be.true;
expect(hostElement.hasUmbController(firstCtrl)).to.be.true;
expect(hostElement.hasUmbController(secondCtrl)).to.be.true;
});
});
});

View File

@@ -29,11 +29,11 @@ export class UmbObserverController<T = unknown> extends UmbObserver<T> implement
}
*/
host.addController(this);
host.addUmbController(this);
}
destroy(): void {
this.#host?.removeController(this);
this.#host?.removeUmbController(this);
(this.#host as any) = undefined;
super.destroy();
}

View File

@@ -140,7 +140,7 @@ export class UmbBlockGridEntriesContext
'observeThisLayouts',
);
this.removeControllerByAlias('observeAreaType');
this.removeUmbControllerByAlias('observeAreaType');
const hostEl = this.getHostElement() as HTMLElement | undefined;
if (hostEl) {

View File

@@ -48,7 +48,7 @@ export class UmbBlockGridTypeWorkspaceViewSettingsElement extends UmbLitElement
if (Array.isArray(value) && value.length > 0) {
this._showSizeOptions = true;
}
this.removeControllerByAlias('_observeColumnSpanOptions');
this.removeUmbControllerByAlias('_observeColumnSpanOptions');
},
'observeColumnSpanOptions',
);

View File

@@ -36,7 +36,7 @@ export class UmbBlockListEntryContext extends UmbBlockEntryContext<
'observeInlineEditingMode',
);
} else {
this.removeControllerByAlias('observeInlineEditingMode');
this.removeUmbControllerByAlias('observeInlineEditingMode');
}
}

View File

@@ -235,7 +235,7 @@ export abstract class UmbBlockEntryContext<
'observeWorkspacePath',
);
} else {
this.removeControllerByAlias('observeWorkspacePath');
this.removeUmbControllerByAlias('observeWorkspacePath');
}
}
@@ -326,7 +326,7 @@ export abstract class UmbBlockEntryContext<
if (!blockType) return;
if (blockType.label) {
this.removeControllerByAlias('observeContentTypeName');
this.removeUmbControllerByAlias('observeContentTypeName');
// Missing part for label syntax, as we need to store the syntax, interpretive it and then set the label: (here we are just parsing the label syntax)
this.#label.setValue(blockType.label);
return;

View File

@@ -131,7 +131,7 @@ export class UmbBlockWorkspaceContext<LayoutDataType extends UmbBlockLayoutBaseM
this.#blockEntries.layoutOf(unique),
(layoutData) => {
this.#initialLayout ??= layoutData as LayoutDataType;
this.removeControllerByAlias('observeLayoutInitially');
this.removeUmbControllerByAlias('observeLayoutInitially');
},
'observeLayoutInitially',
);
@@ -156,7 +156,7 @@ export class UmbBlockWorkspaceContext<LayoutDataType extends UmbBlockLayoutBaseM
this.#blockManager!.contentOf(contentUdi),
(contentData) => {
this.#initialContent ??= contentData;
this.removeControllerByAlias('observeContentInitially');
this.removeUmbControllerByAlias('observeContentInitially');
},
'observeContentInitially',
);
@@ -178,7 +178,7 @@ export class UmbBlockWorkspaceContext<LayoutDataType extends UmbBlockLayoutBaseM
this.#blockManager!.contentOf(settingsUdi),
(settingsData) => {
this.#initialSettings ??= settingsData;
this.removeControllerByAlias('observeSettingsInitially');
this.removeUmbControllerByAlias('observeSettingsInitially');
},
'observeSettingsInitially',
);

View File

@@ -15,7 +15,7 @@ export class UmbCollectionBulkActionPermissionCondition
super(host, args);
this.consumeContext(UMB_DEFAULT_COLLECTION_CONTEXT, (context) => {
const allowedActions = context.getConfig().allowedEntityBulkActions;
const allowedActions = context.getConfig()?.allowedEntityBulkActions;
this.permitted = allowedActions ? this.config.match(allowedActions) : false;
});
}

View File

@@ -43,7 +43,8 @@ describe('UmbCollectionViewManager', () => {
beforeEach(() => {
const hostElement = new UmbTestControllerHostElement();
manager = new UmbCollectionViewManager(hostElement, config);
manager = new UmbCollectionViewManager(hostElement);
manager.setConfig(config);
});
describe('Public API', () => {
@@ -60,8 +61,8 @@ describe('UmbCollectionViewManager', () => {
expect(manager).to.have.property('routes').to.be.an.instanceOf(Observable);
});
it('has a rootPathname property', () => {
expect(manager).to.have.property('rootPathname').to.be.an.instanceOf(Observable);
it('has a rootPathName property', () => {
expect(manager).to.have.property('rootPathName').to.be.an.instanceOf(Observable);
});
});

View File

@@ -8,6 +8,7 @@ import type { UmbRoute } from '@umbraco-cms/backoffice/router';
export interface UmbCollectionViewManagerConfig {
defaultViewAlias?: string;
manifestFilter?: (manifest: ManifestCollectionView) => boolean
}
export class UmbCollectionViewManager extends UmbControllerBase {
@@ -20,24 +21,26 @@ export class UmbCollectionViewManager extends UmbControllerBase {
#routes = new UmbArrayState<UmbRoute>([], (x) => x.path);
public readonly routes = this.#routes.asObservable();
#rootPathname = new UmbStringState('');
public readonly rootPathname = this.#rootPathname.asObservable();
#rootPathName = new UmbStringState('');
public readonly rootPathName = this.#rootPathName.asObservable();
#defaultViewAlias?: string;
constructor(host: UmbControllerHost, config: UmbCollectionViewManagerConfig) {
constructor(host: UmbControllerHost) {
super(host);
this.#defaultViewAlias = config.defaultViewAlias;
this.#observeViews();
// TODO: hack - we need to figure out how to get the "parent path" from the router
setTimeout(() => {
const currentUrl = new URL(window.location.href);
this.#rootPathname.setValue(currentUrl.pathname.substring(0, currentUrl.pathname.lastIndexOf('/')));
this.#rootPathName.setValue(currentUrl.pathname.substring(0, currentUrl.pathname.lastIndexOf('/')));
}, 100);
}
public setConfig(config: UmbCollectionViewManagerConfig) {
this.#defaultViewAlias = config.defaultViewAlias;
this.#observeViews(config.manifestFilter);
}
// Views
/**
* Sets the current view.
@@ -57,12 +60,18 @@ export class UmbCollectionViewManager extends UmbControllerBase {
return this.#currentView.getValue();
}
#observeViews() {
return new UmbExtensionsManifestInitializer(this, umbExtensionsRegistry, 'collectionView', null, (views) => {
const manifests = views.map((view) => view.manifest);
this.#views.setValue(manifests);
this.#createRoutes(manifests);
});
#observeViews(filter?: (manifest: ManifestCollectionView) => boolean) {
return new UmbExtensionsManifestInitializer(
this,
umbExtensionsRegistry,
'collectionView',
filter ?? null,
(views) => {
const manifests = views.map((view) => view.manifest);
this.#views.setValue(manifests);
this.#createRoutes(manifests);
},
);
}
#createRoutes(views: ManifestCollectionView[] | null) {

View File

@@ -8,6 +8,7 @@ import type { ManifestCollection } from '@umbraco-cms/backoffice/extension-regis
@customElement('umb-collection')
export class UmbCollectionElement extends UmbLitElement {
#alias?: string;
@property({ type: String, reflect: true })
set alias(newVal) {
this.#alias = newVal;
@@ -17,7 +18,8 @@ export class UmbCollectionElement extends UmbLitElement {
return this.#alias;
}
#config?: UmbCollectionConfiguration = { pageSize: 50 };
#config?: UmbCollectionConfiguration;
@property({ type: Object, attribute: false })
set config(newVal: UmbCollectionConfiguration | undefined) {
this.#config = newVal;

View File

@@ -1,20 +1,34 @@
import type { UmbDefaultCollectionContext } from '../default/collection-default.context.js';
import { UMB_DEFAULT_COLLECTION_CONTEXT } from '../default/collection-default.context.js';
import type { ManifestCollectionView } from '../../extension-registry/models/collection-view.model.js';
import { css, html, customElement, state, nothing } from '@umbraco-cms/backoffice/external/lit';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import type { UmbCollectionLayoutConfiguration } from '../types.js';
import { css, html, customElement, state, nothing, repeat, query } from '@umbraco-cms/backoffice/external/lit';
import { observeMultiple } from '@umbraco-cms/backoffice/observable-api';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { UMB_ENTITY_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace';
import type { ManifestCollectionView } from '@umbraco-cms/backoffice/extension-registry';
import type { UUIPopoverContainerElement } from '@umbraco-cms/backoffice/external/uui';
interface UmbCollectionViewLayout {
alias: string;
label: string;
icon: string;
pathName: string;
}
@customElement('umb-collection-view-bundle')
export class UmbCollectionViewBundleElement extends UmbLitElement {
@state()
_views: Array<ManifestCollectionView> = [];
_views: Array<UmbCollectionViewLayout> = [];
@state()
_currentView?: ManifestCollectionView;
_currentView?: UmbCollectionViewLayout;
@state()
private _collectionRootPathname?: string;
private _collectionRootPathName?: string;
@state()
private _entityUnique?: string;
#collectionContext?: UmbDefaultCollectionContext<any, any>;
@@ -23,43 +37,86 @@ export class UmbCollectionViewBundleElement extends UmbLitElement {
this.consumeContext(UMB_DEFAULT_COLLECTION_CONTEXT, (context) => {
this.#collectionContext = context;
if (!this.#collectionContext) return;
this.#observeRootPathname();
this.#observeViews();
this.#observeCurrentView();
this.#observeCollection();
});
this.consumeContext(UMB_ENTITY_WORKSPACE_CONTEXT, (context) => {
this._entityUnique = context.getUnique() ?? '';
});
}
#observeRootPathname() {
this.observe(
this.#collectionContext!.view.rootPathname,
(rootPathname) => {
this._collectionRootPathname = rootPathname;
},
'umbCollectionRootPathnameObserver',
);
}
#observeCollection() {
if (!this.#collectionContext) return;
#observeCurrentView() {
this.observe(
this.#collectionContext!.view.currentView,
(view) => {
//TODO: This is not called when the view is changed
this._currentView = view;
this.#collectionContext.view.rootPathName,
(rootPathName) => {
this._collectionRootPathName = rootPathName;
},
'umbCollectionRootPathNameObserver',
);
this.observe(
this.#collectionContext.view.currentView,
(currentView) => {
if (!currentView) return;
this._currentView = this._views.find((view) => view.alias === currentView.alias);
},
'umbCurrentCollectionViewObserver',
);
}
#observeViews() {
this.observe(
this.#collectionContext!.view.views,
(views) => {
this._views = views;
observeMultiple([this.#collectionContext.view.views, this.#collectionContext.viewLayouts]),
([manifests, viewLayouts]) => {
if (!manifests?.length && !viewLayouts?.length) return;
this._views = this.#mapManifestToViewLayout(manifests, viewLayouts);
},
'umbCollectionViewsObserver',
'umbCollectionViewsAndLayoutsObserver',
);
}
@query('#collection-view-bundle-popover')
private _popover?: UUIPopoverContainerElement;
#mapManifestToViewLayout(
manifests: Array<ManifestCollectionView>,
viewLayouts: Array<UmbCollectionLayoutConfiguration>,
): typeof this._views {
if (viewLayouts.length > 0) {
const layouts: typeof this._views = [];
viewLayouts.forEach((viewLayout) => {
const viewManifest = manifests.find((manifest) => manifest.alias === viewLayout.collectionView);
if (!viewManifest) return;
layouts.push({
alias: viewManifest.alias,
label: viewLayout.name ?? viewManifest.meta.label,
icon: viewLayout.icon ?? viewManifest.meta.icon,
pathName: viewManifest.meta.pathName,
});
});
return layouts;
}
// fallback on the 'collectionView' manifests
return manifests.map((manifest) => ({
alias: manifest.alias,
label: manifest.meta.label,
icon: manifest.meta.icon,
pathName: manifest.meta.pathName,
}));
}
#onClick(view: UmbCollectionViewLayout) {
this.#collectionContext?.setLastSelectedView(this._entityUnique, view.alias);
// TODO: This ignorer is just neede for JSON SCHEMA TO WORK, As its not updated with latest TS jet.
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this._popover?.hidePopover();
}
render() {
if (!this._currentView) return nothing;
if (this._views.length <= 1) return nothing;
@@ -70,22 +127,29 @@ export class UmbCollectionViewBundleElement extends UmbLitElement {
</uui-button>
<uui-popover-container id="collection-view-bundle-popover" placement="bottom-end">
<umb-popover-layout>
<div class="filter-dropdown">${this._views.map((view) => this.#renderItem(view))}</div>
<div class="filter-dropdown">
${repeat(
this._views,
(view) => view.alias,
(view) => this.#renderItem(view),
)}
</div>
</umb-popover-layout>
</uui-popover-container>
`;
}
#renderItem(view: ManifestCollectionView) {
#renderItem(view: UmbCollectionViewLayout) {
return html`
<uui-button compact href="${this._collectionRootPathname}/${view.meta.pathName}">
${this.#renderItemDisplay(view)} <span class="label">${view.meta.label}</span>
<uui-button compact href="${this._collectionRootPathName}/${view.pathName}" @click=${() => this.#onClick(view)}>
${this.#renderItemDisplay(view)}
<span class="label">${view.label}</span>
</uui-button>
`;
}
#renderItemDisplay(view: ManifestCollectionView) {
return html`<umb-icon name=${view.meta.icon}></umb-icon>`;
#renderItemDisplay(view: UmbCollectionViewLayout) {
return html`<umb-icon name=${view.icon}></umb-icon>`;
}
static styles = [
@@ -99,9 +163,12 @@ export class UmbCollectionViewBundleElement extends UmbLitElement {
}
.filter-dropdown {
display: flex;
gap: var(--uui-size-space-3);
gap: var(--uui-size-space-1);
flex-direction: column;
}
umb-icon {
display: inline-block;
}
`,
];
}

View File

@@ -1,17 +1,25 @@
import type { UmbCollectionColumnConfiguration, UmbCollectionConfiguration, UmbCollectionContext } from '../types.js';
import { UmbCollectionViewManager } from '../collection-view.manager.js';
import type { UmbCollectionViewManagerConfig } from '../collection-view.manager.js';
import type {
UmbCollectionColumnConfiguration,
UmbCollectionConfiguration,
UmbCollectionContext,
UmbCollectionLayoutConfiguration,
} from '../types.js';
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
import { UmbArrayState, UmbNumberState, UmbObjectState } from '@umbraco-cms/backoffice/observable-api';
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
import { UmbArrayState, UmbNumberState, UmbObjectState } from '@umbraco-cms/backoffice/observable-api';
import { UmbExtensionApiInitializer } from '@umbraco-cms/backoffice/extension-api';
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
import { UmbSelectionManager, UmbPaginationManager } from '@umbraco-cms/backoffice/utils';
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
import type { ManifestCollection, ManifestRepository } from '@umbraco-cms/backoffice/extension-registry';
import type { UmbApi } from '@umbraco-cms/backoffice/extension-api';
import type { UmbCollectionFilterModel, UmbCollectionRepository } from '@umbraco-cms/backoffice/collection';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
const LOCAL_STORAGE_KEY = 'umb-collection-view';
export class UmbDefaultCollectionContext<
CollectionItemType = any,
FilterModelType extends UmbCollectionFilterModel = any,
@@ -19,7 +27,9 @@ export class UmbDefaultCollectionContext<
extends UmbContextBase<UmbDefaultCollectionContext>
implements UmbCollectionContext, UmbApi
{
#config?: UmbCollectionConfiguration = { pageSize: 50 };
#manifest?: ManifestCollection;
#repository?: UmbCollectionRepository;
#items = new UmbArrayState<CollectionItemType>([], (x) => x);
public readonly items = this.#items.asObservable();
@@ -30,10 +40,17 @@ export class UmbDefaultCollectionContext<
#filter = new UmbObjectState<FilterModelType | object>({});
public readonly filter = this.#filter.asObservable();
#userDefinedProperties = new UmbArrayState<UmbCollectionColumnConfiguration>([], (x) => x);
#userDefinedProperties = new UmbArrayState<UmbCollectionColumnConfiguration>([], (x) => x.alias);
public readonly userDefinedProperties = this.#userDefinedProperties.asObservable();
repository?: UmbCollectionRepository;
#viewLayouts = new UmbArrayState<UmbCollectionLayoutConfiguration>([], (x) => x.collectionView);
public readonly viewLayouts = this.#viewLayouts.asObservable();
public readonly pagination = new UmbPaginationManager();
public readonly selection = new UmbSelectionManager(this);
public readonly view = new UmbCollectionViewManager(this);
#defaultViewAlias: string;
#initResolver?: () => void;
#initialized = false;
@@ -42,28 +59,72 @@ export class UmbDefaultCollectionContext<
this.#initialized ? resolve() : (this.#initResolver = resolve);
});
public readonly pagination = new UmbPaginationManager();
public readonly selection = new UmbSelectionManager(this);
public readonly view;
constructor(host: UmbControllerHost, defaultViewAlias: string) {
super(host, UMB_DEFAULT_COLLECTION_CONTEXT);
// listen for page changes on the pagination manager
this.pagination.addEventListener(UmbChangeEvent.TYPE, this.#onPageChange);
this.#defaultViewAlias = defaultViewAlias;
this.view = new UmbCollectionViewManager(this, { defaultViewAlias: defaultViewAlias });
this.pagination.addEventListener(UmbChangeEvent.TYPE, this.#onPageChange);
}
#configured = false;
#configure() {
if (!this.#config) return;
this.selection.setMultiple(true);
if (this.#config.pageSize) {
this.pagination.setPageSize(this.#config.pageSize);
}
this.#filter.setValue({
...this.#config,
...this.#filter.getValue(),
skip: 0,
take: this.#config.pageSize,
});
this.#userDefinedProperties.setValue(this.#config?.userDefinedProperties ?? []);
const viewManagerConfig: UmbCollectionViewManagerConfig = { defaultViewAlias: this.#defaultViewAlias };
if (this.#config.layouts && this.#config.layouts.length > 0) {
this.#viewLayouts.setValue(this.#config.layouts);
const aliases = this.#config.layouts.map((layout) => layout.collectionView);
viewManagerConfig.manifestFilter = (manifest) => aliases.includes(manifest.alias);
}
this.view.setConfig(viewManagerConfig);
this.#configured = true;
}
// TODO: find a generic way to do this
#checkIfInitialized() {
if (this.repository) {
if (this.#repository) {
this.#initialized = true;
this.#initResolver?.();
}
}
#config: UmbCollectionConfiguration = { pageSize: 50 };
#observeRepository(repositoryAlias: string) {
new UmbExtensionApiInitializer<ManifestRepository<UmbCollectionRepository>>(
this,
umbExtensionsRegistry,
repositoryAlias,
[this._host],
(permitted, ctrl) => {
this.#repository = permitted ? ctrl.api : undefined;
this.#checkIfInitialized();
},
);
}
#onPageChange = (event: UmbChangeEvent) => {
const target = event.target as UmbPaginationManager;
const skipFilter = { skip: target.getSkip() } as Partial<FilterModelType>;
this.setFilter(skipFilter);
};
/**
* Sets the configuration for the collection.
@@ -72,7 +133,6 @@ export class UmbDefaultCollectionContext<
*/
public setConfig(config: UmbCollectionConfiguration) {
this.#config = config;
this.#configure();
}
public getConfig() {
@@ -108,10 +168,13 @@ export class UmbDefaultCollectionContext<
*/
public async requestCollection() {
await this.#init;
if (!this.repository) throw new Error(`Missing repository for ${this.#manifest}`);
if (!this.#configured) this.#configure();
if (!this.#repository) throw new Error(`Missing repository for ${this.#manifest}`);
const filter = this.#filter.getValue();
const { data } = await this.repository.requestCollection(filter);
const { data } = await this.#repository.requestCollection(filter);
if (data) {
this.#items.setValue(data.items);
@@ -130,35 +193,24 @@ export class UmbDefaultCollectionContext<
this.requestCollection();
}
#configure() {
this.selection.setMultiple(true);
this.pagination.setPageSize(this.#config.pageSize!);
this.#filter.setValue({
...this.#config,
...this.#filter.getValue(),
skip: 0,
take: this.#config.pageSize,
});
this.#userDefinedProperties.setValue(this.#config.userDefinedProperties ?? []);
public getLastSelectedView(unique: string | undefined): string | undefined {
if (!unique) return;
const layouts = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY) ?? '{}') ?? {};
if (!layouts) return;
return layouts[unique];
}
#onPageChange = (event: UmbChangeEvent) => {
const target = event.target as UmbPaginationManager;
const skipFilter = { skip: target.getSkip() } as Partial<FilterModelType>;
this.setFilter(skipFilter);
};
public setLastSelectedView(unique: string | undefined, viewAlias: string) {
if (!unique) return;
#observeRepository(repositoryAlias: string) {
new UmbExtensionApiInitializer<ManifestRepository<UmbCollectionRepository>>(
this,
umbExtensionsRegistry,
repositoryAlias,
[this._host],
(permitted, ctrl) => {
this.repository = permitted ? ctrl.api : undefined;
this.#checkIfInitialized();
},
);
const layouts = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY) ?? '{}') ?? {};
if (!layouts) return;
layouts[unique] = viewAlias;
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(layouts));
}
}

View File

@@ -34,7 +34,13 @@ export class UmbIconElement extends UmbLitElement {
@property({ type: String })
public set name(value: string | undefined) {
const [icon, alias] = value ? value.split(' ') : [];
if (alias) this.#setColorStyle(alias);
if (alias) {
this.#setColorStyle(alias);
} else {
this._color = undefined;
}
this._icon = icon;
}
public get name(): string | undefined {

View File

@@ -50,12 +50,12 @@ export class UmbPropertyTypeBasedPropertyElement extends UmbLitElement {
(extension) => {
if (!extension) return;
this._propertyEditorUiAlias = extension?.meta.defaultPropertyEditorUiAlias;
this.removeControllerByAlias('_observePropertyEditorSchema');
this.removeUmbControllerByAlias('_observePropertyEditorSchema');
},
'_observePropertyEditorSchema',
);
} else {
this.removeControllerByAlias('_observePropertyEditorSchema');
this.removeUmbControllerByAlias('_observePropertyEditorSchema');
}
},
'_observeDataType',

View File

@@ -96,7 +96,7 @@ export class UmbContentTypeContainerStructureHelper<T extends UmbContentTypeMode
if (this._containerId === null) {
this.#observeHasPropertiesOf(null);
this.#observeRootContainers();
this.removeControllerByAlias('_observeContainers');
this.removeUmbControllerByAlias('_observeContainers');
} else {
this.observe(
this.#structure.containerById(this._containerId),
@@ -114,7 +114,7 @@ export class UmbContentTypeContainerStructureHelper<T extends UmbContentTypeMode
this._parentType = parent.type;
this.#observeSimilarContainers();
} else {
this.removeControllerByAlias('_observeContainers');
this.removeUmbControllerByAlias('_observeContainers');
this._parentName = undefined;
this._parentType = undefined;
}
@@ -122,13 +122,13 @@ export class UmbContentTypeContainerStructureHelper<T extends UmbContentTypeMode
'_observeMainParentContainer',
);
} else {
this.removeControllerByAlias('_observeMainParentContainer');
this.removeUmbControllerByAlias('_observeMainParentContainer');
this._parentName = null; //In this way we want to look for one without a parent. [NL]
this._parentType = undefined;
this.#observeSimilarContainers();
}
} else {
this.removeControllerByAlias('_observeContainers');
this.removeUmbControllerByAlias('_observeContainers');
this._containerName = undefined;
this._containerType = undefined;
// TODO: reset has Properties.

View File

@@ -77,7 +77,7 @@ export class UmbContentTypePropertyStructureHelper<T extends UmbContentTypeModel
if (this._containerId === null) {
this.#observePropertyStructureOf(null);
this.removeControllerByAlias('_observeContainers');
this.removeUmbControllerByAlias('_observeContainers');
} else {
this.observe(
this.#structure.containerById(this._containerId),
@@ -95,7 +95,7 @@ export class UmbContentTypePropertyStructureHelper<T extends UmbContentTypeModel
this._parentType = parent.type;
this.#observeSimilarContainers();
} else {
this.removeControllerByAlias('_observeContainers');
this.removeUmbControllerByAlias('_observeContainers');
this._parentName = undefined;
this._parentType = undefined;
}
@@ -103,13 +103,13 @@ export class UmbContentTypePropertyStructureHelper<T extends UmbContentTypeModel
'_observeMainParentContainer',
);
} else {
this.removeControllerByAlias('_observeMainParentContainer');
this.removeUmbControllerByAlias('_observeMainParentContainer');
this._parentName = null; //In this way we want to look for one without a parent. [NL]
this._parentType = undefined;
this.#observeSimilarContainers();
}
} else {
this.removeControllerByAlias('_observeContainers');
this.removeUmbControllerByAlias('_observeContainers');
this._containerName = undefined;
this._containerType = undefined;
this.#propertyStructure.setValue([]);

View File

@@ -68,7 +68,7 @@ export class UmbContentTypeWorkspaceViewEditGroupElement extends UmbLitElement {
// We use name match to determine inheritance, so no name cannot inherit.
this._inherited = false;
this._hasOwnerContainer = true;
this.removeControllerByAlias('observeGroupContainers');
this.removeUmbControllerByAlias('observeGroupContainers');
}
}
}

View File

@@ -56,6 +56,7 @@ export class UmbPropertyEditorUICollectionViewElement extends UmbLitElement impl
): UmbCollectionConfiguration {
return {
allowedEntityBulkActions: config?.getValueByAlias<UmbCollectionBulkActionPermissions>('bulkActionPermissions'),
layouts: config?.getValueByAlias('layouts'),
orderBy: config?.getValueByAlias('orderBy') ?? 'updateDate',
orderDirection: config?.getValueByAlias('orderDirection') ?? 'asc',
pageSize: Number(config?.getValueByAlias('pageSize')) ?? 50,

View File

@@ -37,10 +37,10 @@ export class UmbSectionSidebarContextMenuElement extends UmbLitElement {
// prettier-ignore
this.observe(this.#sectionSidebarContext.headline, (value) => (this._headline = value), '_observeHeadline');
} else {
this.removeControllerByAlias('_observeContextMenuIsOpen');
this.removeControllerByAlias('_observeUnique');
this.removeControllerByAlias('_observeEntityType');
this.removeControllerByAlias('_observeHeadline');
this.removeUmbControllerByAlias('_observeContextMenuIsOpen');
this.removeUmbControllerByAlias('_observeUnique');
this.removeUmbControllerByAlias('_observeEntityType');
this.removeUmbControllerByAlias('_observeHeadline');
}
});
}
@@ -79,7 +79,7 @@ export class UmbSectionSidebarContextMenuElement extends UmbLitElement {
@action-executed=${this.#onActionExecuted}
.entityType=${this._entityType}
.unique=${this._unique}></umb-entity-action-list>
</div>`
</div>`
: nothing;
}

View File

@@ -234,7 +234,7 @@ export class UmbSorterController<T, ElementType extends HTMLElement = HTMLElemen
}
this.#config = config as INTERNAL_UmbSorterConfig<T, ElementType>;
host.addController(this);
host.addUmbController(this);
this.#observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {

View File

@@ -41,7 +41,7 @@ export class UmbRefDataTypeElement extends UmbElementMixin(UUIRefNodeElement) {
'dataType',
);
} else {
this.removeControllerByAlias('dataType');
this.removeUmbControllerByAlias('dataType');
}
}

View File

@@ -41,7 +41,7 @@ export class UmbDataTypeWorkspaceEditorElement extends UmbLitElement {
(this.shadowRoot!.querySelector('#nameInput') as HTMLElement).focus();
});
}
this.removeControllerByAlias('isNewRedirectController');
this.removeUmbControllerByAlias('isNewRedirectController');
},
'_observeIsNew',
);

View File

@@ -43,7 +43,7 @@ export class UmbDocumentTypeWorkspaceEditorElement extends UmbLitElement {
// TODO: Would be good with a more general way to bring focus to the name input.
(this.shadowRoot?.querySelector('#name') as HTMLElement)?.focus();
}
this.removeControllerByAlias('isNewRedirectController');
this.removeUmbControllerByAlias('isNewRedirectController');
},
'_observeIsNew',
);

View File

@@ -104,7 +104,7 @@ export class UmbDocumentTypeWorkspaceContext
path: 'edit/:id',
component: UmbDocumentTypeWorkspaceEditorElement,
setup: (_component, info) => {
this.removeControllerByAlias('isNewRedirectController');
this.removeUmbControllerByAlias('isNewRedirectController');
const id = info.match.params.id;
this.load(id);
},

View File

@@ -1,11 +1,11 @@
import type { UmbDocumentCollectionContext } from './document-collection.context.js';
import { css, html, customElement } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UMB_DEFAULT_COLLECTION_CONTEXT } from '@umbraco-cms/backoffice/collection';
import type { UmbDefaultCollectionContext } from '@umbraco-cms/backoffice/collection';
@customElement('umb-document-collection-toolbar')
export class UmbDocumentCollectionToolbarElement extends UmbLitElement {
#collectionContext?: UmbDocumentCollectionContext;
#collectionContext?: UmbDefaultCollectionContext;
#inputTimer?: NodeJS.Timeout;
#inputTimerAmount = 500;
@@ -14,7 +14,7 @@ export class UmbDocumentCollectionToolbarElement extends UmbLitElement {
super();
this.consumeContext(UMB_DEFAULT_COLLECTION_CONTEXT, (instance) => {
this.#collectionContext = instance as UmbDocumentCollectionContext;
this.#collectionContext = instance;
});
}

View File

@@ -9,6 +9,5 @@ export class UmbDocumentCollectionContext extends UmbDefaultCollectionContext<
> {
constructor(host: UmbControllerHost) {
super(host, UMB_DOCUMENT_TABLE_COLLECTION_VIEW_ALIAS);
}
}

View File

@@ -37,6 +37,7 @@ export class UmbDocumentCollectionServerDataSource implements UmbCollectionDataS
const model: UmbDocumentCollectionItemModel = {
unique: item.id,
contentTypeAlias: item.documentType.alias,
createDate: new Date(variant.createDate),
creator: item.creator,
icon: item.documentType.icon,

View File

@@ -11,6 +11,7 @@ export interface UmbDocumentCollectionFilterModel extends UmbCollectionFilterMod
export interface UmbDocumentCollectionItemModel {
unique: string;
contentTypeAlias: string;
createDate: Date;
creator?: string | null;
icon: string;

View File

@@ -4,6 +4,8 @@ export { UMB_DOCUMENT_GRID_COLLECTION_VIEW_ALIAS, UMB_DOCUMENT_TABLE_COLLECTION_
export function getPropertyValueByAlias(sortOrder: number, item: UmbDocumentCollectionItemModel, alias: string) {
switch (alias) {
case 'contentTypeAlias':
return item.contentTypeAlias;
case 'createDate':
return item.createDate.toLocaleString();
case 'creator':

View File

@@ -50,7 +50,7 @@ export class UmbLanguageWorkspaceContext
path: 'edit/:unique',
component: UmbLanguageWorkspaceEditorElement,
setup: (_component, info) => {
this.removeControllerByAlias('isNewRedirectController');
this.removeUmbControllerByAlias('isNewRedirectController');
this.load(info.match.params.unique);
},
},

View File

@@ -45,7 +45,7 @@ export class UmbMediaTypeWorkspaceEditorElement extends UmbLitElement {
// TODO: Would be good with a more general way to bring focus to the name input.
(this.shadowRoot?.querySelector('#name') as HTMLElement)?.focus();
}
this.removeControllerByAlias('isNewRedirectController');
this.removeUmbControllerByAlias('isNewRedirectController');
},
'_observeIsNew',
);

View File

@@ -48,7 +48,7 @@ export class UmbMemberTypeWorkspaceEditorElement extends UmbLitElement {
// TODO: Would be good with a more general way to bring focus to the name input.
(this.shadowRoot?.querySelector('#name') as HTMLElement)?.focus();
}
this.removeControllerByAlias('_observeIsNew');
this.removeUmbControllerByAlias('_observeIsNew');
},
'_observeIsNew',
);

View File

@@ -26,7 +26,7 @@ export class UmbExtensionInitializer extends UmbControllerBase {
}
hostDisconnected(): void {
this.removeControllerByAlias('_observeExtensions');
this.removeUmbControllerByAlias('_observeExtensions');
}
async #loadLocalPackages() {

View File

@@ -34,7 +34,7 @@ export class UmbUserGroupRefElement extends UmbElementMixin(UUIRefNodeElement) {
'userPermissionLabels',
);
} else {
this.removeControllerByAlias('userPermissionLabels');
this.removeUmbControllerByAlias('userPermissionLabels');
}
}