Merge branch 'main' into feature/tree-paging
This commit is contained in:
@@ -65,6 +65,16 @@ declare class UmbClassMixinDeclaration extends EventTarget implements UmbClassMi
|
||||
callback: UmbContextCallback<ResultType>,
|
||||
): UmbContextConsumerController<BaseType, ResultType>;
|
||||
|
||||
/**
|
||||
* @description Retrieve a context. Notice this is a one time retrieving of a context, meaning if you expect this to be up to date with reality you should instead use the consumeContext method.
|
||||
* @param {string} contextAlias
|
||||
* @return {Promise<ContextType>} A Promise with the reference to the Context Api Instance
|
||||
* @memberof UmbClassMixin
|
||||
*/
|
||||
getContext<BaseType = unknown, ResultType extends BaseType = BaseType>(
|
||||
alias: string | UmbContextToken<BaseType, ResultType>,
|
||||
): Promise<ResultType>;
|
||||
|
||||
hasController(controller: UmbController): boolean;
|
||||
getControllers(filterMethod: (ctrl: UmbController) => boolean): UmbController[];
|
||||
addController(controller: UmbController): void;
|
||||
@@ -129,6 +139,17 @@ export const UmbClassMixin = <T extends ClassConstructor>(superClass: T) => {
|
||||
return new UmbContextConsumerController(this, contextAlias, callback);
|
||||
}
|
||||
|
||||
async getContext<BaseType = unknown, ResultType extends BaseType = BaseType>(
|
||||
contextAlias: string | UmbContextToken<BaseType, ResultType>,
|
||||
): Promise<ResultType> {
|
||||
const controller = new UmbContextConsumerController(this, contextAlias);
|
||||
const promise = controller.asPromise().then((result) => {
|
||||
controller.destroy();
|
||||
return result;
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
public destroy(): void {
|
||||
if (this._host) {
|
||||
this._host.removeController(this);
|
||||
|
||||
@@ -17,7 +17,7 @@ export class UmbContextConsumerController<BaseType = unknown, ResultType extends
|
||||
constructor(
|
||||
host: UmbControllerHost,
|
||||
contextAlias: string | UmbContextToken<BaseType, ResultType>,
|
||||
callback: UmbContextCallback<ResultType>,
|
||||
callback?: UmbContextCallback<ResultType>,
|
||||
) {
|
||||
super(host.getHostElement(), contextAlias, callback);
|
||||
this.#host = host;
|
||||
|
||||
@@ -1 +1 @@
|
||||
export type UmbControllerAlias = string | symbol | undefined;
|
||||
export type UmbControllerAlias = string | number | symbol | undefined;
|
||||
|
||||
@@ -6,9 +6,9 @@ import type { UmbControllerHost } from './controller-host.interface.js';
|
||||
import { customElement } from '@umbraco-cms/backoffice/external/lit';
|
||||
|
||||
@customElement('test-my-controller-host')
|
||||
export class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {}
|
||||
class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {}
|
||||
|
||||
export class UmbTestControllerImplementation extends UmbControllerHostMixin(class {}) {
|
||||
class UmbTestControllerImplementation extends UmbControllerHostMixin(class {}) {
|
||||
testIsConnected = false;
|
||||
testIsDestroyed = false;
|
||||
|
||||
@@ -47,9 +47,7 @@ export class UmbTestControllerImplementation extends UmbControllerHostMixin(clas
|
||||
}
|
||||
|
||||
describe('UmbController', () => {
|
||||
type NewType = UmbControllerHostElement;
|
||||
|
||||
let hostElement: NewType;
|
||||
let hostElement: UmbControllerHostElement;
|
||||
|
||||
beforeEach(() => {
|
||||
hostElement = document.createElement('test-my-controller-host') as UmbControllerHostElement;
|
||||
|
||||
@@ -32,6 +32,9 @@ export declare class UmbElement extends UmbControllerHostElement {
|
||||
alias: string | UmbContextToken<BaseType, ResultType>,
|
||||
callback: UmbContextCallback<ResultType>,
|
||||
): UmbContextConsumerController<BaseType, ResultType>;
|
||||
getContext<BaseType = unknown, ResultType extends BaseType = BaseType>(
|
||||
alias: string | UmbContextToken<BaseType, ResultType>,
|
||||
): Promise<ResultType>;
|
||||
/**
|
||||
* Use the UmbLocalizeController to localize your element.
|
||||
* @see UmbLocalizationController
|
||||
@@ -86,6 +89,24 @@ export const UmbElementMixin = <T extends HTMLElementConstructor>(superClass: T)
|
||||
return new UmbContextConsumerController(this, alias, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Setup a subscription for a context. The callback is called when the context is resolved.
|
||||
* @param {string} contextAlias
|
||||
* @param {method} callback Callback method called when context is resolved.
|
||||
* @return {UmbContextConsumerController} Reference to a Context Consumer Controller instance
|
||||
* @memberof UmbElementMixin
|
||||
*/
|
||||
async getContext<BaseType = unknown, ResultType extends BaseType = BaseType>(
|
||||
contextAlias: string | UmbContextToken<BaseType, ResultType>,
|
||||
): Promise<ResultType> {
|
||||
const controller = new UmbContextConsumerController(this, contextAlias);
|
||||
const promise = controller.asPromise().then((result) => {
|
||||
controller.destroy();
|
||||
return result;
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
super.destroy();
|
||||
(this.localize as any) = undefined;
|
||||
|
||||
@@ -16,7 +16,7 @@ import { UmbSwitchCondition } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
|
||||
@customElement('umb-test-controller-host')
|
||||
export class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {}
|
||||
class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {}
|
||||
|
||||
class UmbTestExtensionController extends UmbBaseExtensionInitializer {
|
||||
constructor(
|
||||
|
||||
@@ -130,6 +130,8 @@ export abstract class UmbBaseExtensionsInitializer<
|
||||
|
||||
#notifyChange = () => {
|
||||
this.#changeDebounce = undefined;
|
||||
// This means that we have been destroyed:
|
||||
if (this.#permittedExts === undefined) return;
|
||||
|
||||
// The final list of permitted extensions to be displayed, this will be stripped from extensions that are overwritten by another extension and sorted accordingly.
|
||||
this.#exposedPermittedExts = [...this.#permittedExts];
|
||||
|
||||
@@ -9,9 +9,9 @@ import { customElement, html } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { type ManifestSection, UmbSwitchCondition } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
@customElement('umb-test-controller-host')
|
||||
export class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {}
|
||||
class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {}
|
||||
|
||||
export class UmbTestApiController extends UmbBaseController {
|
||||
class UmbTestApiController extends UmbBaseController {
|
||||
public i_am_test_api_controller = true;
|
||||
|
||||
constructor(host: UmbControllerHost) {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { expect, fixture } from '@open-wc/testing';
|
||||
import { UmbExtensionRegistry } from '../registry/extension.registry.js';
|
||||
import { UmbExtensionElementInitializer } from './index.js';
|
||||
import type { UmbControllerHostElement} from '@umbraco-cms/backoffice/controller-api';
|
||||
import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { UmbControllerHostElementMixin } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { customElement, html } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { type ManifestSection, UmbSwitchCondition } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
@customElement('umb-test-controller-host')
|
||||
export class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {}
|
||||
class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {}
|
||||
|
||||
describe('UmbExtensionElementController', () => {
|
||||
describe('Manifest without conditions', () => {
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import { expect } from '@open-wc/testing';
|
||||
import { UmbObjectState } from './states/object-state.js';
|
||||
import { UmbObserverController } from './observer.controller.js';
|
||||
import { customElement } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbControllerHostElementMixin } from '@umbraco-cms/backoffice/controller-api';
|
||||
|
||||
@customElement('test-my-observer-controller-host')
|
||||
class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {}
|
||||
|
||||
describe('UmbObserverController', () => {
|
||||
describe('Observer Controllers against other Observer Controllers', () => {
|
||||
let hostElement: UmbTestControllerHostElement;
|
||||
|
||||
beforeEach(() => {
|
||||
hostElement = document.createElement('test-my-observer-controller-host') as UmbTestControllerHostElement;
|
||||
});
|
||||
|
||||
it('controller is replaced by another controller using the same string as controller-alias', () => {
|
||||
const state = new UmbObjectState(undefined);
|
||||
const observable = state.asObservable();
|
||||
|
||||
const callbackMethod = (state: unknown) => {};
|
||||
|
||||
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;
|
||||
});
|
||||
|
||||
it('controller is replaced by another controller using the the same symbol as controller-alias', () => {
|
||||
const state = new UmbObjectState(undefined);
|
||||
const observable = state.asObservable();
|
||||
|
||||
const callbackMethod = (state: unknown) => {};
|
||||
|
||||
const mySymbol = Symbol();
|
||||
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;
|
||||
});
|
||||
|
||||
it('controller is replacing another controller when using the same callback method and no controller-alias', () => {
|
||||
const state = new UmbObjectState(undefined);
|
||||
const observable = state.asObservable();
|
||||
|
||||
const callbackMethod = (state: unknown) => {};
|
||||
|
||||
const firstCtrl = new UmbObserverController(hostElement, observable, callbackMethod);
|
||||
const secondCtrl = new UmbObserverController(hostElement, observable, callbackMethod);
|
||||
|
||||
expect(hostElement.hasController(firstCtrl)).to.be.false;
|
||||
expect(hostElement.hasController(secondCtrl)).to.be.true;
|
||||
});
|
||||
|
||||
it('controller is NOT replacing another controller when using a null for controller-alias', () => {
|
||||
const state = new UmbObjectState(undefined);
|
||||
const observable = state.asObservable();
|
||||
|
||||
const callbackMethod = (state: unknown) => {};
|
||||
|
||||
const firstCtrl = new UmbObserverController(hostElement, observable, callbackMethod, null);
|
||||
const secondCtrl = new UmbObserverController(hostElement, observable, callbackMethod, null);
|
||||
|
||||
expect(hostElement.hasController(firstCtrl)).to.be.true;
|
||||
expect(hostElement.hasController(secondCtrl)).to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,5 @@
|
||||
import { type ObserverCallback, UmbObserver } from './observer.js';
|
||||
import { simpleHashCode } from './utils/simple-hash-code.function.js';
|
||||
import type { Observable } from '@umbraco-cms/backoffice/external/rxjs';
|
||||
import type { UmbController, UmbControllerAlias, UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
|
||||
@@ -14,11 +15,12 @@ export class UmbObserverController<T = unknown> extends UmbObserver<T> implement
|
||||
host: UmbControllerHost,
|
||||
source: Observable<T>,
|
||||
callback: ObserverCallback<T>,
|
||||
alias?: UmbControllerAlias,
|
||||
alias?: UmbControllerAlias | null,
|
||||
) {
|
||||
super(source, callback);
|
||||
this.#host = host;
|
||||
this.#alias = alias;
|
||||
// Fallback to use a hash of the provided method, but only if the alias is undefined.
|
||||
this.#alias = alias ?? (alias === undefined ? simpleHashCode(callback.toString()) : undefined);
|
||||
|
||||
// Lets check if controller is already here:
|
||||
// No we don't want this, as multiple different controllers might be looking at the same source.
|
||||
|
||||
@@ -8,3 +8,4 @@ export * from './naive-object-comparison.function.js';
|
||||
export * from './observe-multiple.function.js';
|
||||
export * from './partial-update-frozen-array.function.js';
|
||||
export * from './push-to-unique-array.function.js';
|
||||
export * from './simple-hash-code.function.js';
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Returns a hash code from a string
|
||||
* @param {String} str - The string to hash.
|
||||
* @return {Number} - A 32bit integer
|
||||
*/
|
||||
export function simpleHashCode(str: string) {
|
||||
let hash = 0,
|
||||
i = 0;
|
||||
const len = str.length;
|
||||
while (i < len) {
|
||||
hash = (hash << 5) - hash + str.charCodeAt(i++);
|
||||
hash |= 0; // Convert to 32bit integer
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api';
|
||||
import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
|
||||
import { UMB_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/property';
|
||||
import { UMB_CONFIRM_MODAL, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
|
||||
import { umbConfirmModal } from '@umbraco-cms/backoffice/modal';
|
||||
export class UmbBlockGridAreaConfigEntryContext
|
||||
extends UmbContextBase<UmbBlockGridAreaConfigEntryContext>
|
||||
implements UmbBlockGridScalableContext
|
||||
@@ -86,19 +86,14 @@ export class UmbBlockGridAreaConfigEntryContext
|
||||
);
|
||||
}
|
||||
|
||||
requestDelete() {
|
||||
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, async (modalManager) => {
|
||||
const modalContext = modalManager.open(UMB_CONFIRM_MODAL, {
|
||||
data: {
|
||||
headline: `Delete ${this.alias}`,
|
||||
content: 'Are you sure you want to delete this Area?',
|
||||
confirmLabel: 'Delete',
|
||||
color: 'danger',
|
||||
},
|
||||
});
|
||||
await modalContext.onSubmit();
|
||||
this.delete();
|
||||
async requestDelete() {
|
||||
await umbConfirmModal(this, {
|
||||
headline: `Delete ${this.alias}`,
|
||||
content: 'Are you sure you want to delete this Area?',
|
||||
confirmLabel: 'Delete',
|
||||
color: 'danger',
|
||||
});
|
||||
this.delete();
|
||||
}
|
||||
public delete() {
|
||||
if (!this.#areaKey || !this.#propertyContext) return;
|
||||
|
||||
@@ -244,33 +244,32 @@ export class UmbBlockGridEntriesContext
|
||||
// Area entries:
|
||||
if (!this.#areaType) return [];
|
||||
|
||||
if (this.#areaType.specifiedAllowance && this.#areaType.specifiedAllowance.length > 0) {
|
||||
return this.#areaType.specifiedAllowance
|
||||
.flatMap((permission) => {
|
||||
if (permission.groupKey) {
|
||||
return (
|
||||
this._manager
|
||||
?.getBlockTypes()
|
||||
.filter(
|
||||
(blockType) => blockType.groupKey === permission.groupKey && blockType.allowInAreas === true,
|
||||
) ?? []
|
||||
);
|
||||
} else if (permission.elementTypeKey) {
|
||||
return (
|
||||
this._manager?.getBlockTypes().filter((x) => x.contentElementTypeKey === permission.elementTypeKey) ??
|
||||
[]
|
||||
);
|
||||
}
|
||||
return [];
|
||||
})
|
||||
.filter((v, i, a) => a.find((x) => x.contentElementTypeKey === v.contentElementTypeKey) === undefined);
|
||||
if (this.#areaType.specifiedAllowance && this.#areaType.specifiedAllowance?.length > 0) {
|
||||
return (
|
||||
this.#areaType.specifiedAllowance
|
||||
.flatMap((permission) => {
|
||||
if (permission.groupKey) {
|
||||
return (
|
||||
this._manager?.getBlockTypes().filter((blockType) => blockType.groupKey === permission.groupKey) ?? []
|
||||
);
|
||||
} else if (permission.elementTypeKey) {
|
||||
return (
|
||||
this._manager?.getBlockTypes().filter((x) => x.contentElementTypeKey === permission.elementTypeKey) ??
|
||||
[]
|
||||
);
|
||||
}
|
||||
return [];
|
||||
})
|
||||
// Remove duplicates:
|
||||
.filter((v, i, a) => a.findIndex((x) => x.contentElementTypeKey === v.contentElementTypeKey) === i)
|
||||
);
|
||||
}
|
||||
|
||||
// No specific permissions setup, so we will fallback to items allowed in areas:
|
||||
return this._manager.getBlockTypes().filter((x) => x.allowInAreas);
|
||||
}
|
||||
// If no AreaKey, then we are representing the items of the root:
|
||||
|
||||
// Root entries:
|
||||
// If no AreaKey, then we are in the root, looking for items allowed as root:
|
||||
return this._manager.getBlockTypes().filter((x) => x.allowAtRoot);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { UmbBlockTypeBaseModel } from '../../types.js';
|
||||
import {
|
||||
UMB_CONFIRM_MODAL,
|
||||
UMB_DOCUMENT_TYPE_PICKER_MODAL,
|
||||
UMB_MODAL_MANAGER_CONTEXT,
|
||||
umbConfirmModal,
|
||||
} from '@umbraco-cms/backoffice/modal';
|
||||
import '../block-type-card/index.js';
|
||||
import { css, html, customElement, property, state, repeat } from '@umbraco-cms/backoffice/external/lit';
|
||||
@@ -42,35 +42,33 @@ export class UmbInputBlockTypeElement<
|
||||
});
|
||||
}
|
||||
|
||||
create() {
|
||||
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, async (modalManager) => {
|
||||
if (modalManager) {
|
||||
// TODO: Make as mode for the Picker Modal, so the click to select immediately submits the modal(And in that mode we do not want to see a Submit button).
|
||||
const modalContext = modalManager.open(UMB_DOCUMENT_TYPE_PICKER_MODAL, {
|
||||
data: {
|
||||
hideTreeRoot: true,
|
||||
multiple: false,
|
||||
pickableFilter: (docType) =>
|
||||
// Only pick elements:
|
||||
docType.isElement &&
|
||||
// Prevent picking the an already used element type:
|
||||
this.#filter &&
|
||||
this.#filter.find((x) => x.contentElementTypeKey === docType.unique) === undefined,
|
||||
},
|
||||
});
|
||||
async create() {
|
||||
const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
|
||||
|
||||
const modalValue = await modalContext?.onSubmit();
|
||||
const selectedElementType = modalValue.selection[0];
|
||||
|
||||
if (selectedElementType) {
|
||||
this.dispatchEvent(new CustomEvent('create', { detail: { contentElementTypeKey: selectedElementType } }));
|
||||
}
|
||||
}
|
||||
// TODO: Make as mode for the Picker Modal, so the click to select immediately submits the modal(And in that mode we do not want to see a Submit button).
|
||||
const modalContext = modalManager.open(UMB_DOCUMENT_TYPE_PICKER_MODAL, {
|
||||
data: {
|
||||
hideTreeRoot: true,
|
||||
multiple: false,
|
||||
pickableFilter: (docType) =>
|
||||
// Only pick elements:
|
||||
docType.isElement &&
|
||||
// Prevent picking the an already used element type:
|
||||
this.#filter &&
|
||||
this.#filter.find((x) => x.contentElementTypeKey === docType.unique) === undefined,
|
||||
},
|
||||
});
|
||||
|
||||
const modalValue = await modalContext?.onSubmit();
|
||||
const selectedElementType = modalValue.selection[0];
|
||||
|
||||
if (selectedElementType) {
|
||||
this.dispatchEvent(new CustomEvent('create', { detail: { contentElementTypeKey: selectedElementType } }));
|
||||
}
|
||||
}
|
||||
|
||||
deleteItem(contentElementTypeKey: string) {
|
||||
this.value = this._items.filter((x) => x.contentElementTypeKey !== contentElementTypeKey);
|
||||
this.value = this.value.filter((x) => x.contentElementTypeKey !== contentElementTypeKey);
|
||||
this.dispatchEvent(new UmbChangeEvent());
|
||||
}
|
||||
|
||||
@@ -78,20 +76,14 @@ export class UmbInputBlockTypeElement<
|
||||
return undefined;
|
||||
}
|
||||
|
||||
#onRequestDelete(item: BlockType) {
|
||||
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, async (modalManager) => {
|
||||
const modalContext = modalManager.open(UMB_CONFIRM_MODAL, {
|
||||
data: {
|
||||
color: 'danger',
|
||||
headline: `Remove [TODO: Get name]?`,
|
||||
content: 'Are you sure you want to remove this block type?',
|
||||
confirmLabel: 'Remove',
|
||||
},
|
||||
});
|
||||
|
||||
await modalContext?.onSubmit();
|
||||
this.deleteItem(item.contentElementTypeKey);
|
||||
async #onRequestDelete(item: BlockType) {
|
||||
await umbConfirmModal(this, {
|
||||
color: 'danger',
|
||||
headline: `Remove [TODO: Get name]?`,
|
||||
content: 'Are you sure you want to remove this block type?',
|
||||
confirmLabel: 'Remove',
|
||||
});
|
||||
this.deleteItem(item.contentElementTypeKey);
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -109,7 +101,7 @@ export class UmbInputBlockTypeElement<
|
||||
.href="${this.workspacePath}/edit/${block.contentElementTypeKey}"
|
||||
.contentElementTypeKey=${block.contentElementTypeKey}>
|
||||
<uui-action-bar slot="actions">
|
||||
<uui-button @click=${this.#onRequestDelete} label="Remove block">
|
||||
<uui-button @click=${() => this.#onRequestDelete(block)} label="Remove block">
|
||||
<uui-icon name="icon-trash"></uui-icon>
|
||||
</uui-button>
|
||||
</uui-action-bar>
|
||||
|
||||
@@ -7,7 +7,7 @@ import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { UmbNumberState, UmbObjectState, UmbStringState } from '@umbraco-cms/backoffice/observable-api';
|
||||
import { encodeFilePath } from '@umbraco-cms/backoffice/utils';
|
||||
import { UMB_CONFIRM_MODAL, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
|
||||
import { umbConfirmModal } from '@umbraco-cms/backoffice/modal';
|
||||
import type { UmbContentTypeModel } from '@umbraco-cms/backoffice/content-type';
|
||||
import type { Observable } from '@umbraco-cms/backoffice/external/rxjs';
|
||||
|
||||
@@ -353,20 +353,16 @@ export abstract class UmbBlockEntryContext<
|
||||
window.location.href = this.#generateWorkspaceEditSettingsPath(this.#workspacePath.value);
|
||||
}
|
||||
|
||||
requestDelete() {
|
||||
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, async (modalManager) => {
|
||||
const modalContext = modalManager.open(UMB_CONFIRM_MODAL, {
|
||||
data: {
|
||||
headline: `Delete ${this.getLabel()}`,
|
||||
content: 'Are you sure you want to delete this [INSERT BLOCK TYPE NAME]?',
|
||||
confirmLabel: 'Delete',
|
||||
color: 'danger',
|
||||
},
|
||||
});
|
||||
await modalContext.onSubmit();
|
||||
this.delete();
|
||||
async requestDelete() {
|
||||
await umbConfirmModal(this, {
|
||||
headline: `Delete ${this.getLabel()}`,
|
||||
content: 'Are you sure you want to delete this [INSERT BLOCK TYPE NAME]?',
|
||||
confirmLabel: 'Delete',
|
||||
color: 'danger',
|
||||
});
|
||||
this.delete();
|
||||
}
|
||||
|
||||
public delete() {
|
||||
if (!this._entries) return;
|
||||
const contentUdi = this._layout.value?.contentUdi;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { UMB_BLOCK_WORKSPACE_MODAL } from '../../workspace/index.js';
|
||||
import type { UmbBlockCatalogueModalData, UmbBlockCatalogueModalValue } from '@umbraco-cms/backoffice/block';
|
||||
import type { UmbBlockTypeGroup, UmbBlockTypeWithGroupKey } from '@umbraco-cms/backoffice/block-type';
|
||||
import type { UmbBlockCatalogueModalData, UmbBlockCatalogueModalValue } from '@umbraco-cms/backoffice/block';
|
||||
import { css, html, customElement, state, repeat, nothing } from '@umbraco-cms/backoffice/external/lit';
|
||||
import type { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui';
|
||||
import {
|
||||
UMB_MODAL_CONTEXT,
|
||||
UmbModalBaseElement,
|
||||
@@ -15,14 +16,19 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement<
|
||||
UmbBlockCatalogueModalData,
|
||||
UmbBlockCatalogueModalValue
|
||||
> {
|
||||
@state()
|
||||
//
|
||||
private _search = '';
|
||||
|
||||
private _groupedBlocks: Array<{ name?: string; blocks: Array<UmbBlockTypeWithGroupKey> }> = [];
|
||||
|
||||
@state()
|
||||
_openClipboard?: boolean;
|
||||
private _openClipboard?: boolean;
|
||||
|
||||
@state()
|
||||
_workspacePath?: string;
|
||||
private _workspacePath?: string;
|
||||
|
||||
@state()
|
||||
private _filtered: Array<{ name?: string; blocks: Array<UmbBlockTypeWithGroupKey> }> = [];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@@ -61,6 +67,23 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement<
|
||||
}));
|
||||
|
||||
this._groupedBlocks = [{ blocks: noGroupBlocks }, ...grouped];
|
||||
this.#updateFiltered();
|
||||
}
|
||||
|
||||
#updateFiltered() {
|
||||
if (this._search.length === 0) {
|
||||
this._filtered = this._groupedBlocks;
|
||||
} else {
|
||||
const search = this._search.toLowerCase();
|
||||
this._filtered = this._groupedBlocks.map((group) => {
|
||||
return { ...group, blocks: group.blocks.filter((block) => block.label?.toLocaleLowerCase().includes(search)) };
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#onSearch(e: UUIInputEvent) {
|
||||
this._search = e.target.value as string;
|
||||
this.#updateFiltered();
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -85,28 +108,35 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement<
|
||||
|
||||
#renderCreateEmpty() {
|
||||
return html`
|
||||
${this._groupedBlocks
|
||||
? this._groupedBlocks.map(
|
||||
(group) => html`
|
||||
${group.name && group.name !== '' ? html`<h4>${group.name}</h4>` : nothing}
|
||||
<div class="blockGroup">
|
||||
${repeat(
|
||||
group.blocks,
|
||||
(block) => block.contentElementTypeKey,
|
||||
(block) => html`
|
||||
<umb-block-type-card
|
||||
.name=${block.label}
|
||||
.iconColor=${block.iconColor}
|
||||
.backgroundColor=${block.backgroundColor}
|
||||
.contentElementTypeKey=${block.contentElementTypeKey}
|
||||
.href="${this._workspacePath}create/${block.contentElementTypeKey}">
|
||||
</umb-block-type-card>
|
||||
`,
|
||||
)}
|
||||
</div>
|
||||
`,
|
||||
)
|
||||
: ''}
|
||||
${this.data?.blocks && this.data.blocks.length > 8
|
||||
? html`<uui-input
|
||||
id="search"
|
||||
@input=${this.#onSearch}
|
||||
label=${this.localize.term('general_search')}
|
||||
placeholder=${this.localize.term('placeholders_search')}>
|
||||
<uui-icon name="icon-search" slot="prepend"></uui-icon>
|
||||
</uui-input>`
|
||||
: nothing}
|
||||
${this._filtered.map(
|
||||
(group) => html`
|
||||
${group.name && group.name !== '' ? html`<h4>${group.name}</h4>` : nothing}
|
||||
<div class="blockGroup">
|
||||
${repeat(
|
||||
group.blocks,
|
||||
(block) => block.contentElementTypeKey,
|
||||
(block) => html`
|
||||
<umb-block-type-card
|
||||
.name=${block.label}
|
||||
.iconColor=${block.iconColor}
|
||||
.backgroundColor=${block.backgroundColor}
|
||||
.contentElementTypeKey=${block.contentElementTypeKey}
|
||||
.href="${this._workspacePath}create/${block.contentElementTypeKey}">
|
||||
</umb-block-type-card>
|
||||
`,
|
||||
)}
|
||||
</div>
|
||||
`,
|
||||
)}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -133,6 +163,14 @@ export class UmbBlockCatalogueModalElement extends UmbModalBaseElement<
|
||||
|
||||
static styles = [
|
||||
css`
|
||||
#search {
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
margin-bottom: var(--uui-size-layout-1);
|
||||
}
|
||||
#search uui-icon {
|
||||
padding-left: var(--uui-size-space-3);
|
||||
}
|
||||
.blockGroup {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
|
||||
@@ -2,12 +2,12 @@ import { expect } from '@open-wc/testing';
|
||||
import { UmbCollectionViewManager } from './collection-view.manager.js';
|
||||
import { Observable } from '@umbraco-cms/backoffice/external/rxjs';
|
||||
import { UmbControllerHostElementMixin } from '@umbraco-cms/backoffice/controller-api';
|
||||
import type { ManifestCollectionView} from '@umbraco-cms/backoffice/extension-registry';
|
||||
import type { ManifestCollectionView } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { customElement } from '@umbraco-cms/backoffice/external/lit';
|
||||
|
||||
@customElement('test-my-controller-host')
|
||||
export class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {}
|
||||
class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {}
|
||||
|
||||
const VIEW_1_ALIAS = 'UmbTest.CollectionView.1';
|
||||
const VIEW_2_ALIAS = 'UmbTest.CollectionView.2';
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { css, html, nothing, customElement, property, query, ifDefined } from '@umbraco-cms/backoffice/external/lit';
|
||||
import type { UUIColorPickerElement, UUIInputElement, UUIInputEvent } from '@umbraco-cms/backoffice/external/uui';
|
||||
import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui';
|
||||
import type { UmbModalManagerContext } from '@umbraco-cms/backoffice/modal';
|
||||
import { UMB_MODAL_MANAGER_CONTEXT, UMB_CONFIRM_MODAL } from '@umbraco-cms/backoffice/modal';
|
||||
import { umbConfirmModal } from '@umbraco-cms/backoffice/modal';
|
||||
import { UmbChangeEvent, UmbInputEvent, UmbDeleteEvent } from '@umbraco-cms/backoffice/event';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
|
||||
@@ -38,32 +37,18 @@ export class UmbMultipleColorPickerItemInputElement extends FormControlMixin(Umb
|
||||
@query('#color')
|
||||
protected _colorPicker!: UUIColorPickerElement;
|
||||
|
||||
private _modalContext?: UmbModalManagerContext;
|
||||
|
||||
@property({ type: Boolean })
|
||||
showLabels = true;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => {
|
||||
this._modalContext = instance;
|
||||
});
|
||||
}
|
||||
|
||||
#onDelete() {
|
||||
const modalContext = this._modalContext?.open(UMB_CONFIRM_MODAL, {
|
||||
data: {
|
||||
headline: `${this.localize.term('actions_delete')} ${this.value || ''}`,
|
||||
content: this.localize.term('content_nestedContentDeleteItem'),
|
||||
color: 'danger',
|
||||
confirmLabel: this.localize.term('actions_delete'),
|
||||
},
|
||||
async #onDelete() {
|
||||
await umbConfirmModal(this, {
|
||||
headline: `${this.localize.term('actions_delete')} ${this.value || ''}`,
|
||||
content: this.localize.term('content_nestedContentDeleteItem'),
|
||||
color: 'danger',
|
||||
confirmLabel: this.localize.term('actions_delete'),
|
||||
});
|
||||
|
||||
modalContext?.onSubmit().then(() => {
|
||||
this.dispatchEvent(new UmbDeleteEvent());
|
||||
});
|
||||
this.dispatchEvent(new UmbDeleteEvent());
|
||||
}
|
||||
|
||||
#onLabelInput(event: UUIInputEvent) {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { css, html, nothing, customElement, property, query } from '@umbraco-cms/backoffice/external/lit';
|
||||
import type { UUIInputElement, UUIInputEvent } from '@umbraco-cms/backoffice/external/uui';
|
||||
import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui';
|
||||
import type { UmbModalManagerContext } from '@umbraco-cms/backoffice/modal';
|
||||
import { UMB_MODAL_MANAGER_CONTEXT, UMB_CONFIRM_MODAL } from '@umbraco-cms/backoffice/modal';
|
||||
import { umbConfirmModal } from '@umbraco-cms/backoffice/modal';
|
||||
import { UmbChangeEvent, UmbInputEvent, UmbDeleteEvent } from '@umbraco-cms/backoffice/event';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
|
||||
@@ -32,29 +31,15 @@ export class UmbInputMultipleTextStringItemElement extends FormControlMixin(UmbL
|
||||
@query('#input')
|
||||
protected _input?: UUIInputElement;
|
||||
|
||||
private _modalContext?: UmbModalManagerContext;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => {
|
||||
this._modalContext = instance;
|
||||
});
|
||||
}
|
||||
|
||||
#onDelete() {
|
||||
const modalContext = this._modalContext?.open(UMB_CONFIRM_MODAL, {
|
||||
data: {
|
||||
headline: `Delete ${this.value || 'item'}`,
|
||||
content: 'Are you sure you want to delete this item?',
|
||||
color: 'danger',
|
||||
confirmLabel: 'Delete',
|
||||
},
|
||||
async #onDelete() {
|
||||
await umbConfirmModal(this, {
|
||||
headline: `Delete ${this.value || 'item'}`,
|
||||
content: 'Are you sure you want to delete this item?',
|
||||
color: 'danger',
|
||||
confirmLabel: 'Delete',
|
||||
});
|
||||
|
||||
modalContext?.onSubmit().then(() => {
|
||||
this.dispatchEvent(new UmbDeleteEvent());
|
||||
});
|
||||
this.dispatchEvent(new UmbDeleteEvent());
|
||||
}
|
||||
|
||||
#onInput(event: UUIInputEvent) {
|
||||
|
||||
@@ -1,39 +1,22 @@
|
||||
import { UmbEntityActionBase } from '../../entity-action.js';
|
||||
import { UmbContextConsumerController } from '@umbraco-cms/backoffice/context-api';
|
||||
import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
|
||||
import type { UmbModalManagerContext} from '@umbraco-cms/backoffice/modal';
|
||||
import { UMB_MODAL_MANAGER_CONTEXT, UMB_CONFIRM_MODAL } from '@umbraco-cms/backoffice/modal';
|
||||
import { umbConfirmModal } from '@umbraco-cms/backoffice/modal';
|
||||
import type { UmbDetailRepository, UmbItemRepository } from '@umbraco-cms/backoffice/repository';
|
||||
|
||||
export class UmbDeleteEntityAction<
|
||||
T extends UmbDetailRepository<any> & UmbItemRepository<any>,
|
||||
> extends UmbEntityActionBase<T> {
|
||||
#modalManager?: UmbModalManagerContext;
|
||||
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string, entityType: string) {
|
||||
super(host, repositoryAlias, unique, entityType);
|
||||
|
||||
new UmbContextConsumerController(this._host, UMB_MODAL_MANAGER_CONTEXT, (instance) => {
|
||||
this.#modalManager = instance;
|
||||
});
|
||||
}
|
||||
|
||||
async execute() {
|
||||
if (!this.repository || !this.#modalManager) return;
|
||||
if (!this.repository) return;
|
||||
|
||||
// TOOD: add back when entity actions can support multiple repositories
|
||||
//const { data } = await this.repository.requestItems([this.unique]);
|
||||
|
||||
const modalContext = this.#modalManager.open(UMB_CONFIRM_MODAL, {
|
||||
data: {
|
||||
headline: `Delete`,
|
||||
content: 'Are you sure you want to delete this item?',
|
||||
color: 'danger',
|
||||
confirmLabel: 'Delete',
|
||||
},
|
||||
await umbConfirmModal(this._host, {
|
||||
headline: `Delete`,
|
||||
content: 'Are you sure you want to delete this item?',
|
||||
color: 'danger',
|
||||
confirmLabel: 'Delete',
|
||||
});
|
||||
|
||||
await modalContext.onSubmit();
|
||||
await this.repository?.delete(this.unique);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,10 @@
|
||||
import { UmbEntityActionBase } from '../../entity-action.js';
|
||||
import { UmbContextConsumerController } from '@umbraco-cms/backoffice/context-api';
|
||||
import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
|
||||
import type { UmbModalManagerContext} from '@umbraco-cms/backoffice/modal';
|
||||
import { UMB_MODAL_MANAGER_CONTEXT, UMB_CONFIRM_MODAL } from '@umbraco-cms/backoffice/modal';
|
||||
import { umbConfirmModal } from '@umbraco-cms/backoffice/modal';
|
||||
import type { UmbItemRepository } from '@umbraco-cms/backoffice/repository';
|
||||
|
||||
export class UmbTrashEntityAction<
|
||||
T extends UmbItemRepository<any> & { trash(unique: string): Promise<void> },
|
||||
> extends UmbEntityActionBase<T> {
|
||||
#modalContext?: UmbModalManagerContext;
|
||||
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string, entityType: string) {
|
||||
super(host, repositoryAlias, unique, entityType);
|
||||
|
||||
new UmbContextConsumerController(this._host, UMB_MODAL_MANAGER_CONTEXT, (instance) => {
|
||||
this.#modalContext = instance;
|
||||
});
|
||||
}
|
||||
|
||||
async execute() {
|
||||
if (!this.repository) return;
|
||||
|
||||
@@ -26,18 +13,14 @@ export class UmbTrashEntityAction<
|
||||
if (data) {
|
||||
const item = data[0];
|
||||
|
||||
const modalContext = this.#modalContext?.open(UMB_CONFIRM_MODAL, {
|
||||
data: {
|
||||
headline: `Trash ${item.name}`,
|
||||
content: 'Are you sure you want to move this item to the recycle bin?',
|
||||
color: 'danger',
|
||||
confirmLabel: 'Trash',
|
||||
},
|
||||
await umbConfirmModal(this._host, {
|
||||
headline: `Trash ${item.name}`,
|
||||
content: 'Are you sure you want to move this item to the recycle bin?',
|
||||
color: 'danger',
|
||||
confirmLabel: 'Trash',
|
||||
});
|
||||
|
||||
modalContext?.onSubmit().then(() => {
|
||||
this.repository?.trash(this.unique);
|
||||
});
|
||||
this.repository?.trash(this.unique);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,34 +2,20 @@ import { umbExtensionsRegistry } from '../../index.js';
|
||||
import type { ManifestBase } from '@umbraco-cms/backoffice/extension-api';
|
||||
import { html, customElement, property } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import type { UmbModalManagerContext } from '@umbraco-cms/backoffice/modal';
|
||||
import { UMB_CONFIRM_MODAL, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
|
||||
import { umbConfirmModal } from '@umbraco-cms/backoffice/modal';
|
||||
|
||||
@customElement('umb-extension-table-action-column-layout')
|
||||
export class UmbExtensionTableActionColumnLayoutElement extends UmbLitElement {
|
||||
@property({ attribute: false })
|
||||
value!: ManifestBase;
|
||||
|
||||
#modalContext?: UmbModalManagerContext;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => {
|
||||
this.#modalContext = instance;
|
||||
});
|
||||
}
|
||||
|
||||
async #removeExtension() {
|
||||
const modalContext = this.#modalContext?.open(UMB_CONFIRM_MODAL, {
|
||||
data: {
|
||||
headline: 'Unload extension',
|
||||
confirmLabel: 'Unload',
|
||||
content: html`<p>Are you sure you want to unload the extension <strong>${this.value.alias}</strong>?</p>`,
|
||||
color: 'danger',
|
||||
},
|
||||
await umbConfirmModal(this, {
|
||||
headline: 'Unload extension',
|
||||
confirmLabel: 'Unload',
|
||||
content: html`<p>Are you sure you want to unload the extension <strong>${this.value.alias}</strong>?</p>`,
|
||||
color: 'danger',
|
||||
});
|
||||
|
||||
await modalContext?.onSubmit();
|
||||
umbExtensionsRegistry.unregister(this.value.alias);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { ManifestApi } from '@umbraco-cms/backoffice/extension-api';
|
||||
import type { UmbItemStore, UmbStoreBase } from '@umbraco-cms/backoffice/store';
|
||||
import type { UmbItemStore } from '@umbraco-cms/backoffice/store';
|
||||
import type { UmbTreeStore } from '@umbraco-cms/backoffice/tree';
|
||||
|
||||
export interface ManifestStore extends ManifestApi<UmbStoreBase> {
|
||||
export interface ManifestStore extends ManifestApi<any> {
|
||||
type: 'store';
|
||||
}
|
||||
// TODO: TREE STORE TYPE PROBLEM: Provide a base tree item type?
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { UMB_CONFIRM_MODAL, type UmbConfirmModalData } from '../../token/confirm-modal.token.js';
|
||||
import { UMB_MODAL_MANAGER_CONTEXT } from '../../context/modal-manager.context.js';
|
||||
import { UmbBaseController } from '@umbraco-cms/backoffice/class-api';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
|
||||
export interface UmbConfirmModalArgs extends UmbConfirmModalData {}
|
||||
|
||||
export class UmbConfirmModalController extends UmbBaseController {
|
||||
async open(args: UmbConfirmModalArgs): Promise<void> {
|
||||
const modalManagerContext = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
|
||||
|
||||
const modalContext = modalManagerContext.open(UMB_CONFIRM_MODAL, {
|
||||
data: args,
|
||||
});
|
||||
|
||||
await modalContext.onSubmit().catch(() => {
|
||||
this.destroy();
|
||||
});
|
||||
|
||||
// This is a one time off, so we can destroy our selfs.
|
||||
this.destroy();
|
||||
|
||||
// Map back into UmbVariantId instances:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
export function umbConfirmModal(host: UmbControllerHost, args: UmbConfirmModalArgs) {
|
||||
return new UmbConfirmModalController(host).open(args);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { UmbModalContext } from './modal.context.js';
|
||||
import { UMB_MODAL_CONTEXT } from './modal.context.js';
|
||||
import type { UmbModalContext } from '../context/modal.context.js';
|
||||
import { UMB_MODAL_CONTEXT } from '../context/modal.context.js';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import type { ManifestModal } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { UmbModalToken } from './token/modal-token.js';
|
||||
import type { UmbModalToken } from '../token/modal-token.js';
|
||||
import { UmbModalContext, type UmbModalContextClassArgs } from './modal.context.js';
|
||||
import type { UUIModalSidebarSize } from '@umbraco-cms/backoffice/external/uui';
|
||||
import { UmbBasicState, appendToFrozenArray } from '@umbraco-cms/backoffice/observable-api';
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { UmbModalConfig, UmbModalType } from './modal-manager.context.js';
|
||||
import { UmbModalToken } from './token/modal-token.js';
|
||||
import { UmbModalToken } from '../token/modal-token.js';
|
||||
import type { IRouterSlot } from '@umbraco-cms/backoffice/external/router-slot';
|
||||
import type { UUIModalSidebarSize } from '@umbraco-cms/backoffice/external/uui';
|
||||
import { UmbId } from '@umbraco-cms/backoffice/id';
|
||||
@@ -81,6 +81,7 @@ export class UmbModalContext<ModalPreset extends object = object, ModalValue = a
|
||||
*/
|
||||
public submit() {
|
||||
this.#submitResolver?.(this.getValue());
|
||||
// TODO: Could we clean up this class here? (Example destroy the value state, and other things?)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,6 +91,7 @@ export class UmbModalContext<ModalPreset extends object = object, ModalValue = a
|
||||
*/
|
||||
public reject(reason?: UmbModalRejectReason) {
|
||||
this.#submitRejecter?.(reason);
|
||||
// TODO: Could we clean up this class here? (Example destroy the value state, and other things?)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1,10 +1,11 @@
|
||||
import './modal.element.js';
|
||||
import './component/modal.element.js';
|
||||
|
||||
export * from './modal-manager.context.js';
|
||||
export * from './modal.context.js';
|
||||
export * from './modal-route-registration.js';
|
||||
export * from './modal-route-registration.controller.js';
|
||||
export * from './context/modal-manager.context.js';
|
||||
export * from './context/modal.context.js';
|
||||
export * from './route-registration/modal-route-registration.js';
|
||||
export * from './route-registration/modal-route-registration.controller.js';
|
||||
export * from './token/index.js';
|
||||
export * from './modal.interfaces.js';
|
||||
export * from './modal-element.element.js';
|
||||
export * from './modal.element.js';
|
||||
export * from './types.js';
|
||||
export * from './component/modal-element.element.js';
|
||||
export * from './component/modal.element.js';
|
||||
export * from './common/confirm/confirm-modal.controller.js';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { UmbModalRouteRegistration } from './modal-route-registration.js';
|
||||
import type { UmbModalToken } from './token/index.js';
|
||||
import type { UmbModalToken } from '../token/index.js';
|
||||
import { UMB_ROUTE_CONTEXT } from '@umbraco-cms/backoffice/router';
|
||||
import type { UmbController, UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { UmbContextConsumerController } from '@umbraco-cms/backoffice/context-api';
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { UmbModalContext } from './modal.context.js';
|
||||
import type { UmbModalConfig, UmbModalManagerContext } from './modal-manager.context.js';
|
||||
import type { UmbModalToken } from './token/modal-token.js';
|
||||
import type { UmbModalContext } from '../context/modal.context.js';
|
||||
import type { UmbModalConfig, UmbModalManagerContext } from '../context/modal-manager.context.js';
|
||||
import type { UmbModalToken } from '../token/modal-token.js';
|
||||
import type { IRouterSlot } from '@umbraco-cms/backoffice/external/router-slot';
|
||||
import { encodeFolderName } from '@umbraco-cms/backoffice/router';
|
||||
import { UmbId } from '@umbraco-cms/backoffice/id';
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { UmbPickerModalValue, UmbTreePickerModalData } from '../modal.interfaces.js';
|
||||
import type { UmbPickerModalValue, UmbTreePickerModalData } from '../types.js';
|
||||
import { UmbModalToken } from './modal-token.js';
|
||||
import type { UmbUniqueTreeItemModel } from '@umbraco-cms/backoffice/tree';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { UmbModalConfig } from '../modal-manager.context.js';
|
||||
import type { UmbModalConfig } from '../context/modal-manager.context.js';
|
||||
|
||||
export interface UmbModalTokenDefaults<ModalDataType extends object = object, ModalValueType = unknown> {
|
||||
modal?: UmbModalConfig;
|
||||
|
||||
@@ -2,13 +2,8 @@ import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
|
||||
import { type UmbItemRepository, UmbRepositoryItemsManager } from '@umbraco-cms/backoffice/repository';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { UmbBaseController } from '@umbraco-cms/backoffice/class-api';
|
||||
import type {
|
||||
UmbModalManagerContext,
|
||||
UmbModalToken,
|
||||
UmbPickerModalData,
|
||||
UmbPickerModalValue,
|
||||
} from '@umbraco-cms/backoffice/modal';
|
||||
import { UMB_CONFIRM_MODAL, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
|
||||
import type { UmbModalToken, UmbPickerModalData, UmbPickerModalValue } from '@umbraco-cms/backoffice/modal';
|
||||
import { UMB_MODAL_MANAGER_CONTEXT, umbConfirmModal } from '@umbraco-cms/backoffice/modal';
|
||||
|
||||
export class UmbPickerInputContext<ItemType extends { name: string; unique: string }> extends UmbBaseController {
|
||||
// TODO: We are way too unsecure about the requirements for the Modal Token, as we have certain expectation for the data and value.
|
||||
@@ -16,10 +11,6 @@ export class UmbPickerInputContext<ItemType extends { name: string; unique: stri
|
||||
repository?: UmbItemRepository<ItemType>;
|
||||
#getUnique: (entry: ItemType) => string | undefined;
|
||||
|
||||
public modalManager?: UmbModalManagerContext;
|
||||
|
||||
#init: Promise<unknown>;
|
||||
|
||||
#itemManager;
|
||||
|
||||
selection;
|
||||
@@ -63,13 +54,6 @@ export class UmbPickerInputContext<ItemType extends { name: string; unique: stri
|
||||
|
||||
this.selection = this.#itemManager.uniques;
|
||||
this.selectedItems = this.#itemManager.items;
|
||||
|
||||
this.#init = Promise.all([
|
||||
this.#itemManager.init,
|
||||
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => {
|
||||
this.modalManager = instance;
|
||||
}).asPromise(),
|
||||
]);
|
||||
}
|
||||
|
||||
getSelection() {
|
||||
@@ -82,10 +66,9 @@ export class UmbPickerInputContext<ItemType extends { name: string; unique: stri
|
||||
}
|
||||
|
||||
async openPicker(pickerData?: Partial<UmbPickerModalData<ItemType>>) {
|
||||
await this.#init;
|
||||
if (!this.modalManager) throw new Error('Modal manager context is not initialized');
|
||||
|
||||
const modalContext = this.modalManager.open(this.modalAlias, {
|
||||
await this.#itemManager.init;
|
||||
const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
|
||||
const modalContext = modalManager.open(this.modalAlias, {
|
||||
data: {
|
||||
multiple: this._max === 1 ? false : true,
|
||||
...pickerData,
|
||||
@@ -105,16 +88,12 @@ export class UmbPickerInputContext<ItemType extends { name: string; unique: stri
|
||||
const item = this.#itemManager.getItems().find((item) => this.#getUnique(item) === unique);
|
||||
if (!item) throw new Error('Could not find item with unique: ' + unique);
|
||||
|
||||
const modalContext = this.modalManager?.open(UMB_CONFIRM_MODAL, {
|
||||
data: {
|
||||
color: 'danger',
|
||||
headline: `Remove ${item.name}?`,
|
||||
content: 'Are you sure you want to remove this item',
|
||||
confirmLabel: 'Remove',
|
||||
},
|
||||
await umbConfirmModal(this, {
|
||||
color: 'danger',
|
||||
headline: `Remove ${item.name}?`,
|
||||
content: 'Are you sure you want to remove this item',
|
||||
confirmLabel: 'Remove',
|
||||
});
|
||||
|
||||
await modalContext?.onSubmit();
|
||||
this.#removeItem(unique);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,38 +1,22 @@
|
||||
import { UmbEntityActionBase } from '../../../../entity-action/entity-action.js';
|
||||
import { UmbContextConsumerController } from '@umbraco-cms/backoffice/context-api';
|
||||
import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
|
||||
import type { UmbModalManagerContext} from '@umbraco-cms/backoffice/modal';
|
||||
import { UMB_MODAL_MANAGER_CONTEXT, UMB_CONFIRM_MODAL } from '@umbraco-cms/backoffice/modal';
|
||||
import { umbConfirmModal } from '@umbraco-cms/backoffice/modal';
|
||||
import type { UmbFolderRepository } from '@umbraco-cms/backoffice/tree';
|
||||
|
||||
export class UmbDeleteFolderEntityAction<T extends UmbFolderRepository> extends UmbEntityActionBase<T> {
|
||||
#modalContext?: UmbModalManagerContext;
|
||||
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string, entityType: string) {
|
||||
super(host, repositoryAlias, unique, entityType);
|
||||
|
||||
new UmbContextConsumerController(this._host, UMB_MODAL_MANAGER_CONTEXT, (instance) => {
|
||||
this.#modalContext = instance;
|
||||
});
|
||||
}
|
||||
|
||||
async execute() {
|
||||
if (!this.repository || !this.#modalContext) return;
|
||||
if (!this.repository) return;
|
||||
|
||||
const { data: folder } = await this.repository.request(this.unique);
|
||||
|
||||
if (folder) {
|
||||
// TODO: maybe we can show something about how many items are part of the folder?
|
||||
const modalContext = this.#modalContext.open(UMB_CONFIRM_MODAL, {
|
||||
data: {
|
||||
headline: `Delete folder ${folder.name}`,
|
||||
content: 'Are you sure you want to delete this folder?',
|
||||
color: 'danger',
|
||||
confirmLabel: 'Delete',
|
||||
},
|
||||
});
|
||||
|
||||
await modalContext.onSubmit();
|
||||
await umbConfirmModal(this._host, {
|
||||
headline: `Delete folder ${folder.name}`,
|
||||
content: 'Are you sure you want to delete this folder?',
|
||||
color: 'danger',
|
||||
confirmLabel: 'Delete',
|
||||
});
|
||||
await this.repository?.delete(this.unique);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { customElement } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbControllerHostElementMixin } from '@umbraco-cms/backoffice/controller-api';
|
||||
|
||||
@customElement('test-my-controller-host')
|
||||
export class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {}
|
||||
class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {}
|
||||
|
||||
describe('UmbSelectionManager', () => {
|
||||
let manager: UmbSelectionManager;
|
||||
|
||||
@@ -8,11 +8,11 @@ import { UmbArrayState, UmbBooleanState } from '@umbraco-cms/backoffice/observab
|
||||
* @export
|
||||
* @class UmbSelectionManager
|
||||
*/
|
||||
export class UmbSelectionManager extends UmbBaseController {
|
||||
export class UmbSelectionManager<ValueType = string | null> extends UmbBaseController {
|
||||
#selectable = new UmbBooleanState(false);
|
||||
public readonly selectable = this.#selectable.asObservable();
|
||||
|
||||
#selection = new UmbArrayState(<Array<string | null>>[], (x) => x);
|
||||
#selection = new UmbArrayState(<Array<ValueType>>[], (x) => x);
|
||||
public readonly selection = this.#selection.asObservable();
|
||||
|
||||
#multiple = new UmbBooleanState(false);
|
||||
@@ -51,10 +51,10 @@ export class UmbSelectionManager extends UmbBaseController {
|
||||
|
||||
/**
|
||||
* Sets the current selection.
|
||||
* @param {Array<string | null>} value
|
||||
* @param {Array<ValueType>} value
|
||||
* @memberof UmbSelectionManager
|
||||
*/
|
||||
public setSelection(value: Array<string | null>) {
|
||||
public setSelection(value: Array<ValueType>) {
|
||||
if (this.getSelectable() === false) return;
|
||||
if (value === undefined) throw new Error('Value cannot be undefined');
|
||||
const newSelection = this.getMultiple() ? value : value.slice(0, 1);
|
||||
@@ -87,20 +87,20 @@ export class UmbSelectionManager extends UmbBaseController {
|
||||
|
||||
/**
|
||||
* Toggles the given unique id in the current selection.
|
||||
* @param {(string | null)} unique
|
||||
* @param {(ValueType)} unique
|
||||
* @memberof UmbSelectionManager
|
||||
*/
|
||||
public toggleSelect(unique: string | null) {
|
||||
public toggleSelect(unique: ValueType) {
|
||||
if (this.getSelectable() === false) return;
|
||||
this.isSelected(unique) ? this.deselect(unique) : this.select(unique);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the given unique id to the current selection.
|
||||
* @param {(string | null)} unique
|
||||
* @param {(ValueType)} unique
|
||||
* @memberof UmbSelectionManager
|
||||
*/
|
||||
public select(unique: string | null) {
|
||||
public select(unique: ValueType) {
|
||||
if (this.getSelectable() === false) return;
|
||||
if (this.isSelected(unique)) return;
|
||||
const newSelection = this.getMultiple() ? [...this.getSelection(), unique] : [unique];
|
||||
@@ -110,10 +110,10 @@ export class UmbSelectionManager extends UmbBaseController {
|
||||
|
||||
/**
|
||||
* Removes the given unique id from the current selection.
|
||||
* @param {(string | null)} unique
|
||||
* @param {(ValueType)} unique
|
||||
* @memberof UmbSelectionManager
|
||||
*/
|
||||
public deselect(unique: string | null) {
|
||||
public deselect(unique: ValueType) {
|
||||
if (this.getSelectable() === false) return;
|
||||
const newSelection = this.getSelection().filter((x) => x !== unique);
|
||||
this.#selection.setValue(newSelection);
|
||||
@@ -122,11 +122,11 @@ export class UmbSelectionManager extends UmbBaseController {
|
||||
|
||||
/**
|
||||
* Returns true if the given unique id is selected.
|
||||
* @param {(string | null)} unique
|
||||
* @param {(ValueType)} unique
|
||||
* @return {*}
|
||||
* @memberof UmbSelectionManager
|
||||
*/
|
||||
public isSelected(unique: string | null) {
|
||||
public isSelected(unique: ValueType) {
|
||||
return this.getSelection().includes(unique);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { UmbLanguageDetailModel } from '@umbraco-cms/backoffice/language';
|
||||
|
||||
export interface UmbVariantModel {
|
||||
createDate: string | null;
|
||||
culture: string | null;
|
||||
@@ -5,3 +7,12 @@ export interface UmbVariantModel {
|
||||
segment: string | null;
|
||||
updateDate: string | null;
|
||||
}
|
||||
|
||||
export interface UmbVariantOptionModel<VariantType extends UmbVariantModel = UmbVariantModel> {
|
||||
variant?: VariantType;
|
||||
language: UmbLanguageDetailModel;
|
||||
/**
|
||||
* The unique identifier is a VariantId string.
|
||||
*/
|
||||
unique: string;
|
||||
}
|
||||
|
||||
@@ -1,32 +1,45 @@
|
||||
export type variantObject = {
|
||||
export type UmbObjectWithVariantProperties = {
|
||||
culture: string | null;
|
||||
segment: string | null;
|
||||
schedule?: { publishTime?: string | null; unpublishTime?: string | null };
|
||||
};
|
||||
|
||||
export function variantPropertiesObjectToString(variant: UmbObjectWithVariantProperties): string {
|
||||
// Currently a direct copy of the toString method of variantId.
|
||||
return (variant.culture || UMB_INVARIANT_CULTURE) + (variant.segment ? `_${variant.segment}` : '');
|
||||
}
|
||||
|
||||
export const UMB_INVARIANT_CULTURE = 'invariant';
|
||||
|
||||
/**
|
||||
* An identifier representing a Variant. This is at current state a culture and a segment.
|
||||
* The identifier is not specific for ContentType Variants, but is used for many type of identification of a culture and a segment. One case is any property of a ContentType can be resolved into a VariantId depending on their structural settings such as Vary by Culture and Vary by Segmentation.
|
||||
*/
|
||||
export class UmbVariantId {
|
||||
public static Create(variantData: variantObject): UmbVariantId {
|
||||
return Object.freeze(new UmbVariantId(variantData));
|
||||
public static Create(variantData: UmbObjectWithVariantProperties): UmbVariantId {
|
||||
return Object.freeze(new UmbVariantId(variantData.culture, variantData.segment));
|
||||
}
|
||||
|
||||
public static CreateInvariant(): UmbVariantId {
|
||||
return Object.freeze(new UmbVariantId({ culture: null, segment: null }));
|
||||
return Object.freeze(new UmbVariantId(null, null));
|
||||
}
|
||||
|
||||
public static FromString(str: string): UmbVariantId {
|
||||
const split = str.split('_');
|
||||
const culture = split[0] === UMB_INVARIANT_CULTURE ? null : split[0];
|
||||
const segment = split[1] ?? null;
|
||||
return Object.freeze(new UmbVariantId(segment, culture));
|
||||
}
|
||||
|
||||
public readonly culture: string | null = null;
|
||||
public readonly segment: string | null = null;
|
||||
public readonly schedule: { publishTime?: string | null; unpublishTime?: string | null } | null = null;
|
||||
|
||||
constructor(variantData: variantObject) {
|
||||
this.culture = (variantData.culture === UMB_INVARIANT_CULTURE ? null : variantData.culture) ?? null;
|
||||
this.segment = variantData.segment ?? null;
|
||||
this.schedule = variantData.schedule ?? null;
|
||||
constructor(culture?: string | null, segment?: string | null) {
|
||||
this.culture = (culture === UMB_INVARIANT_CULTURE ? null : culture?.toLowerCase()) ?? null;
|
||||
this.segment = segment ?? null;
|
||||
}
|
||||
|
||||
public compare(obj: variantObject): boolean {
|
||||
return this.equal(new UmbVariantId(obj));
|
||||
public compare(obj: UmbObjectWithVariantProperties): boolean {
|
||||
return this.equal(new UmbVariantId(obj.culture, obj.segment));
|
||||
}
|
||||
|
||||
public equal(variantId: UmbVariantId): boolean {
|
||||
@@ -34,6 +47,7 @@ export class UmbVariantId {
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
// Currently a direct copy of the VariantPropertiesObjectToString method const.
|
||||
return (this.culture || UMB_INVARIANT_CULTURE) + (this.segment ? `_${this.segment}` : '');
|
||||
}
|
||||
|
||||
@@ -57,11 +71,12 @@ export class UmbVariantId {
|
||||
return this.culture === null && this.segment === null;
|
||||
}
|
||||
|
||||
public toObject(): variantObject {
|
||||
public toObject(): UmbObjectWithVariantProperties {
|
||||
return { culture: this.culture, segment: this.segment };
|
||||
}
|
||||
|
||||
// TODO: needs localization option:
|
||||
// TODO: Consider if this should be handled else where, it does not seem like the responsibility of this class, since it contains wordings:
|
||||
public toDifferencesString(variantId: UmbVariantId): string {
|
||||
let r = '';
|
||||
|
||||
|
||||
@@ -5,20 +5,22 @@ import {
|
||||
UUIInputEvent,
|
||||
type UUIPopoverContainerElement,
|
||||
} from '@umbraco-cms/backoffice/external/uui';
|
||||
import {
|
||||
css,
|
||||
html,
|
||||
nothing,
|
||||
customElement,
|
||||
property,
|
||||
state,
|
||||
ifDefined,
|
||||
query,
|
||||
} from '@umbraco-cms/backoffice/external/lit';
|
||||
import { css, html, nothing, customElement, state, query } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UMB_WORKSPACE_SPLIT_VIEW_CONTEXT, type ActiveVariant } from '@umbraco-cms/backoffice/workspace';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { DocumentVariantStateModel } from '@umbraco-cms/backoffice/external/backend-api';
|
||||
import type { UmbDocumentVariantModel } from '@umbraco-cms/backoffice/document';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import type { UmbDocumentWorkspaceContext } from '@umbraco-cms/backoffice/document';
|
||||
|
||||
type UmbDocumentVariantOption = {
|
||||
culture: string | null;
|
||||
segment: string | null;
|
||||
title: string;
|
||||
displayName: string;
|
||||
state: DocumentVariantStateModel;
|
||||
};
|
||||
|
||||
type UmbDocumentVariantOptions = Array<UmbDocumentVariantOption>;
|
||||
|
||||
@customElement('umb-variant-selector')
|
||||
export class UmbVariantSelectorElement extends UmbLitElement {
|
||||
@@ -26,73 +28,75 @@ export class UmbVariantSelectorElement extends UmbLitElement {
|
||||
private _popoverElement?: UUIPopoverContainerElement;
|
||||
|
||||
@state()
|
||||
_variants: Array<UmbDocumentVariantModel> = [];
|
||||
private _variants: UmbDocumentVariantOptions = [];
|
||||
|
||||
// TODO: Stop using document context specific ActiveVariant type.
|
||||
@state()
|
||||
_activeVariants: Array<ActiveVariant> = [];
|
||||
|
||||
@property({ attribute: false })
|
||||
public get _activeVariantsCultures(): string[] {
|
||||
return this._activeVariants.map((el) => el.culture ?? '') ?? [];
|
||||
}
|
||||
@state()
|
||||
_activeVariantsCultures: string[] = [];
|
||||
|
||||
#splitViewContext?: typeof UMB_WORKSPACE_SPLIT_VIEW_CONTEXT.TYPE;
|
||||
#variantContext?: typeof UMB_PROPERTY_DATASET_CONTEXT.TYPE;
|
||||
#datasetContext?: typeof UMB_PROPERTY_DATASET_CONTEXT.TYPE;
|
||||
|
||||
@state()
|
||||
private _name?: string;
|
||||
|
||||
private _culture?: string | null;
|
||||
private _segment?: string | null;
|
||||
@state()
|
||||
private _variantDisplayName = '';
|
||||
|
||||
@state()
|
||||
private _variantDisplayName?: string;
|
||||
|
||||
@state()
|
||||
private _variantTitleName?: string;
|
||||
private _variantTitleName = '';
|
||||
|
||||
@state()
|
||||
private _variantSelectorOpen = false;
|
||||
|
||||
// TODO: make adapt to backoffice locale.
|
||||
private _cultureNames = new Intl.DisplayNames('en', { type: 'language' });
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.consumeContext(UMB_WORKSPACE_SPLIT_VIEW_CONTEXT, (instance) => {
|
||||
this.#splitViewContext = instance;
|
||||
this._observeVariants();
|
||||
this._observeActiveVariants();
|
||||
this.#observeVariants();
|
||||
this.#observeActiveVariants();
|
||||
this.#observeCurrentVariant();
|
||||
});
|
||||
this.consumeContext(UMB_PROPERTY_DATASET_CONTEXT, (instance) => {
|
||||
this.#variantContext = instance;
|
||||
this._observeVariantContext();
|
||||
this.#datasetContext = instance;
|
||||
this.#observeDatasetContext();
|
||||
this.#observeCurrentVariant();
|
||||
});
|
||||
}
|
||||
|
||||
private async _observeVariants() {
|
||||
async #observeVariants() {
|
||||
if (!this.#splitViewContext) return;
|
||||
|
||||
const workspaceContext = this.#splitViewContext.getWorkspaceContext();
|
||||
if (workspaceContext) {
|
||||
this.observe(
|
||||
workspaceContext.variants,
|
||||
(variants) => {
|
||||
if (variants) {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
// TODO: figure out what we do with the different variant models. Document has a state, but the variant model does not.
|
||||
this._variants = variants;
|
||||
}
|
||||
},
|
||||
'_observeVariants',
|
||||
);
|
||||
}
|
||||
// NOTICE: This is dirty (the TypeScript casting), we can only accept doing this so far because we currently only use the Variant Selector on Document Workspace. [NL]
|
||||
// This would need a refactor to enable the code below to work with different ContentTypes. Main problem here is the state, which is not generic for them all. [NL]
|
||||
const workspaceContext = this.#splitViewContext.getWorkspaceContext() as UmbDocumentWorkspaceContext;
|
||||
if (!workspaceContext) throw new Error('Split View Workspace context not found');
|
||||
|
||||
this.observe(
|
||||
workspaceContext.variantOptions,
|
||||
(options) => {
|
||||
this._variants = options.map<UmbDocumentVariantOption>((option) => {
|
||||
const name = option.variant?.name ?? option.language.name;
|
||||
const segment = option.variant?.segment ?? null;
|
||||
return {
|
||||
// Notice the option object has a unique property, but it's not used here. (Its equivalent to a UmbVariantId string) [NL]
|
||||
culture: option.language.unique,
|
||||
segment: segment,
|
||||
title: name + (segment ? ` — ${segment}` : ''),
|
||||
displayName: name + (segment ? ` — ${segment}` : ''),
|
||||
state: option.variant?.state ?? DocumentVariantStateModel.NOT_CREATED,
|
||||
};
|
||||
});
|
||||
},
|
||||
'_observeVariants',
|
||||
);
|
||||
}
|
||||
|
||||
private async _observeActiveVariants() {
|
||||
async #observeActiveVariants() {
|
||||
if (!this.#splitViewContext) return;
|
||||
|
||||
const workspaceContext = this.#splitViewContext.getWorkspaceContext();
|
||||
@@ -102,6 +106,7 @@ export class UmbVariantSelectorElement extends UmbLitElement {
|
||||
(activeVariants) => {
|
||||
if (activeVariants) {
|
||||
this._activeVariants = activeVariants;
|
||||
this._activeVariantsCultures = this._activeVariants.map((el) => el.culture ?? '') ?? [];
|
||||
}
|
||||
},
|
||||
'_observeActiveVariants',
|
||||
@@ -109,16 +114,10 @@ export class UmbVariantSelectorElement extends UmbLitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private async _observeVariantContext() {
|
||||
if (!this.#variantContext) return;
|
||||
|
||||
const variantId = this.#variantContext.getVariantId();
|
||||
this._culture = variantId.culture;
|
||||
this._segment = variantId.segment;
|
||||
this.updateVariantDisplayName();
|
||||
|
||||
async #observeDatasetContext() {
|
||||
if (!this.#datasetContext) return;
|
||||
this.observe(
|
||||
this.#variantContext.name,
|
||||
this.#datasetContext.name,
|
||||
(name) => {
|
||||
this._name = name;
|
||||
},
|
||||
@@ -126,48 +125,62 @@ export class UmbVariantSelectorElement extends UmbLitElement {
|
||||
);
|
||||
}
|
||||
|
||||
private updateVariantDisplayName() {
|
||||
if (!this._culture && !this._segment) return;
|
||||
this._variantTitleName =
|
||||
(this._culture ? this._cultureNames.of(this._culture) + ` (${this._culture})` : '') +
|
||||
(this._segment ? ' — ' + this._segment : '');
|
||||
this._variantDisplayName =
|
||||
(this._culture ? this._cultureNames.of(this._culture) : '') + (this._segment ? ' — ' + this._segment : '');
|
||||
async #observeCurrentVariant() {
|
||||
if (!this.#datasetContext || !this.#splitViewContext) return;
|
||||
const workspaceContext = this.#splitViewContext.getWorkspaceContext();
|
||||
if (!workspaceContext) return;
|
||||
|
||||
const variantId = this.#datasetContext.getVariantId();
|
||||
// Find the variant option matching this, to get the language name...
|
||||
|
||||
const culture = variantId.culture;
|
||||
const segment = variantId.segment;
|
||||
|
||||
this.observe(
|
||||
workspaceContext.variantOptions,
|
||||
(options) => {
|
||||
const option = options.find((option) => option.language.unique === culture);
|
||||
const languageName = option?.language.name;
|
||||
this._variantDisplayName = (languageName ? languageName : '') + (segment ? ` — ${segment}` : '');
|
||||
this._variantTitleName =
|
||||
(languageName ? `${languageName} (${culture})` : '') + (segment ? ` — ${segment}` : '');
|
||||
},
|
||||
'_currentLanguage',
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: find a way where we don't have to do this for all workspaces.
|
||||
private _handleInput(event: UUIInputEvent) {
|
||||
#handleInput(event: UUIInputEvent) {
|
||||
if (event instanceof UUIInputEvent) {
|
||||
const target = event.composedPath()[0] as UUIInputElement;
|
||||
|
||||
if (
|
||||
typeof target?.value === 'string' &&
|
||||
this.#variantContext &&
|
||||
isNameablePropertyDatasetContext(this.#variantContext)
|
||||
this.#datasetContext &&
|
||||
isNameablePropertyDatasetContext(this.#datasetContext)
|
||||
) {
|
||||
this.#variantContext.setName(target.value);
|
||||
this.#datasetContext.setName(target.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _switchVariant(variant: UmbDocumentVariantModel) {
|
||||
#switchVariant(variant: UmbDocumentVariantOption) {
|
||||
this.#splitViewContext?.switchVariant(UmbVariantId.Create(variant));
|
||||
}
|
||||
|
||||
private _openSplitView(variant: UmbDocumentVariantModel) {
|
||||
#openSplitView(variant: UmbDocumentVariantOption) {
|
||||
this.#splitViewContext?.openSplitView(UmbVariantId.Create(variant));
|
||||
}
|
||||
|
||||
private _closeSplitView() {
|
||||
#closeSplitView() {
|
||||
this.#splitViewContext?.closeSplitView();
|
||||
}
|
||||
|
||||
private _isVariantActive(culture: string) {
|
||||
return this._activeVariantsCultures.includes(culture);
|
||||
#isVariantActive(culture: string | null) {
|
||||
return culture !== null ? this._activeVariantsCultures.includes(culture) : true;
|
||||
}
|
||||
|
||||
private _isNotPublishedMode(culture: string, state: DocumentVariantStateModel) {
|
||||
return state !== DocumentVariantStateModel.PUBLISHED && !this._isVariantActive(culture!);
|
||||
#isNotPublishedMode(culture: string | null, state: DocumentVariantStateModel) {
|
||||
return state !== DocumentVariantStateModel.PUBLISHED && !this.#isVariantActive(culture);
|
||||
}
|
||||
|
||||
// TODO: This ignorer is just needed for JSON SCHEMA TO WORK, As its not updated with latest TS jet.
|
||||
@@ -182,26 +195,27 @@ export class UmbVariantSelectorElement extends UmbLitElement {
|
||||
if (!isOpen) return;
|
||||
|
||||
const host = this.getBoundingClientRect();
|
||||
// TODO: Ideally this is kept updated while open, but for now we just set it once:
|
||||
this._popoverElement.style.width = `${host.width}px`;
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<uui-input id="name-input" .value=${this._name} @input="${this._handleInput}">
|
||||
<uui-input id="name-input" .value=${this._name ?? ''} @input="${this.#handleInput}">
|
||||
${
|
||||
this._variants && this._variants.length > 0
|
||||
this._variants?.length
|
||||
? html`
|
||||
<uui-button
|
||||
id="variant-selector-toggle"
|
||||
slot="append"
|
||||
popovertarget="variant-selector-popover"
|
||||
title=${ifDefined(this._variantTitleName)}>
|
||||
title=${this._variantTitleName}>
|
||||
${this._variantDisplayName}
|
||||
<uui-symbol-expand .open=${this._variantSelectorOpen}></uui-symbol-expand>
|
||||
</uui-button>
|
||||
${this._activeVariants.length > 1
|
||||
? html`
|
||||
<uui-button slot="append" compact id="variant-close" @click=${this._closeSplitView}>
|
||||
<uui-button slot="append" compact id="variant-close" @click=${this.#closeSplitView}>
|
||||
<uui-icon name="remove"></uui-icon>
|
||||
</uui-button>
|
||||
`
|
||||
@@ -212,7 +226,7 @@ export class UmbVariantSelectorElement extends UmbLitElement {
|
||||
</uui-input>
|
||||
|
||||
${
|
||||
this._variants && this._variants.length > 0
|
||||
this._variants?.length
|
||||
? html`
|
||||
<uui-popover-container
|
||||
id="variant-selector-popover"
|
||||
@@ -223,25 +237,26 @@ export class UmbVariantSelectorElement extends UmbLitElement {
|
||||
<ul>
|
||||
${this._variants.map(
|
||||
(variant) => html`
|
||||
<li class="${this._isVariantActive(variant.culture!) ? 'selected' : ''}">
|
||||
<li class="${this.#isVariantActive(variant.culture) ? 'selected' : ''}">
|
||||
<button
|
||||
class="variant-selector-switch-button
|
||||
${this._isNotPublishedMode(variant.culture!, variant.state!) ? 'add-mode' : ''}"
|
||||
@click=${() => this._switchVariant(variant)}>
|
||||
${this._isNotPublishedMode(variant.culture!, variant.state!)
|
||||
${this.#isNotPublishedMode(variant.culture, variant.state) ? 'add-mode' : ''}"
|
||||
@click=${() => this.#switchVariant(variant)}>
|
||||
${this.#isNotPublishedMode(variant.culture, variant.state)
|
||||
? html`<uui-icon class="add-icon" name="icon-add"></uui-icon>`
|
||||
: nothing}
|
||||
<div>
|
||||
${variant.name} <i>(${variant.culture})</i> ${variant.segment}
|
||||
${variant.title}
|
||||
<i>(${variant.culture})</i> ${variant.segment}
|
||||
<div class="variant-selector-state">${variant.state}</div>
|
||||
</div>
|
||||
</button>
|
||||
${this._isVariantActive(variant.culture!)
|
||||
${this.#isVariantActive(variant.culture)
|
||||
? nothing
|
||||
: html`
|
||||
<uui-button
|
||||
class="variant-selector-split-view"
|
||||
@click=${() => this._openSplitView(variant)}>
|
||||
@click=${() => this.#openSplitView(variant)}>
|
||||
Split view
|
||||
</uui-button>
|
||||
`}
|
||||
@@ -260,6 +275,7 @@ export class UmbVariantSelectorElement extends UmbLitElement {
|
||||
}
|
||||
|
||||
static styles = [
|
||||
UmbTextStyles,
|
||||
css`
|
||||
#name-input {
|
||||
width: 100%;
|
||||
@@ -331,12 +347,6 @@ export class UmbVariantSelectorElement extends UmbLitElement {
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid var(--uui-color-divider-standalone);
|
||||
font-family:
|
||||
Lato,
|
||||
Helvetica Neue,
|
||||
Helvetica,
|
||||
Arial,
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
.variant-selector-switch-button:hover {
|
||||
|
||||
@@ -2,16 +2,18 @@ import type { UmbWorkspaceSplitViewManager } from '../workspace-split-view-manag
|
||||
import type { UmbPropertyDatasetContext } from '../../property/property-dataset/property-dataset-context.interface.js';
|
||||
import type { UmbSaveableWorkspaceContextInterface } from './saveable-workspace-context.interface.js';
|
||||
import type { Observable } from '@umbraco-cms/backoffice/external/rxjs';
|
||||
import type { UmbVariantId, UmbVariantModel } from '@umbraco-cms/backoffice/variant';
|
||||
import type { UmbVariantId, UmbVariantModel, UmbVariantOptionModel } from '@umbraco-cms/backoffice/variant';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
|
||||
export interface UmbVariantableWorkspaceContextInterface extends UmbSaveableWorkspaceContextInterface {
|
||||
export interface UmbVariantableWorkspaceContextInterface<VariantType extends UmbVariantModel = UmbVariantModel>
|
||||
extends UmbSaveableWorkspaceContextInterface {
|
||||
// Name:
|
||||
getName(variantId?: UmbVariantId): string | undefined;
|
||||
setName(name: string, variantId?: UmbVariantId): void;
|
||||
|
||||
// Variant:
|
||||
variants: Observable<Array<UmbVariantModel>>;
|
||||
variantOptions: Observable<Array<UmbVariantOptionModel<VariantType>>>;
|
||||
splitView: UmbWorkspaceSplitViewManager;
|
||||
getVariant(variantId: UmbVariantId): UmbVariantModel | undefined;
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ export class UmbWorkspaceSplitViewManager {
|
||||
}
|
||||
|
||||
setActiveVariant(index: number, culture: string | null, segment: string | null) {
|
||||
this.#activeVariantsInfo.appendOne({ index, culture, segment });
|
||||
this.#activeVariantsInfo.appendOne({ index, culture: culture || null, segment: segment || null });
|
||||
}
|
||||
|
||||
getActiveVariants() {
|
||||
@@ -53,7 +53,7 @@ export class UmbWorkspaceSplitViewManager {
|
||||
const newVariants = [...activeVariants];
|
||||
newVariants[index] = { index, culture: variantId.culture, segment: variantId.segment };
|
||||
|
||||
const variantPart: string = newVariants.map((v) => new UmbVariantId(v).toString()).join('_&_');
|
||||
const variantPart: string = newVariants.map((v) => UmbVariantId.Create(v).toString()).join('_&_');
|
||||
|
||||
history.pushState(null, '', `${workspaceRoute}/${variantPart}`);
|
||||
return true;
|
||||
@@ -70,7 +70,7 @@ export class UmbWorkspaceSplitViewManager {
|
||||
const currentVariant = this.getActiveVariants()[0];
|
||||
const workspaceRoute = this.getWorkspaceRoute();
|
||||
if (currentVariant && workspaceRoute) {
|
||||
history.pushState(null, '', `${workspaceRoute}/${new UmbVariantId(currentVariant)}_&_${newVariant.toString()}`);
|
||||
history.pushState(null, '', `${workspaceRoute}/${UmbVariantId.Create(currentVariant)}_&_${newVariant}`);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -83,7 +83,7 @@ export class UmbWorkspaceSplitViewManager {
|
||||
if (activeVariants && index < activeVariants.length) {
|
||||
const newVariants = activeVariants.filter((x) => x.index !== index);
|
||||
|
||||
const variantPart: string = newVariants.map((v) => new UmbVariantId(v).toString()).join('_&_');
|
||||
const variantPart: string = newVariants.map((v) => UmbVariantId.Create(v)).join('_&_');
|
||||
|
||||
history.pushState(null, '', `${workspaceRoute}/${variantPart}`);
|
||||
return true;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { UUIButtonState, UUIPaginationElement, UUIPaginationEvent } from '@umbraco-cms/backoffice/external/uui';
|
||||
import { css, html, nothing, customElement, state, query, property } from '@umbraco-cms/backoffice/external/lit';
|
||||
import type { UmbModalManagerContext } from '@umbraco-cms/backoffice/modal';
|
||||
import { UMB_MODAL_MANAGER_CONTEXT, UMB_CONFIRM_MODAL } from '@umbraco-cms/backoffice/modal';
|
||||
import { umbConfirmModal } from '@umbraco-cms/backoffice/modal';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import type { RedirectUrlResponseModel } from '@umbraco-cms/backoffice/external/backend-api';
|
||||
import { RedirectManagementResource, RedirectStatusModel } from '@umbraco-cms/backoffice/external/backend-api';
|
||||
@@ -37,15 +36,6 @@ export class UmbDashboardRedirectManagementElement extends UmbLitElement {
|
||||
@query('uui-pagination')
|
||||
private _pagination?: UUIPaginationElement;
|
||||
|
||||
private _modalContext?: UmbModalManagerContext;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (_instance) => {
|
||||
this._modalContext = _instance;
|
||||
});
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.#getTrackerStatus();
|
||||
@@ -80,29 +70,23 @@ export class UmbDashboardRedirectManagementElement extends UmbLitElement {
|
||||
}
|
||||
|
||||
// Delete Redirect Action
|
||||
#onRequestDelete(data: RedirectUrlResponseModel) {
|
||||
async #onRequestDelete(data: RedirectUrlResponseModel) {
|
||||
if (!data.id) return;
|
||||
const modalContext = this._modalContext?.open(UMB_CONFIRM_MODAL, {
|
||||
data: {
|
||||
headline: 'Delete',
|
||||
content: html`
|
||||
<div style="width:300px">
|
||||
<p>${this.localize.term('redirectUrls_redirectRemoveWarning')}</p>
|
||||
${this.localize.term('redirectUrls_originalUrl')}: <strong>${data.originalUrl}</strong><br />
|
||||
${this.localize.term('redirectUrls_redirectedTo')}: <strong>${data.destinationUrl}</strong>
|
||||
</div>
|
||||
`,
|
||||
color: 'danger',
|
||||
confirmLabel: 'Delete',
|
||||
},
|
||||
|
||||
await umbConfirmModal(this, {
|
||||
headline: 'Delete',
|
||||
content: html`
|
||||
<div style="width:300px">
|
||||
<p>${this.localize.term('redirectUrls_redirectRemoveWarning')}</p>
|
||||
${this.localize.term('redirectUrls_originalUrl')}: <strong>${data.originalUrl}</strong><br />
|
||||
${this.localize.term('redirectUrls_redirectedTo')}: <strong>${data.destinationUrl}</strong>
|
||||
</div>
|
||||
`,
|
||||
color: 'danger',
|
||||
confirmLabel: 'Delete',
|
||||
});
|
||||
|
||||
modalContext
|
||||
?.onSubmit()
|
||||
.then(() => {
|
||||
this.#redirectDelete(data.id!);
|
||||
})
|
||||
.catch(() => undefined);
|
||||
this.#redirectDelete(data.id!);
|
||||
}
|
||||
async #redirectDelete(id: string) {
|
||||
const { error } = await tryExecuteAndNotify(this, RedirectManagementResource.deleteRedirectManagementById({ id }));
|
||||
@@ -125,26 +109,20 @@ export class UmbDashboardRedirectManagementElement extends UmbLitElement {
|
||||
}
|
||||
|
||||
// Tracker disable/enable
|
||||
#onRequestTrackerToggle() {
|
||||
async #onRequestTrackerToggle() {
|
||||
if (!this._trackerEnabled) {
|
||||
this.#trackerToggle();
|
||||
return;
|
||||
}
|
||||
|
||||
const modalContext = this._modalContext?.open(UMB_CONFIRM_MODAL, {
|
||||
data: {
|
||||
headline: `${this.localize.term('redirectUrls_disableUrlTracker')}`,
|
||||
content: `${this.localize.term('redirectUrls_confirmDisable')}`,
|
||||
color: 'danger',
|
||||
confirmLabel: 'Disable',
|
||||
},
|
||||
await umbConfirmModal(this, {
|
||||
headline: `${this.localize.term('redirectUrls_disableUrlTracker')}`,
|
||||
content: `${this.localize.term('redirectUrls_confirmDisable')}`,
|
||||
color: 'danger',
|
||||
confirmLabel: 'Disable',
|
||||
});
|
||||
modalContext
|
||||
?.onSubmit()
|
||||
.then(() => {
|
||||
this.#trackerToggle();
|
||||
})
|
||||
.catch(() => undefined);
|
||||
|
||||
this.#trackerToggle();
|
||||
}
|
||||
|
||||
async #trackerToggle() {
|
||||
|
||||
@@ -2,13 +2,11 @@ import { UmbDataTypeDetailRepository } from '@umbraco-cms/backoffice/data-type';
|
||||
import type { UUIInputElement } from '@umbraco-cms/backoffice/external/uui';
|
||||
import { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui';
|
||||
import { css, html, customElement, property, state, ifDefined, nothing } from '@umbraco-cms/backoffice/external/lit';
|
||||
import type { UmbConfirmModalData } from '@umbraco-cms/backoffice/modal';
|
||||
import {
|
||||
UMB_CONFIRM_MODAL,
|
||||
UMB_MODAL_MANAGER_CONTEXT,
|
||||
UMB_PROPERTY_SETTINGS_MODAL,
|
||||
UMB_WORKSPACE_MODAL,
|
||||
UmbModalRouteRegistrationController,
|
||||
umbConfirmModal,
|
||||
} from '@umbraco-cms/backoffice/modal';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { generateAlias } from '@umbraco-cms/backoffice/utils';
|
||||
@@ -57,7 +55,6 @@ export class UmbDocumentTypeWorkspacePropertyElement extends UmbLitElement {
|
||||
#dataTypeDetailRepository = new UmbDataTypeDetailRepository(this);
|
||||
|
||||
#modalRegistration;
|
||||
private _modalManagerContext?: typeof UMB_MODAL_MANAGER_CONTEXT.TYPE;
|
||||
|
||||
@state()
|
||||
protected _modalRoute?: string;
|
||||
@@ -113,10 +110,6 @@ export class UmbDocumentTypeWorkspacePropertyElement extends UmbLitElement {
|
||||
.observeRouteBuilder((routeBuilder) => {
|
||||
this._editDocumentTypePath = routeBuilder({});
|
||||
});
|
||||
|
||||
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (context) => {
|
||||
this._modalManagerContext = context;
|
||||
});
|
||||
}
|
||||
|
||||
_partialUpdate(partialObject: UmbPropertyTypeModel) {
|
||||
@@ -137,12 +130,12 @@ export class UmbDocumentTypeWorkspacePropertyElement extends UmbLitElement {
|
||||
this._aliasLocked = !this._aliasLocked;
|
||||
}
|
||||
|
||||
#requestRemove(e: Event) {
|
||||
async #requestRemove(e: Event) {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
if (!this.property || !this.property.id) return;
|
||||
|
||||
const modalData: UmbConfirmModalData = {
|
||||
await umbConfirmModal(this, {
|
||||
headline: `${this.localize.term('actions_delete')} property`,
|
||||
content: html`<umb-localize key="contentTypeEditor_confirmDeletePropertyMessage" .args=${[
|
||||
this.property.name || this.property.id,
|
||||
@@ -152,19 +145,9 @@ export class UmbDocumentTypeWorkspacePropertyElement extends UmbLitElement {
|
||||
</div>`,
|
||||
confirmLabel: this.localize.term('actions_delete'),
|
||||
color: 'danger',
|
||||
};
|
||||
});
|
||||
|
||||
const modalHandler = this._modalManagerContext?.open(UMB_CONFIRM_MODAL, { data: modalData });
|
||||
|
||||
modalHandler
|
||||
?.onSubmit()
|
||||
.then(() => {
|
||||
this.dispatchEvent(new CustomEvent('property-delete'));
|
||||
})
|
||||
.catch(() => {
|
||||
// We do not need to react to cancel, so we will leave an empty method to prevent Uncaught Promise Rejection error.
|
||||
return;
|
||||
});
|
||||
this.dispatchEvent(new CustomEvent('property-delete'));
|
||||
}
|
||||
|
||||
#onNameChange(event: UUIInputEvent) {
|
||||
|
||||
@@ -15,7 +15,7 @@ import { UMB_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace';
|
||||
import type { UmbRoute, UmbRouterSlotChangeEvent, UmbRouterSlotInitEvent } from '@umbraco-cms/backoffice/router';
|
||||
import type { UmbWorkspaceViewElement } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import type { UmbConfirmModalData } from '@umbraco-cms/backoffice/modal';
|
||||
import { UMB_CONFIRM_MODAL, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
|
||||
import { UMB_MODAL_MANAGER_CONTEXT, umbConfirmModal } from '@umbraco-cms/backoffice/modal';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import { UmbSorterController } from '@umbraco-cms/backoffice/sorter';
|
||||
|
||||
@@ -99,8 +99,6 @@ export class UmbDocumentTypeWorkspaceViewEditElement extends UmbLitElement imple
|
||||
|
||||
private _tabsStructureHelper = new UmbContentTypeContainerStructureHelper<UmbDocumentTypeDetailModel>(this);
|
||||
|
||||
private _modalManagerContext?: typeof UMB_MODAL_MANAGER_CONTEXT.TYPE;
|
||||
|
||||
@state()
|
||||
private _compositionConfiguration?: UmbCompositionPickerModalData;
|
||||
|
||||
@@ -154,10 +152,6 @@ export class UmbDocumentTypeWorkspaceViewEditElement extends UmbLitElement imple
|
||||
|
||||
this._observeRootGroups();
|
||||
});
|
||||
|
||||
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (context) => {
|
||||
this._modalManagerContext = context;
|
||||
});
|
||||
}
|
||||
|
||||
private _observeRootGroups() {
|
||||
@@ -220,7 +214,7 @@ export class UmbDocumentTypeWorkspaceViewEditElement extends UmbLitElement imple
|
||||
this._routes = routes;
|
||||
}
|
||||
|
||||
#requestRemoveTab(tab: PropertyTypeContainerModelBaseModel | undefined) {
|
||||
async #requestRemoveTab(tab: PropertyTypeContainerModelBaseModel | undefined) {
|
||||
const modalData: UmbConfirmModalData = {
|
||||
headline: 'Delete tab',
|
||||
content: html`<umb-localize key="contentTypeEditor_confirmDeleteTabMessage" .args=${[tab?.name ?? tab?.id]}>
|
||||
@@ -237,11 +231,9 @@ export class UmbDocumentTypeWorkspaceViewEditElement extends UmbLitElement imple
|
||||
|
||||
// TODO: If this tab is composed of other tabs, then notify that it will only delete the local tab.
|
||||
|
||||
const modalHandler = this._modalManagerContext?.open(UMB_CONFIRM_MODAL, { data: modalData });
|
||||
await umbConfirmModal(this, modalData);
|
||||
|
||||
modalHandler?.onSubmit().then(() => {
|
||||
this.#remove(tab?.id);
|
||||
});
|
||||
this.#remove(tab?.id);
|
||||
}
|
||||
#remove(tabId?: string) {
|
||||
if (!tabId) return;
|
||||
@@ -303,7 +295,8 @@ export class UmbDocumentTypeWorkspaceViewEditElement extends UmbLitElement imple
|
||||
}
|
||||
|
||||
async #openCompositionModal() {
|
||||
const modalContext = this._modalManagerContext?.open(UMB_COMPOSITION_PICKER_MODAL, {
|
||||
const modalManagerContext = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
|
||||
const modalContext = modalManagerContext.open(UMB_COMPOSITION_PICKER_MODAL, {
|
||||
data: this._compositionConfiguration,
|
||||
});
|
||||
await modalContext?.onSubmit();
|
||||
|
||||
@@ -19,9 +19,9 @@ export class UmbDocumentWorkspaceHasCollectionCondition extends UmbBaseControlle
|
||||
|
||||
this.consumeContext(UMB_DOCUMENT_WORKSPACE_CONTEXT, (context) => {
|
||||
this.observe(
|
||||
context.contentTypeCollection,
|
||||
(collection) => {
|
||||
this.permitted = !!collection?.unique;
|
||||
context.contentTypeHasCollection,
|
||||
(hasCollection) => {
|
||||
this.permitted = hasCollection;
|
||||
this.#onChange();
|
||||
},
|
||||
'observeCollection',
|
||||
|
||||
@@ -1,21 +1,50 @@
|
||||
import { UMB_DOCUMENT_VARIANT_MANAGER_CONTEXT } from '../global-contexts/index.js';
|
||||
import type { UmbDocumentPublishingRepository } from '../repository/index.js';
|
||||
import { umbPickDocumentVariantModal } from '../modals/pick-document-variant-modal.controller.js';
|
||||
import { type UmbDocumentDetailRepository, UmbDocumentPublishingRepository } from '../repository/index.js';
|
||||
import { UmbDocumentVariantState } from '../types.js';
|
||||
import { UmbLanguageCollectionRepository } from '@umbraco-cms/backoffice/language';
|
||||
import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action';
|
||||
import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
|
||||
|
||||
export class UmbPublishDocumentEntityAction extends UmbEntityActionBase<UmbDocumentPublishingRepository> {
|
||||
#variantManagerContext?: typeof UMB_DOCUMENT_VARIANT_MANAGER_CONTEXT.TYPE;
|
||||
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string, entityType: string) {
|
||||
super(host, repositoryAlias, unique, entityType);
|
||||
|
||||
this.consumeContext(UMB_DOCUMENT_VARIANT_MANAGER_CONTEXT, (context) => {
|
||||
this.#variantManagerContext = context;
|
||||
});
|
||||
}
|
||||
import { UmbVariantId } from '@umbraco-cms/backoffice/variant';
|
||||
|
||||
export class UmbPublishDocumentEntityAction extends UmbEntityActionBase<UmbDocumentDetailRepository> {
|
||||
async execute() {
|
||||
if (!this.#variantManagerContext) throw new Error('Variant manager context is missing');
|
||||
await this.#variantManagerContext.publish(this.unique);
|
||||
if (!this.repository) throw new Error('Document repository not set');
|
||||
|
||||
const languageRepository = new UmbLanguageCollectionRepository(this._host);
|
||||
const { data: languageData } = await languageRepository.requestCollection({});
|
||||
const { data: documentData } = await this.repository.requestByUnique(this.unique);
|
||||
|
||||
if (!documentData) throw new Error('The document was not found');
|
||||
|
||||
// If the document has only one variant, we can skip the modal and publish directly:
|
||||
if (documentData.variants.length === 1) {
|
||||
const variantId = UmbVariantId.Create(documentData.variants[0]);
|
||||
const publishingRepository = new UmbDocumentPublishingRepository(this._host);
|
||||
await publishingRepository.publish(this.unique, [variantId]);
|
||||
return;
|
||||
}
|
||||
|
||||
const allOptions = (languageData?.items ?? []).map((language) => ({
|
||||
language: language,
|
||||
variant: documentData.variants.find((variant) => variant.culture === language.unique),
|
||||
unique: new UmbVariantId(language.unique, null).toString(),
|
||||
}));
|
||||
|
||||
// TODO: Maybe move this to modal [NL]
|
||||
// Only display variants that are relevant to pick from, i.e. variants that are draft or published with pending changes:
|
||||
const options = allOptions.filter(
|
||||
(option) =>
|
||||
option.variant &&
|
||||
(option.variant.state === UmbDocumentVariantState.DRAFT ||
|
||||
option.variant.state === UmbDocumentVariantState.PUBLISHED ||
|
||||
option.variant.state === UmbDocumentVariantState.PUBLISHED_PENDING_CHANGES),
|
||||
);
|
||||
|
||||
// TODO: Missing features to pre-select the variant that fits with the variant-id of the tree/collection? (Again only relevant if the action is executed from a Tree or Collection) [NL]
|
||||
const selectedVariants = await umbPickDocumentVariantModal(this, { type: 'publish', options });
|
||||
|
||||
if (selectedVariants.length) {
|
||||
const publishingRepository = new UmbDocumentPublishingRepository(this._host);
|
||||
await publishingRepository.publish(this.unique, selectedVariants);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,41 @@
|
||||
import { UMB_DOCUMENT_VARIANT_MANAGER_CONTEXT } from '../global-contexts/index.js';
|
||||
import type { UmbDocumentPublishingRepository } from '../repository/index.js';
|
||||
import { umbPickDocumentVariantModal } from '../modals/pick-document-variant-modal.controller.js';
|
||||
import { type UmbDocumentDetailRepository, UmbDocumentPublishingRepository } from '../repository/index.js';
|
||||
import { UmbDocumentVariantState } from '../types.js';
|
||||
import { UmbLanguageCollectionRepository } from '@umbraco-cms/backoffice/language';
|
||||
import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action';
|
||||
import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
|
||||
|
||||
export class UmbUnpublishDocumentEntityAction extends UmbEntityActionBase<UmbDocumentPublishingRepository> {
|
||||
#variantManagerContext?: typeof UMB_DOCUMENT_VARIANT_MANAGER_CONTEXT.TYPE;
|
||||
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string, entityType: string) {
|
||||
super(host, repositoryAlias, unique, entityType);
|
||||
|
||||
this.consumeContext(UMB_DOCUMENT_VARIANT_MANAGER_CONTEXT, (context) => {
|
||||
this.#variantManagerContext = context;
|
||||
});
|
||||
}
|
||||
import { UmbVariantId } from '@umbraco-cms/backoffice/variant';
|
||||
|
||||
export class UmbUnpublishDocumentEntityAction extends UmbEntityActionBase<UmbDocumentDetailRepository> {
|
||||
async execute() {
|
||||
if (!this.#variantManagerContext) throw new Error('Variant manager context is missing');
|
||||
await this.#variantManagerContext.unpublish(this.unique);
|
||||
if (!this.repository) throw new Error('Document repository not set');
|
||||
|
||||
const languageRepository = new UmbLanguageCollectionRepository(this._host);
|
||||
const { data: languageData } = await languageRepository.requestCollection({});
|
||||
const { data: documentData } = await this.repository.requestByUnique(this.unique);
|
||||
|
||||
if (!documentData) throw new Error('The document was not found');
|
||||
|
||||
const allOptions = (languageData?.items ?? []).map((language) => ({
|
||||
language: language,
|
||||
variant: documentData.variants.find((variant) => variant.culture === language.unique),
|
||||
unique: new UmbVariantId(language.unique, null).toString(),
|
||||
}));
|
||||
|
||||
// TODO: Maybe move this to modal [NL]
|
||||
// Only display variants that are relevant to pick from, i.e. variants that are published or published with pending changes:
|
||||
const options = allOptions.filter(
|
||||
(option) =>
|
||||
option.variant &&
|
||||
(option.variant.state === UmbDocumentVariantState.PUBLISHED ||
|
||||
option.variant.state === UmbDocumentVariantState.PUBLISHED_PENDING_CHANGES),
|
||||
);
|
||||
|
||||
// TODO: Missing features to pre-select the variant that fits with the variant-id of the tree/collection? (Again only relevant if the action is executed from a Tree or Collection) [NL]
|
||||
const selectedVariants = await umbPickDocumentVariantModal(this, { type: 'unpublish', options });
|
||||
|
||||
if (selectedVariants.length) {
|
||||
const publishingRepository = new UmbDocumentPublishingRepository(this._host);
|
||||
await publishingRepository.unpublish(this.unique, selectedVariants);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
import { UmbDocumentVariantState, type UmbDocumentVariantModel } from '../types.js';
|
||||
import { UmbDocumentDetailRepository } from '../repository/detail/document-detail.repository.js';
|
||||
import {
|
||||
UMB_DOCUMENT_LANGUAGE_PICKER_MODAL,
|
||||
type UmbDocumentVariantPickerModalData,
|
||||
} from '../modals/variant-picker/index.js';
|
||||
import { UmbDocumentPublishingRepository } from '../repository/publishing/index.js';
|
||||
import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
|
||||
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import type { UmbApi } from '@umbraco-cms/backoffice/extension-api';
|
||||
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
|
||||
import { UmbVariantId } from '@umbraco-cms/backoffice/variant';
|
||||
import { UMB_APP_LANGUAGE_CONTEXT } from '@umbraco-cms/backoffice/language';
|
||||
|
||||
export class UmbDocumentVariantManagerContext
|
||||
extends UmbContextBase<UmbDocumentVariantManagerContext>
|
||||
implements UmbApi
|
||||
{
|
||||
#publishingRepository = new UmbDocumentPublishingRepository(this);
|
||||
#documentRepository = new UmbDocumentDetailRepository(this);
|
||||
#modalManagerContext?: typeof UMB_MODAL_MANAGER_CONTEXT.TYPE;
|
||||
#appLanguageCulture?: string;
|
||||
|
||||
constructor(host: UmbControllerHost) {
|
||||
super(host, UMB_DOCUMENT_VARIANT_MANAGER_CONTEXT);
|
||||
|
||||
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => {
|
||||
this.#modalManagerContext = instance;
|
||||
});
|
||||
|
||||
this.consumeContext(UMB_APP_LANGUAGE_CONTEXT, (appLanguageContext) => {
|
||||
this.observe(appLanguageContext.appLanguageCulture, (culture) => {
|
||||
this.#appLanguageCulture = culture?.toLowerCase();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Helps the user pick variants for a specific operation.
|
||||
* If there is only one variant, it will be selected automatically.
|
||||
* If there are multiple variants, a modal will be shown to the user.
|
||||
* @param type The type of operation to perform.
|
||||
* @param documentUnique The unique identifier of the document.
|
||||
* @param activeVariantCulture The culture of the active variant (will be pre-selected in the modal).
|
||||
* @param filterFn Optional filter function to filter the available variants.
|
||||
* @returns The selected variants to perform the operation on.
|
||||
*/
|
||||
async pickVariants(
|
||||
availableVariants: Array<UmbDocumentVariantModel>,
|
||||
type: UmbDocumentVariantPickerModalData['type'],
|
||||
activeVariantCulture?: string,
|
||||
): Promise<UmbVariantId[]> {
|
||||
// If there is only one variant, we don't need to select anything.
|
||||
if (availableVariants.length === 1) {
|
||||
return [UmbVariantId.Create(availableVariants[0])];
|
||||
}
|
||||
|
||||
if (!this.#modalManagerContext) throw new Error('Modal manager context is missing');
|
||||
|
||||
const modalData: UmbDocumentVariantPickerModalData = {
|
||||
type,
|
||||
variants: availableVariants,
|
||||
};
|
||||
|
||||
const modalContext = this.#modalManagerContext.open(UMB_DOCUMENT_LANGUAGE_PICKER_MODAL, {
|
||||
data: modalData,
|
||||
value: { selection: activeVariantCulture ? [activeVariantCulture] : [] },
|
||||
});
|
||||
|
||||
const result = await modalContext.onSubmit().catch(() => undefined);
|
||||
|
||||
if (!result?.selection.length) return [];
|
||||
|
||||
const selectedVariants = result.selection.map((x) => x?.toLowerCase() ?? '');
|
||||
|
||||
// Match the result to the available variants.
|
||||
const variantIds = availableVariants
|
||||
.filter((x) => selectedVariants.includes(x.culture!))
|
||||
.map((x) => UmbVariantId.Create(x));
|
||||
|
||||
return variantIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish the latest version of a document indescriminately.
|
||||
* @param documentUnique The unique identifier of the document.
|
||||
*/
|
||||
async publish(documentUnique: string) {
|
||||
const { data } = await this.#documentRepository.requestByUnique(documentUnique);
|
||||
if (!data) throw new Error('Document not found');
|
||||
const variantIds = await this.pickVariants(data.variants, 'publish', this.#appLanguageCulture);
|
||||
if (variantIds.length) {
|
||||
await this.#publishingRepository.publish(documentUnique, variantIds);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpublish the latest version of a document indescriminately.
|
||||
* @param documentUnique The unique identifier of the document.
|
||||
*/
|
||||
async unpublish(documentUnique: string) {
|
||||
const { data } = await this.#documentRepository.requestByUnique(documentUnique);
|
||||
if (!data) throw new Error('Document not found');
|
||||
|
||||
// Only show published variants
|
||||
const variants = data.variants.filter((variant) => variant.state === UmbDocumentVariantState.PUBLISHED);
|
||||
|
||||
const variantIds = await this.pickVariants(variants, 'unpublish', this.#appLanguageCulture);
|
||||
|
||||
if (variantIds.length) {
|
||||
await this.#publishingRepository.unpublish(documentUnique, variantIds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default UmbDocumentVariantManagerContext;
|
||||
|
||||
export const UMB_DOCUMENT_VARIANT_MANAGER_CONTEXT = new UmbContextToken<UmbDocumentVariantManagerContext>(
|
||||
'UmbDocumentVariantManagerContext',
|
||||
);
|
||||
@@ -1 +0,0 @@
|
||||
export * from './document-variant-manager.context.js';
|
||||
@@ -1,10 +0,0 @@
|
||||
import type { ManifestGlobalContext } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
export const manifests: Array<ManifestGlobalContext> = [
|
||||
{
|
||||
type: 'globalContext',
|
||||
alias: 'Umb.GlobalContext.DocumentVariantManager',
|
||||
name: 'Document Variant Manager Context',
|
||||
js: () => import('./document-variant-manager.context.js'),
|
||||
},
|
||||
];
|
||||
@@ -12,7 +12,6 @@ import { manifests as modalManifests } from './modals/manifests.js';
|
||||
import { manifests as treeManifests } from './tree/manifests.js';
|
||||
import { manifests as userPermissionManifests } from './user-permissions/manifests.js';
|
||||
import { manifests as workspaceManifests } from './workspace/manifests.js';
|
||||
import { manifests as globalContextManifests } from './global-contexts/manifests.js';
|
||||
|
||||
export const manifests = [
|
||||
...breadcrumbManifests,
|
||||
@@ -29,5 +28,4 @@ export const manifests = [
|
||||
...treeManifests,
|
||||
...userPermissionManifests,
|
||||
...workspaceManifests,
|
||||
...globalContextManifests,
|
||||
];
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export * from './variant-picker/index.js';
|
||||
export * from './pick-document-variant-modal.controller.js';
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import type { UmbDocumentVariantOptionModel } from '../types.js';
|
||||
import {
|
||||
UMB_DOCUMENT_LANGUAGE_PICKER_MODAL,
|
||||
type UmbDocumentVariantPickerModalData,
|
||||
type UmbDocumentVariantPickerModalType,
|
||||
} from './variant-picker/document-variant-picker-modal.token.js';
|
||||
import { UmbVariantId } from '@umbraco-cms/backoffice/variant';
|
||||
import { UmbBaseController } from '@umbraco-cms/backoffice/class-api';
|
||||
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
|
||||
export interface UmbPickDocumentVariantModalArgs {
|
||||
type: UmbDocumentVariantPickerModalType;
|
||||
options: Array<UmbDocumentVariantOptionModel>;
|
||||
selected?: Array<UmbVariantId>;
|
||||
}
|
||||
|
||||
export class UmbPickDocumentVariantModalController extends UmbBaseController {
|
||||
async open(args: UmbPickDocumentVariantModalArgs): Promise<UmbVariantId[]> {
|
||||
const modalManagerContext = await this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, () => {}).asPromise();
|
||||
const selected = args.selected ?? [];
|
||||
|
||||
const modalData: UmbDocumentVariantPickerModalData = {
|
||||
type: args.type,
|
||||
options: args.options,
|
||||
};
|
||||
|
||||
if (modalData.options.length === 0) {
|
||||
// TODO: What do to when there is no options?
|
||||
}
|
||||
|
||||
const modalContext = modalManagerContext.open(UMB_DOCUMENT_LANGUAGE_PICKER_MODAL, {
|
||||
data: modalData,
|
||||
// We need to turn the selected variant ids into strings for them to be serializable to the value state, in other words the value of a modal cannot hold class instances:
|
||||
value: { selection: selected.map((x) => x.toString()) ?? [] },
|
||||
});
|
||||
|
||||
const result = await modalContext.onSubmit().catch(() => undefined);
|
||||
|
||||
// This is a one time off, so we can destroy our selfs.
|
||||
this.destroy();
|
||||
|
||||
// Map back into UmbVariantId instances:
|
||||
return result?.selection.map((x) => UmbVariantId.FromString(x)) ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
export function umbPickDocumentVariantModal(host: UmbControllerHost, args: UmbPickDocumentVariantModalArgs) {
|
||||
return new UmbPickDocumentVariantModalController(host).open(args);
|
||||
}
|
||||
@@ -1,36 +1,66 @@
|
||||
import { type UmbDocumentVariantModel, UmbDocumentVariantState } from '../../types.js';
|
||||
import { UmbDocumentVariantState, type UmbDocumentVariantOptionModel } from '../../types.js';
|
||||
import type {
|
||||
UmbDocumentVariantPickerModalValue,
|
||||
UmbDocumentVariantPickerModalData,
|
||||
} from './document-variant-picker-modal.token.js';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import { css, html, customElement, repeat } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { css, html, customElement, repeat, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbSelectionManager } from '@umbraco-cms/backoffice/utils';
|
||||
import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';
|
||||
import { UMB_APP_LANGUAGE_CONTEXT } from '@umbraco-cms/backoffice/language';
|
||||
import { UmbVariantId } from '@umbraco-cms/backoffice/variant';
|
||||
import { appendToFrozenArray } from '@umbraco-cms/backoffice/observable-api';
|
||||
|
||||
@customElement('umb-document-variant-picker-modal')
|
||||
export class UmbDocumentVariantPickerModalElement extends UmbModalBaseElement<
|
||||
UmbDocumentVariantPickerModalData,
|
||||
UmbDocumentVariantPickerModalValue
|
||||
> {
|
||||
#selectionManager = new UmbSelectionManager(this);
|
||||
#selectionManager = new UmbSelectionManager<string>(this);
|
||||
|
||||
@state()
|
||||
_selection: Array<string> = [];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.observe(this.#selectionManager.selection, (selection) => {
|
||||
this._selection = selection;
|
||||
});
|
||||
}
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this.#selectionManager.setSelectable(true);
|
||||
this.#selectionManager.setMultiple(true);
|
||||
this.#setInitialSelection();
|
||||
}
|
||||
|
||||
async #setInitialSelection() {
|
||||
let selected = this.value?.selection ?? [];
|
||||
|
||||
if (selected.length === 0) {
|
||||
// TODO: Make it possible to use consume context without callback. [NL]
|
||||
const ctrl = this.consumeContext(UMB_APP_LANGUAGE_CONTEXT, () => {});
|
||||
const context = await ctrl.asPromise();
|
||||
const appCulture = context.getAppCulture();
|
||||
// If the app language is one of the options, select it by default:
|
||||
if (appCulture && this.data?.options.some((o) => o.language.unique === appCulture)) {
|
||||
selected = appendToFrozenArray(selected, new UmbVariantId(appCulture, null).toString());
|
||||
}
|
||||
ctrl.destroy();
|
||||
}
|
||||
|
||||
this.#selectionManager.setMultiple(true);
|
||||
this.#selectionManager.setSelectable(true);
|
||||
this.#selectionManager.setSelection(selected);
|
||||
|
||||
// Make sure all mandatory variants are selected when not in unpublish mode
|
||||
this.#selectionManager.setSelection(this.value?.selection ?? []);
|
||||
if (this.data?.type !== 'unpublish') {
|
||||
this.#selectMandatoryVariants();
|
||||
}
|
||||
}
|
||||
|
||||
#selectMandatoryVariants() {
|
||||
this.data?.variants.forEach((variant) => {
|
||||
if (variant.isMandatory) {
|
||||
this.#selectionManager.select(variant.culture);
|
||||
this.data?.options.forEach((variant) => {
|
||||
if (variant.language?.isMandatory) {
|
||||
this.#selectionManager.select(variant.unique);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -87,17 +117,17 @@ export class UmbDocumentVariantPickerModalElement extends UmbModalBaseElement<
|
||||
return html`<umb-body-layout headline=${this.localize.term(this.#headline)}>
|
||||
<p id="subtitle">${this.localize.term(this.#subtitle)}</p>
|
||||
${repeat(
|
||||
this.data?.variants ?? [],
|
||||
(item) => item.culture,
|
||||
(item) => html`
|
||||
this.data?.options ?? [],
|
||||
(option) => option.unique,
|
||||
(option) => html`
|
||||
<uui-menu-item
|
||||
selectable
|
||||
label=${item.name}
|
||||
@selected=${() => this.#selectionManager.select(item.culture)}
|
||||
@deselected=${() => this.#selectionManager.deselect(item.culture)}
|
||||
?selected=${this.#selectionManager.isSelected(item.culture)}>
|
||||
label=${option.variant?.name ?? option.language.name}
|
||||
@selected=${() => this.#selectionManager.select(option.unique)}
|
||||
@deselected=${() => this.#selectionManager.deselect(option.unique)}
|
||||
?selected=${this._selection.includes(option.language.unique)}>
|
||||
<uui-icon slot="icon" name="icon-globe"></uui-icon>
|
||||
${this.#renderLabel(item)}
|
||||
${this.#renderLabel(option)}
|
||||
</uui-menu-item>
|
||||
`,
|
||||
)}
|
||||
@@ -114,11 +144,14 @@ export class UmbDocumentVariantPickerModalElement extends UmbModalBaseElement<
|
||||
</umb-body-layout> `;
|
||||
}
|
||||
|
||||
#renderLabel(variant: UmbDocumentVariantModel) {
|
||||
#renderLabel(option: UmbDocumentVariantOptionModel) {
|
||||
return html`<div class="label" slot="label">
|
||||
<strong>${variant.segment ? variant.segment + ' - ' : ''}${variant.name}</strong>
|
||||
<div class="label-status">${this.#renderVariantStatus(variant)}</div>
|
||||
${variant.isMandatory && variant.state !== UmbDocumentVariantState.PUBLISHED
|
||||
<strong
|
||||
>${option.variant?.segment ? option.variant.segment + ' - ' : ''}${option.variant?.name ??
|
||||
option.language.name}</strong
|
||||
>
|
||||
<div class="label-status">${this.#renderVariantStatus(option)}</div>
|
||||
${option.language.isMandatory && option.variant?.state !== UmbDocumentVariantState.PUBLISHED
|
||||
? html`<div class="label-status">
|
||||
<umb-localize key="languages_mandatoryLanguage">Mandatory language</umb-localize>
|
||||
</div>`
|
||||
@@ -126,16 +159,17 @@ export class UmbDocumentVariantPickerModalElement extends UmbModalBaseElement<
|
||||
</div>`;
|
||||
}
|
||||
|
||||
#renderVariantStatus(variant: UmbDocumentVariantModel) {
|
||||
switch (variant.state) {
|
||||
#renderVariantStatus(option: UmbDocumentVariantOptionModel) {
|
||||
switch (option.variant?.state) {
|
||||
case UmbDocumentVariantState.PUBLISHED:
|
||||
return this.localize.term('content_published');
|
||||
case UmbDocumentVariantState.PUBLISHED_PENDING_CHANGES:
|
||||
return this.localize.term('content_publishedPendingChanges');
|
||||
case UmbDocumentVariantState.NOT_CREATED:
|
||||
case UmbDocumentVariantState.DRAFT:
|
||||
default:
|
||||
return this.localize.term('content_unpublished');
|
||||
case UmbDocumentVariantState.NOT_CREATED:
|
||||
default:
|
||||
return this.localize.term('content_notCreated');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,17 +12,29 @@ import { html } from '@umbraco-cms/backoffice/external/lit';
|
||||
|
||||
const modalData: UmbDocumentVariantPickerModalData = {
|
||||
type: 'save',
|
||||
variants: [
|
||||
options: [
|
||||
{
|
||||
name: 'English',
|
||||
culture: 'en-us',
|
||||
state: UmbDocumentVariantState.PUBLISHED,
|
||||
createDate: '2021-08-25T14:00:00Z',
|
||||
publishDate: null,
|
||||
updateDate: null,
|
||||
segment: null,
|
||||
isMandatory: true,
|
||||
unique: 'en-us',
|
||||
variant: {
|
||||
name: 'English variant name',
|
||||
culture: 'en-us',
|
||||
state: UmbDocumentVariantState.PUBLISHED,
|
||||
createDate: '2021-08-25T14:00:00Z',
|
||||
publishDate: null,
|
||||
updateDate: null,
|
||||
segment: null,
|
||||
},
|
||||
language: {
|
||||
entityType: 'language',
|
||||
name: 'English',
|
||||
unique: 'en-us',
|
||||
isDefault: true,
|
||||
isMandatory: true,
|
||||
fallbackIsoCode: null,
|
||||
},
|
||||
},
|
||||
/*
|
||||
// TODO: We do not support segments currently
|
||||
{
|
||||
name: 'English',
|
||||
culture: 'en-us',
|
||||
@@ -31,17 +43,27 @@ const modalData: UmbDocumentVariantPickerModalData = {
|
||||
publishDate: null,
|
||||
updateDate: null,
|
||||
segment: 'GTM',
|
||||
isMandatory: true,
|
||||
},
|
||||
*/
|
||||
{
|
||||
name: 'Danish',
|
||||
culture: 'da-dk',
|
||||
state: UmbDocumentVariantState.NOT_CREATED,
|
||||
createDate: null,
|
||||
publishDate: null,
|
||||
updateDate: null,
|
||||
segment: null,
|
||||
isMandatory: false,
|
||||
unique: 'da-dk',
|
||||
variant: {
|
||||
name: 'Danish variant name',
|
||||
culture: 'da-dk',
|
||||
state: UmbDocumentVariantState.NOT_CREATED,
|
||||
createDate: null,
|
||||
publishDate: null,
|
||||
updateDate: null,
|
||||
segment: null,
|
||||
},
|
||||
language: {
|
||||
entityType: 'language',
|
||||
name: 'Danish',
|
||||
unique: 'da-dk',
|
||||
isDefault: false,
|
||||
isMandatory: false,
|
||||
fallbackIsoCode: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -80,7 +102,6 @@ this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (modalManager) => {
|
||||
publishDate: '2021-08-25T14:00:00Z',
|
||||
updateDate: null,
|
||||
segment: null,
|
||||
isMandatory: true,
|
||||
},
|
||||
{
|
||||
name: 'English',
|
||||
@@ -90,7 +111,6 @@ this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (modalManager) => {
|
||||
publishDate: '2021-08-25T14:00:00Z',
|
||||
updateDate: null,
|
||||
segment: 'GTM',
|
||||
isMandatory: false,
|
||||
},
|
||||
{
|
||||
name: 'Danish',
|
||||
@@ -100,7 +120,6 @@ this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (modalManager) => {
|
||||
publishDate: null,
|
||||
updateDate: null,
|
||||
segment: null,
|
||||
isMandatory: false,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import { UMB_DOCUMENT_VARIANT_PICKER_MODAL_ALIAS } from '../manifests.js';
|
||||
import type { UmbDocumentVariantModel } from '../../types.js';
|
||||
import type { UmbDocumentVariantOptionModel } from '../../types.js';
|
||||
import { UmbModalToken } from '@umbraco-cms/backoffice/modal';
|
||||
|
||||
export type UmbDocumentVariantPickerModalType = 'save' | 'publish' | 'schedule' | 'unpublish';
|
||||
|
||||
export interface UmbDocumentVariantPickerModalData {
|
||||
type: 'save' | 'publish' | 'schedule' | 'unpublish';
|
||||
variants: Array<UmbDocumentVariantModel>;
|
||||
type: UmbDocumentVariantPickerModalType;
|
||||
options: Array<UmbDocumentVariantOptionModel>;
|
||||
}
|
||||
|
||||
export interface UmbDocumentVariantPickerModalValue {
|
||||
selection: Array<string | null>;
|
||||
selection: Array<string>;
|
||||
}
|
||||
|
||||
export const UMB_DOCUMENT_LANGUAGE_PICKER_MODAL = new UmbModalToken<
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { UmbDocumentDetailModel } from '../../types.js';
|
||||
import type { UmbDocumentDetailModel, UmbDocumentVariantModel } from '../../types.js';
|
||||
import { UMB_DOCUMENT_ENTITY_TYPE } from '../../entity.js';
|
||||
import { UmbId } from '@umbraco-cms/backoffice/id';
|
||||
import type { UmbDetailDataSource } from '@umbraco-cms/backoffice/repository';
|
||||
@@ -47,24 +47,32 @@ export class UmbDocumentServerDataSource implements UmbDetailDataSource<UmbDocum
|
||||
},
|
||||
isTrashed: false,
|
||||
values: [],
|
||||
variants: [
|
||||
{
|
||||
state: null,
|
||||
culture: null,
|
||||
segment: null,
|
||||
name: '',
|
||||
publishDate: null,
|
||||
createDate: null,
|
||||
updateDate: null,
|
||||
isMandatory: false,
|
||||
},
|
||||
],
|
||||
variants: [],
|
||||
...preset,
|
||||
};
|
||||
|
||||
return { data };
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new variant scaffold.
|
||||
* @returns A new variant scaffold.
|
||||
*/
|
||||
/*
|
||||
// TDOD: remove if not used
|
||||
createVariantScaffold(): UmbDocumentVariantModel {
|
||||
return {
|
||||
state: null,
|
||||
culture: null,
|
||||
segment: null,
|
||||
name: '',
|
||||
publishDate: null,
|
||||
createDate: null,
|
||||
updateDate: null,
|
||||
};
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fetches a Document with the given id from the server
|
||||
* @param {string} unique
|
||||
@@ -102,7 +110,6 @@ export class UmbDocumentServerDataSource implements UmbDetailDataSource<UmbDocum
|
||||
publishDate: variant.publishDate || null,
|
||||
createDate: variant.createDate,
|
||||
updateDate: variant.updateDate,
|
||||
isMandatory: false, // TODO: this is not correct. It will be solved when we know where to get the isMandatory from
|
||||
};
|
||||
}),
|
||||
urls: data.urls.map((url) => {
|
||||
|
||||
@@ -40,7 +40,8 @@ export class UmbDocumentPublishingServerDataSource {
|
||||
(variant) => {
|
||||
return {
|
||||
culture: variant.isCultureInvariant() ? null : variant.toCultureString(),
|
||||
schedule: variant.schedule,
|
||||
// TODO: NO, this does not belong as part of the UmbVariantID, we need another way to parse that around:
|
||||
//schedule: variant.schedule,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { UmbDocumentEntityType } from './entity.js';
|
||||
import type { UmbVariantModel } from '@umbraco-cms/backoffice/variant';
|
||||
import type { UmbVariantModel, UmbVariantOptionModel } from '@umbraco-cms/backoffice/variant';
|
||||
import type { UmbReferenceByUnique } from '@umbraco-cms/backoffice/models';
|
||||
import { DocumentVariantStateModel as UmbDocumentVariantState } from '@umbraco-cms/backoffice/external/backend-api';
|
||||
export { UmbDocumentVariantState };
|
||||
@@ -22,7 +22,6 @@ export interface UmbDocumentDetailModel {
|
||||
export interface UmbDocumentVariantModel extends UmbVariantModel {
|
||||
state: UmbDocumentVariantState | null;
|
||||
publishDate: string | null;
|
||||
isMandatory: boolean;
|
||||
}
|
||||
|
||||
export interface UmbDocumentUrlInfoModel {
|
||||
@@ -36,3 +35,5 @@ export interface UmbDocumentValueModel<ValueType = unknown> {
|
||||
alias: string;
|
||||
value: ValueType;
|
||||
}
|
||||
|
||||
export interface UmbDocumentVariantOptionModel extends UmbVariantOptionModel<UmbDocumentVariantModel> {}
|
||||
|
||||
@@ -1,62 +1,36 @@
|
||||
import type { UmbDocumentVariantOptionModel } from '../types.js';
|
||||
import { UmbDocumentWorkspaceSplitViewElement } from './document-workspace-split-view.element.js';
|
||||
import { UMB_DOCUMENT_WORKSPACE_CONTEXT } from './document-workspace.context-token.js';
|
||||
import { customElement, state, css, html } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import { UmbVariantId } from '@umbraco-cms/backoffice/variant';
|
||||
import type { ActiveVariant } from '@umbraco-cms/backoffice/workspace';
|
||||
import type { UmbRoute, UmbRouterSlotInitEvent } from '@umbraco-cms/backoffice/router';
|
||||
import type { UmbVariantModel } from '@umbraco-cms/backoffice/variant';
|
||||
|
||||
// TODO: This seem fully identical with Media Workspace Editor, so we can refactor this to a generic component. [NL]
|
||||
@customElement('umb-document-workspace-editor')
|
||||
export class UmbDocumentWorkspaceEditorElement extends UmbLitElement {
|
||||
//private _defaultVariant?: VariantViewModelBaseModel;
|
||||
|
||||
// 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.
|
||||
//
|
||||
// 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 UmbDocumentWorkspaceSplitViewElement();
|
||||
|
||||
#workspaceContext?: typeof UMB_DOCUMENT_WORKSPACE_CONTEXT.TYPE;
|
||||
|
||||
@state()
|
||||
_routes?: Array<UmbRoute>;
|
||||
|
||||
@state()
|
||||
_availableVariants: Array<UmbVariantModel> = [];
|
||||
|
||||
@state()
|
||||
_workspaceSplitViews: Array<ActiveVariant> = [];
|
||||
|
||||
#workspaceContext?: typeof UMB_DOCUMENT_WORKSPACE_CONTEXT.TYPE;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.consumeContext(UMB_DOCUMENT_WORKSPACE_CONTEXT, (instance) => {
|
||||
this.#workspaceContext = instance;
|
||||
this.#observeVariants();
|
||||
this.#observeSplitViews();
|
||||
});
|
||||
}
|
||||
|
||||
#observeVariants() {
|
||||
if (!this.#workspaceContext) return;
|
||||
this.observe(
|
||||
this.#workspaceContext.variants,
|
||||
(variants) => {
|
||||
this._availableVariants = variants;
|
||||
this._generateRoutes();
|
||||
},
|
||||
'_observeVariants',
|
||||
);
|
||||
}
|
||||
|
||||
#observeSplitViews() {
|
||||
if (!this.#workspaceContext) return;
|
||||
this.observe(
|
||||
this.#workspaceContext.splitView.activeVariantsInfo,
|
||||
(variants) => {
|
||||
this._workspaceSplitViews = variants;
|
||||
},
|
||||
'_observeSplitViews',
|
||||
);
|
||||
// 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');
|
||||
}
|
||||
|
||||
private _handleVariantFolderPart(index: number, folderPart: string) {
|
||||
@@ -66,17 +40,18 @@ export class UmbDocumentWorkspaceEditorElement extends UmbLitElement {
|
||||
this.#workspaceContext?.splitView.setActiveVariant(index, culture, segment);
|
||||
}
|
||||
|
||||
private _generateRoutes() {
|
||||
if (!this._availableVariants || this._availableVariants.length === 0) return;
|
||||
private async _generateRoutes(options: Array<UmbDocumentVariantOptionModel>) {
|
||||
if (!options || options.length === 0) return;
|
||||
|
||||
// Generate split view routes for all available routes
|
||||
const routes: Array<UmbRoute> = [];
|
||||
|
||||
// Split view routes:
|
||||
this._availableVariants.forEach((variantA) => {
|
||||
this._availableVariants.forEach((variantB) => {
|
||||
options.forEach((variantA) => {
|
||||
options.forEach((variantB) => {
|
||||
routes.push({
|
||||
path: new UmbVariantId(variantA).toString() + '_&_' + new UmbVariantId(variantB).toString(),
|
||||
// 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..
|
||||
@@ -90,9 +65,10 @@ export class UmbDocumentWorkspaceEditorElement extends UmbLitElement {
|
||||
});
|
||||
|
||||
// Single view:
|
||||
this._availableVariants.forEach((variant) => {
|
||||
options.forEach((variant) => {
|
||||
routes.push({
|
||||
path: new UmbVariantId(variant).toString(),
|
||||
// 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.
|
||||
@@ -106,11 +82,21 @@ export class UmbDocumentWorkspaceEditorElement extends UmbLitElement {
|
||||
// Using first single view as the default route for now (hence the math below):
|
||||
routes.push({
|
||||
path: '',
|
||||
redirectTo: routes[this._availableVariants.length * this._availableVariants.length]?.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);
|
||||
}
|
||||
|
||||
private _gotWorkspaceRoute = (e: UmbRouterSlotInitEvent) => {
|
||||
|
||||
@@ -2,12 +2,12 @@ import { UmbDocumentTypeDetailRepository } from '../../document-types/repository
|
||||
import { UmbDocumentPropertyDataContext } from '../property-dataset-context/document-property-dataset-context.js';
|
||||
import { UMB_DOCUMENT_ENTITY_TYPE } from '../entity.js';
|
||||
import { UmbDocumentDetailRepository } from '../repository/index.js';
|
||||
import type { UmbDocumentDetailModel } from '../types.js';
|
||||
import type { UmbDocumentVariantPickerModalData } from '../modals/index.js';
|
||||
import type { UmbDocumentDetailModel, UmbDocumentVariantModel, UmbDocumentVariantOptionModel } from '../types.js';
|
||||
import { umbPickDocumentVariantModal, type UmbDocumentVariantPickerModalType } from '../modals/index.js';
|
||||
import { UmbDocumentPublishingRepository } from '../repository/publishing/index.js';
|
||||
import { UMB_DOCUMENT_VARIANT_MANAGER_CONTEXT } from '../global-contexts/document-variant-manager.context.js';
|
||||
import { UmbUnpublishDocumentEntityAction } from '../entity-actions/unpublish.action.js';
|
||||
import { UMB_DOCUMENT_WORKSPACE_ALIAS } from './manifests.js';
|
||||
import type { UmbVariantId } from '@umbraco-cms/backoffice/variant';
|
||||
import { UmbVariantId } from '@umbraco-cms/backoffice/variant';
|
||||
import { UmbContentTypePropertyStructureManager } from '@umbraco-cms/backoffice/content-type';
|
||||
import {
|
||||
UmbEditableWorkspaceContextBase,
|
||||
@@ -15,13 +15,21 @@ import {
|
||||
type UmbVariantableWorkspaceContextInterface,
|
||||
type UmbPublishableWorkspaceContextInterface,
|
||||
} from '@umbraco-cms/backoffice/workspace';
|
||||
import { appendToFrozenArray, partialUpdateFrozenArray, UmbObjectState } from '@umbraco-cms/backoffice/observable-api';
|
||||
import {
|
||||
appendToFrozenArray,
|
||||
mergeObservables,
|
||||
naiveObjectComparison,
|
||||
UmbArrayState,
|
||||
UmbObjectState,
|
||||
} from '@umbraco-cms/backoffice/observable-api';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { UmbLanguageCollectionRepository, type UmbLanguageDetailModel } from '@umbraco-cms/backoffice/language';
|
||||
import { firstValueFrom } from '@umbraco-cms/backoffice/external/rxjs';
|
||||
|
||||
type EntityType = UmbDocumentDetailModel;
|
||||
export class UmbDocumentWorkspaceContext
|
||||
extends UmbEditableWorkspaceContextBase<EntityType>
|
||||
implements UmbVariantableWorkspaceContextInterface, UmbPublishableWorkspaceContextInterface
|
||||
implements UmbVariantableWorkspaceContextInterface<UmbDocumentVariantModel>, UmbPublishableWorkspaceContextInterface
|
||||
{
|
||||
//
|
||||
public readonly repository = new UmbDocumentDetailRepository(this);
|
||||
@@ -30,8 +38,14 @@ export class UmbDocumentWorkspaceContext
|
||||
/**
|
||||
* The document is the current state/draft version of the document.
|
||||
*/
|
||||
#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;
|
||||
}
|
||||
@@ -39,29 +53,37 @@ export class UmbDocumentWorkspaceContext
|
||||
readonly unique = this.#currentData.asObservablePart((data) => data?.unique);
|
||||
|
||||
readonly contentTypeUnique = this.#currentData.asObservablePart((data) => data?.documentType.unique);
|
||||
readonly contentTypeCollection = this.#currentData.asObservablePart((data) => data?.documentType.collection);
|
||||
readonly contentTypeHasCollection = this.#currentData.asObservablePart((data) => !!data?.documentType.collection);
|
||||
readonly variants = this.#currentData.asObservablePart((data) => data?.variants ?? []);
|
||||
readonly variantOptions = mergeObservables([this.variants, this.languages], ([variants, languages]) => {
|
||||
return languages.map((language) => {
|
||||
return {
|
||||
variant: variants.find((x) => x.culture === language.unique),
|
||||
language,
|
||||
// TODO: When including segments, this should be updated to include the segment as well. [NL]
|
||||
unique: language.unique, // This must be a variantId string!
|
||||
} as UmbDocumentVariantOptionModel;
|
||||
});
|
||||
});
|
||||
|
||||
readonly variants = this.#currentData.asObservablePart((data) => data?.variants || []);
|
||||
readonly urls = this.#currentData.asObservablePart((data) => data?.urls || []);
|
||||
readonly templateId = this.#currentData.asObservablePart((data) => data?.template?.unique || null);
|
||||
|
||||
readonly structure = new UmbContentTypePropertyStructureManager(this, new UmbDocumentTypeDetailRepository(this));
|
||||
readonly splitView = new UmbWorkspaceSplitViewManager();
|
||||
|
||||
#variantManagerContext?: typeof UMB_DOCUMENT_VARIANT_MANAGER_CONTEXT.TYPE;
|
||||
|
||||
constructor(host: UmbControllerHost) {
|
||||
super(host, UMB_DOCUMENT_WORKSPACE_ALIAS);
|
||||
|
||||
this.consumeContext(UMB_DOCUMENT_VARIANT_MANAGER_CONTEXT, (instance) => {
|
||||
this.#variantManagerContext = instance;
|
||||
});
|
||||
|
||||
this.observe(this.contentTypeUnique, (unique) => this.structure.loadType(unique));
|
||||
|
||||
/*
|
||||
TODO: Make something to ensure all variants are present in data? Seems like a good idea?.
|
||||
*/
|
||||
this.loadLanguages();
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -70,7 +92,7 @@ export class UmbDocumentWorkspaceContext
|
||||
if (!data) return undefined;
|
||||
|
||||
this.setIsNew(false);
|
||||
//this.#persisted.next(data);
|
||||
this.#persistedData.setValue(data);
|
||||
this.#currentData.setValue(data);
|
||||
return data || undefined;
|
||||
}
|
||||
@@ -86,6 +108,7 @@ export class UmbDocumentWorkspaceContext
|
||||
if (!data) return undefined;
|
||||
|
||||
this.setIsNew(true);
|
||||
this.#persistedData.setValue(undefined);
|
||||
this.#currentData.setValue(data);
|
||||
return data || undefined;
|
||||
}
|
||||
@@ -125,6 +148,7 @@ export class UmbDocumentWorkspaceContext
|
||||
}
|
||||
|
||||
setName(name: string, variantId?: UmbVariantId) {
|
||||
/*
|
||||
const oldVariants = this.#currentData.getValue()?.variants || [];
|
||||
const variants = partialUpdateFrozenArray(
|
||||
oldVariants,
|
||||
@@ -132,6 +156,9 @@ export class UmbDocumentWorkspaceContext
|
||||
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 });
|
||||
}
|
||||
|
||||
setTemplate(templateUnique: string) {
|
||||
@@ -171,7 +198,7 @@ export class UmbDocumentWorkspaceContext
|
||||
value: UmbDocumentValueModel,
|
||||
variantId?: UmbVariantId,
|
||||
) {
|
||||
if (!variantId) throw new Error('VariantId is missing');
|
||||
variantId ??= UmbVariantId.CreateInvariant();
|
||||
|
||||
const entry = { ...variantId.toObject(), alias, value };
|
||||
const currentData = this.getData();
|
||||
@@ -182,27 +209,95 @@ export class UmbDocumentWorkspaceContext
|
||||
(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);
|
||||
}
|
||||
}
|
||||
|
||||
async #createOrSave(type: UmbDocumentVariantPickerModalData['type']): Promise<UmbVariantId[]> {
|
||||
const data = this.getData();
|
||||
if (!data) throw new Error('Data is missing');
|
||||
if (!data.unique) throw new Error('Unique is missing');
|
||||
if (!this.#variantManagerContext) throw new Error('Variant manager context is missing');
|
||||
#calculateChangedVariants() {
|
||||
const persisted = this.#persistedData.getValue();
|
||||
const current = this.#currentData.getValue();
|
||||
if (!current) throw new Error('Current data is missing');
|
||||
|
||||
const activeVariants = this.splitView.getActiveVariants();
|
||||
const activeVariant = activeVariants.length ? activeVariants[0] : undefined;
|
||||
const changedVariants = current?.variants.map((variant) => {
|
||||
const persistedVariant = persisted?.variants.find((x) => UmbVariantId.Create(variant).compare(x));
|
||||
return {
|
||||
culture: variant.culture,
|
||||
segment: variant.segment,
|
||||
equal: persistedVariant ? naiveObjectComparison(variant, persistedVariant) : false,
|
||||
};
|
||||
});
|
||||
|
||||
const selectedVariants = await this.#variantManagerContext.pickVariants(
|
||||
data.variants, // TODO: Add a filter function to only show variants that have been changed
|
||||
type,
|
||||
activeVariant?.culture ?? undefined,
|
||||
const changedProperties = current?.values.map((value) => {
|
||||
const persistedValues = persisted?.values.find((x) => UmbVariantId.Create(value).compare(x));
|
||||
return {
|
||||
culture: value.culture,
|
||||
segment: value.segment,
|
||||
equal: persistedValues ? naiveObjectComparison(value, persistedValues) : false,
|
||||
};
|
||||
});
|
||||
|
||||
// calculate the variantIds of those who either have a change in properties or in variants:
|
||||
return (
|
||||
changedVariants
|
||||
?.concat(changedProperties ?? [])
|
||||
.filter((x) => x.equal === false)
|
||||
.map((x) => new UmbVariantId(x.culture, x.segment)) ?? []
|
||||
);
|
||||
}
|
||||
|
||||
#updateVariantData(variantId: UmbVariantId, update?: Partial<UmbDocumentVariantModel>) {
|
||||
const currentData = this.getData();
|
||||
if (!currentData) throw new Error('Data is missing');
|
||||
const variant = currentData.variants.find((x) => variantId.compare(x));
|
||||
const newVariants = appendToFrozenArray(
|
||||
currentData.variants,
|
||||
{
|
||||
state: null,
|
||||
name: '',
|
||||
publishDate: null,
|
||||
createDate: null,
|
||||
updateDate: null,
|
||||
...variantId.toObject(),
|
||||
...variant,
|
||||
...update,
|
||||
},
|
||||
(x) => variantId.compare(x),
|
||||
);
|
||||
this.#currentData.update({ variants: newVariants });
|
||||
}
|
||||
|
||||
async #pickVariantsForAction(type: UmbDocumentVariantPickerModalType): Promise<UmbVariantId[]> {
|
||||
const activeVariants = this.splitView.getActiveVariants();
|
||||
|
||||
// TODO: Picked variants should include the ones that has been changed (but not jet saved) this requires some more awareness about the state of runtime data. [NL]
|
||||
const activeVariantIds = activeVariants.map((activeVariant) => UmbVariantId.Create(activeVariant));
|
||||
const selected = activeVariantIds.concat(this.#calculateChangedVariants());
|
||||
const options = await firstValueFrom(this.variantOptions);
|
||||
|
||||
// If there is only one variant, we don't need to open the modal.
|
||||
if (options.length === 0) {
|
||||
throw new Error('No variants are available');
|
||||
} else if (options.length === 1) {
|
||||
// If only one option we will skip ahead and save the document with the only variant available:
|
||||
const firstVariant = new UmbVariantId(options[0].language.unique, null);
|
||||
return await this.#performSaveOrCreate([firstVariant]);
|
||||
}
|
||||
|
||||
const selectedVariants = await umbPickDocumentVariantModal(this, { type, options, selected });
|
||||
|
||||
// If no variants are selected, we don't save anything.
|
||||
if (!selectedVariants.length) return [];
|
||||
|
||||
return await this.#performSaveOrCreate(selectedVariants);
|
||||
}
|
||||
|
||||
async #performSaveOrCreate(selectedVariants: Array<UmbVariantId>) {
|
||||
const data = this.getData();
|
||||
if (!data) throw new Error('Data is missing');
|
||||
if (!data.unique) throw new Error('Unique is missing');
|
||||
|
||||
if (this.getIsNew()) {
|
||||
if ((await this.repository.create(data)).data !== undefined) {
|
||||
this.setIsNew(false);
|
||||
@@ -215,14 +310,18 @@ export class UmbDocumentWorkspaceContext
|
||||
}
|
||||
|
||||
async save() {
|
||||
await this.#createOrSave('save');
|
||||
await this.#pickVariantsForAction('save');
|
||||
const data = this.getData();
|
||||
if (!data) throw new Error('Data is missing');
|
||||
|
||||
this.#persistedData.setValue(data);
|
||||
this.#currentData.setValue(data);
|
||||
|
||||
this.saveComplete(data);
|
||||
}
|
||||
|
||||
public async publish() {
|
||||
const variantIds = await this.#createOrSave('publish');
|
||||
const variantIds = await this.#pickVariantsForAction('publish');
|
||||
const unique = this.getEntityId();
|
||||
if (variantIds.length && unique) {
|
||||
await this.publishingRepository.publish(unique, variantIds);
|
||||
@@ -237,9 +336,7 @@ export class UmbDocumentWorkspaceContext
|
||||
const unique = this.getEntityId();
|
||||
|
||||
if (!unique) throw new Error('Unique is missing');
|
||||
if (!this.#variantManagerContext) throw new Error('Variant manager context is missing');
|
||||
|
||||
this.#variantManagerContext.unpublish(unique);
|
||||
new UmbUnpublishDocumentEntityAction(this, '', unique, '').execute();
|
||||
}
|
||||
|
||||
async delete() {
|
||||
|
||||
@@ -13,6 +13,10 @@ export class UmbAppLanguageContext extends UmbBaseController implements UmbApi {
|
||||
appLanguage = this.#appLanguage.asObservable();
|
||||
appLanguageCulture = this.#appLanguage.asObservablePart((x) => x?.unique);
|
||||
|
||||
getAppCulture() {
|
||||
return this.#appLanguage.getValue()?.unique;
|
||||
}
|
||||
|
||||
constructor(host: UmbControllerHost) {
|
||||
super(host);
|
||||
this.provideContext(UMB_APP_LANGUAGE_CONTEXT, this);
|
||||
@@ -26,7 +30,7 @@ export class UmbAppLanguageContext extends UmbBaseController implements UmbApi {
|
||||
}
|
||||
|
||||
async #observeLanguages() {
|
||||
const { data } = await this.#languageCollectionRepository.requestCollection({ skip: 0, take: 100 });
|
||||
const { data } = await this.#languageCollectionRepository.requestCollection({});
|
||||
|
||||
// TODO: make this observable / update when languages are added/removed/updated
|
||||
if (data) {
|
||||
|
||||
@@ -65,11 +65,11 @@ export class UmbLanguageServerDataSource implements UmbDetailDataSource<UmbLangu
|
||||
// TODO: make data mapper to prevent errors
|
||||
const dataType: UmbLanguageDetailModel = {
|
||||
entityType: UMB_LANGUAGE_ENTITY_TYPE,
|
||||
fallbackIsoCode: data.fallbackIsoCode || null,
|
||||
fallbackIsoCode: data.fallbackIsoCode?.toLowerCase() || null,
|
||||
isDefault: data.isDefault,
|
||||
isMandatory: data.isMandatory,
|
||||
name: data.name,
|
||||
unique: data.isoCode,
|
||||
unique: data.isoCode.toLowerCase(),
|
||||
};
|
||||
|
||||
return { data: dataType };
|
||||
@@ -86,10 +86,10 @@ export class UmbLanguageServerDataSource implements UmbDetailDataSource<UmbLangu
|
||||
|
||||
// TODO: make data mapper to prevent errors
|
||||
const requestBody: CreateLanguageRequestModel = {
|
||||
fallbackIsoCode: model.fallbackIsoCode,
|
||||
fallbackIsoCode: model.fallbackIsoCode?.toLowerCase(),
|
||||
isDefault: model.isDefault,
|
||||
isMandatory: model.isMandatory,
|
||||
isoCode: model.unique,
|
||||
isoCode: model.unique.toLowerCase(),
|
||||
name: model.name,
|
||||
};
|
||||
|
||||
@@ -118,7 +118,7 @@ export class UmbLanguageServerDataSource implements UmbDetailDataSource<UmbLangu
|
||||
|
||||
// TODO: make data mapper to prevent errors
|
||||
const requestBody: LanguageModelBaseModel = {
|
||||
fallbackIsoCode: model.fallbackIsoCode,
|
||||
fallbackIsoCode: model.fallbackIsoCode?.toLowerCase(),
|
||||
isDefault: model.isDefault,
|
||||
isMandatory: model.isMandatory,
|
||||
name: model.name,
|
||||
@@ -127,7 +127,7 @@ export class UmbLanguageServerDataSource implements UmbDetailDataSource<UmbLangu
|
||||
const { error } = await tryExecuteAndNotify(
|
||||
this.#host,
|
||||
LanguageResource.putLanguageByIsoCode({
|
||||
isoCode: model.unique,
|
||||
isoCode: model.unique.toLowerCase(),
|
||||
requestBody,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { SavedLogSearchResponseModel } from '@umbraco-cms/backoffice/extern
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { query as getQuery, path, toQueryString } from '@umbraco-cms/backoffice/router';
|
||||
import type { UmbModalManagerContext, UmbModalContext } from '@umbraco-cms/backoffice/modal';
|
||||
import { UMB_MODAL_MANAGER_CONTEXT, UmbModalToken, UMB_CONFIRM_MODAL } from '@umbraco-cms/backoffice/modal';
|
||||
import { UMB_MODAL_MANAGER_CONTEXT, UmbModalToken, umbConfirmModal } from '@umbraco-cms/backoffice/modal';
|
||||
|
||||
import './log-viewer-search-input-modal.element.js';
|
||||
import type { UmbDropdownElement } from '@umbraco-cms/backoffice/components';
|
||||
@@ -126,20 +126,16 @@ export class UmbLogViewerSearchInputElement extends UmbLitElement {
|
||||
this.#logViewerContext?.saveSearch(savedSearch);
|
||||
}
|
||||
|
||||
#removeSearch(name: string) {
|
||||
const modalContext = this._modalContext?.open(UMB_CONFIRM_MODAL, {
|
||||
data: {
|
||||
headline: this.localize.term('logViewer_deleteSavedSearch'),
|
||||
content: `${this.localize.term('defaultdialogs_confirmdelete')} ${name}?`,
|
||||
color: 'danger',
|
||||
confirmLabel: 'Delete',
|
||||
},
|
||||
async #removeSearch(name: string) {
|
||||
await umbConfirmModal(this, {
|
||||
headline: this.localize.term('logViewer_deleteSavedSearch'),
|
||||
content: `${this.localize.term('defaultdialogs_confirmdelete')} ${name}?`,
|
||||
color: 'danger',
|
||||
confirmLabel: 'Delete',
|
||||
});
|
||||
|
||||
modalContext?.onSubmit().then(() => {
|
||||
this.#logViewerContext?.removeSearch({ name });
|
||||
//this.dispatchEvent(new UmbDeleteEvent());
|
||||
});
|
||||
this.#logViewerContext?.removeSearch({ name });
|
||||
//this.dispatchEvent(new UmbDeleteEvent());
|
||||
}
|
||||
|
||||
#openSaveSearchDialog() {
|
||||
|
||||
@@ -4,11 +4,10 @@ import { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui';
|
||||
import { css, html, customElement, property, state, ifDefined, nothing } from '@umbraco-cms/backoffice/external/lit';
|
||||
import type { UmbConfirmModalData } from '@umbraco-cms/backoffice/modal';
|
||||
import {
|
||||
UMB_CONFIRM_MODAL,
|
||||
UMB_MODAL_MANAGER_CONTEXT,
|
||||
UMB_PROPERTY_SETTINGS_MODAL,
|
||||
UMB_WORKSPACE_MODAL,
|
||||
UmbModalRouteRegistrationController,
|
||||
umbConfirmModal,
|
||||
} from '@umbraco-cms/backoffice/modal';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { generateAlias } from '@umbraco-cms/backoffice/utils';
|
||||
@@ -57,7 +56,6 @@ export class UmbMediaTypeWorkspacePropertyElement extends UmbLitElement {
|
||||
#dataTypeDetailRepository = new UmbDataTypeDetailRepository(this);
|
||||
|
||||
#modalRegistration;
|
||||
private _modalManagerContext?: typeof UMB_MODAL_MANAGER_CONTEXT.TYPE;
|
||||
|
||||
@state()
|
||||
protected _modalRoute?: string;
|
||||
@@ -113,10 +111,6 @@ export class UmbMediaTypeWorkspacePropertyElement extends UmbLitElement {
|
||||
.observeRouteBuilder((routeBuilder) => {
|
||||
this._editMediaTypePath = routeBuilder({});
|
||||
});
|
||||
|
||||
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (context) => {
|
||||
this._modalManagerContext = context;
|
||||
});
|
||||
}
|
||||
|
||||
_partialUpdate(partialObject: UmbPropertyTypeModel) {
|
||||
@@ -137,7 +131,7 @@ export class UmbMediaTypeWorkspacePropertyElement extends UmbLitElement {
|
||||
this._aliasLocked = !this._aliasLocked;
|
||||
}
|
||||
|
||||
#requestRemove(e: Event) {
|
||||
async #requestRemove(e: Event) {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
if (!this.property || !this.property.id) return;
|
||||
@@ -154,17 +148,9 @@ export class UmbMediaTypeWorkspacePropertyElement extends UmbLitElement {
|
||||
color: 'danger',
|
||||
};
|
||||
|
||||
const modalHandler = this._modalManagerContext?.open(UMB_CONFIRM_MODAL, { data: modalData });
|
||||
await umbConfirmModal(this, modalData);
|
||||
|
||||
modalHandler
|
||||
?.onSubmit()
|
||||
.then(() => {
|
||||
this.dispatchEvent(new CustomEvent('property-delete'));
|
||||
})
|
||||
.catch(() => {
|
||||
// We do not need to react to cancel, so we will leave an empty method to prevent Uncaught Promise Rejection error.
|
||||
return;
|
||||
});
|
||||
this.dispatchEvent(new CustomEvent('property-delete'));
|
||||
}
|
||||
|
||||
#onNameChange(event: UUIInputEvent) {
|
||||
|
||||
@@ -11,7 +11,7 @@ import { UMB_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace';
|
||||
import type { UmbRoute, UmbRouterSlotChangeEvent, UmbRouterSlotInitEvent } from '@umbraco-cms/backoffice/router';
|
||||
import type { UmbWorkspaceViewElement } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import type { UmbConfirmModalData } from '@umbraco-cms/backoffice/modal';
|
||||
import { UMB_CONFIRM_MODAL, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
|
||||
import { umbConfirmModal } from '@umbraco-cms/backoffice/modal';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import type { UmbSorterConfig } from '@umbraco-cms/backoffice/sorter';
|
||||
import { UmbSorterController } from '@umbraco-cms/backoffice/sorter';
|
||||
@@ -89,8 +89,6 @@ export class UmbMediaTypeWorkspaceViewEditElement extends UmbLitElement implemen
|
||||
|
||||
private _tabsStructureHelper = new UmbContentTypeContainerStructureHelper<UmbMediaTypeDetailModel>(this);
|
||||
|
||||
private _modalManagerContext?: typeof UMB_MODAL_MANAGER_CONTEXT.TYPE;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.sorter = new UmbSorterController(this, this.config);
|
||||
@@ -116,10 +114,6 @@ export class UmbMediaTypeWorkspaceViewEditElement extends UmbLitElement implemen
|
||||
);
|
||||
this._observeRootGroups();
|
||||
});
|
||||
|
||||
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (context) => {
|
||||
this._modalManagerContext = context;
|
||||
});
|
||||
}
|
||||
|
||||
private _observeRootGroups() {
|
||||
@@ -188,7 +182,7 @@ export class UmbMediaTypeWorkspaceViewEditElement extends UmbLitElement implemen
|
||||
this._routes = routes;
|
||||
}
|
||||
|
||||
#requestRemoveTab(tab: PropertyTypeContainerModelBaseModel | undefined) {
|
||||
async #requestRemoveTab(tab: PropertyTypeContainerModelBaseModel | undefined) {
|
||||
const modalData: UmbConfirmModalData = {
|
||||
headline: 'Delete tab',
|
||||
content: html`<umb-localize key="contentTypeEditor_confirmDeleteTabMessage" .args=${[tab?.name ?? tab?.id]}>
|
||||
@@ -204,12 +198,9 @@ export class UmbMediaTypeWorkspaceViewEditElement extends UmbLitElement implemen
|
||||
};
|
||||
|
||||
// TODO: If this tab is composed of other tabs, then notify that it will only delete the local tab.
|
||||
await umbConfirmModal(this, modalData);
|
||||
|
||||
const modalHandler = this._modalManagerContext?.open(UMB_CONFIRM_MODAL, { data: modalData });
|
||||
|
||||
modalHandler?.onSubmit().then(() => {
|
||||
this.#remove(tab?.id);
|
||||
});
|
||||
this.#remove(tab?.id);
|
||||
}
|
||||
#remove(tabId?: string) {
|
||||
if (!tabId) return;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { UmbMediaEntityType } from './entity.js';
|
||||
import type { UmbReferenceByUnique } from '@umbraco-cms/backoffice/models';
|
||||
import type { UmbVariantModel } from '@umbraco-cms/backoffice/variant';
|
||||
import type { UmbVariantModel, UmbVariantOptionModel } from '@umbraco-cms/backoffice/variant';
|
||||
import type { MediaUrlInfoModel, MediaValueModel } from '@umbraco-cms/backoffice/external/backend-api';
|
||||
import type { UmbReferenceByUnique } from '@umbraco-cms/backoffice/models';
|
||||
|
||||
export interface UmbMediaDetailModel {
|
||||
mediaType: {
|
||||
@@ -16,3 +16,5 @@ export interface UmbMediaDetailModel {
|
||||
values: Array<MediaValueModel>;
|
||||
variants: Array<UmbVariantModel>;
|
||||
}
|
||||
|
||||
export interface UmbMediaVariantOptionModel extends UmbVariantOptionModel<UmbVariantModel> {}
|
||||
|
||||
@@ -1,28 +1,19 @@
|
||||
import type { UmbMediaVariantOptionModel } from '../types.js';
|
||||
import { UmbMediaWorkspaceSplitViewElement } from './media-workspace-split-view.element.js';
|
||||
import { UMB_MEDIA_WORKSPACE_CONTEXT } from './media-workspace.context-token.js';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { customElement, state, css, html } from '@umbraco-cms/backoffice/external/lit';
|
||||
import type { UmbVariantModel } from '@umbraco-cms/backoffice/variant';
|
||||
import { UmbVariantId } from '@umbraco-cms/backoffice/variant';
|
||||
import type { UmbRoute, UmbRouterSlotInitEvent } from '@umbraco-cms/backoffice/router';
|
||||
import type { ActiveVariant } from '@umbraco-cms/backoffice/workspace';
|
||||
@customElement('umb-media-workspace-editor')
|
||||
export class UmbMediaWorkspaceEditorElement extends UmbLitElement {
|
||||
//private _defaultVariant?: VariantViewModelBaseModel;
|
||||
|
||||
// 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.
|
||||
//
|
||||
// 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 UmbMediaWorkspaceSplitViewElement();
|
||||
|
||||
@state()
|
||||
_routes?: Array<UmbRoute>;
|
||||
|
||||
@state()
|
||||
_availableVariants: Array<UmbVariantModel> = [];
|
||||
|
||||
@state()
|
||||
_workspaceSplitViews: Array<ActiveVariant> = [];
|
||||
|
||||
#workspaceContext?: typeof UMB_MEDIA_WORKSPACE_CONTEXT.TYPE;
|
||||
|
||||
constructor() {
|
||||
@@ -31,31 +22,12 @@ export class UmbMediaWorkspaceEditorElement extends UmbLitElement {
|
||||
this.consumeContext(UMB_MEDIA_WORKSPACE_CONTEXT, (instance) => {
|
||||
this.#workspaceContext = instance;
|
||||
this.#observeVariants();
|
||||
this.#observeSplitViews();
|
||||
});
|
||||
}
|
||||
|
||||
#observeVariants() {
|
||||
if (!this.#workspaceContext) return;
|
||||
this.observe(
|
||||
this.#workspaceContext.variants,
|
||||
(variants) => {
|
||||
this._availableVariants = variants;
|
||||
this._generateRoutes();
|
||||
},
|
||||
'_observeVariants',
|
||||
);
|
||||
}
|
||||
|
||||
#observeSplitViews() {
|
||||
if (!this.#workspaceContext) return;
|
||||
this.observe(
|
||||
this.#workspaceContext.splitView.activeVariantsInfo,
|
||||
(variants) => {
|
||||
this._workspaceSplitViews = variants;
|
||||
},
|
||||
'_observeSplitViews',
|
||||
);
|
||||
this.observe(this.#workspaceContext.variantOptions, (options) => this._generateRoutes(options), '_observeVariants');
|
||||
}
|
||||
|
||||
private _handleVariantFolderPart(index: number, folderPart: string) {
|
||||
@@ -65,17 +37,18 @@ export class UmbMediaWorkspaceEditorElement extends UmbLitElement {
|
||||
this.#workspaceContext?.splitView.setActiveVariant(index, culture, segment);
|
||||
}
|
||||
|
||||
private _generateRoutes() {
|
||||
if (!this._availableVariants || this._availableVariants.length === 0) return;
|
||||
private async _generateRoutes(variants: Array<UmbMediaVariantOptionModel>) {
|
||||
if (!variants || variants.length === 0) return;
|
||||
|
||||
// Generate split view routes for all available routes
|
||||
const routes: Array<UmbRoute> = [];
|
||||
|
||||
// Split view routes:
|
||||
this._availableVariants.forEach((variantA) => {
|
||||
this._availableVariants.forEach((variantB) => {
|
||||
variants.forEach((variantA) => {
|
||||
variants.forEach((variantB) => {
|
||||
routes.push({
|
||||
path: new UmbVariantId(variantA).toString() + '_&_' + new UmbVariantId(variantB).toString(),
|
||||
// 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..
|
||||
@@ -89,9 +62,10 @@ export class UmbMediaWorkspaceEditorElement extends UmbLitElement {
|
||||
});
|
||||
|
||||
// Single view:
|
||||
this._availableVariants.forEach((variant) => {
|
||||
variants.forEach((variant) => {
|
||||
routes.push({
|
||||
path: new UmbVariantId(variant).toString(),
|
||||
// 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.
|
||||
@@ -105,11 +79,21 @@ export class UmbMediaWorkspaceEditorElement extends UmbLitElement {
|
||||
// Using first single view as the default route for now (hence the math below):
|
||||
routes.push({
|
||||
path: '',
|
||||
redirectTo: routes[this._availableVariants.length * this._availableVariants.length]?.path,
|
||||
redirectTo: routes[variants.length * variants.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);
|
||||
}
|
||||
|
||||
private _gotWorkspaceRoute = (e: UmbRouterSlotInitEvent) => {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { UmbMediaTypeDetailRepository } from '../../media-types/repository/detai
|
||||
import { UmbMediaPropertyDataContext } from '../property-dataset-context/media-property-dataset-context.js';
|
||||
import { UMB_MEDIA_ENTITY_TYPE } from '../entity.js';
|
||||
import { UmbMediaDetailRepository } from '../repository/index.js';
|
||||
import type { UmbMediaDetailModel } from '../types.js';
|
||||
import type { UmbMediaDetailModel, UmbMediaVariantOptionModel } from '../types.js';
|
||||
import type { UmbVariantId } from '@umbraco-cms/backoffice/variant';
|
||||
import { UmbContentTypePropertyStructureManager } from '@umbraco-cms/backoffice/content-type';
|
||||
import {
|
||||
@@ -10,8 +10,15 @@ import {
|
||||
UmbWorkspaceSplitViewManager,
|
||||
type UmbVariantableWorkspaceContextInterface,
|
||||
} from '@umbraco-cms/backoffice/workspace';
|
||||
import { appendToFrozenArray, partialUpdateFrozenArray, UmbObjectState } from '@umbraco-cms/backoffice/observable-api';
|
||||
import {
|
||||
appendToFrozenArray,
|
||||
mergeObservables,
|
||||
partialUpdateFrozenArray,
|
||||
UmbArrayState,
|
||||
UmbObjectState,
|
||||
} from '@umbraco-cms/backoffice/observable-api';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { UmbLanguageCollectionRepository, type UmbLanguageDetailModel } from '@umbraco-cms/backoffice/language';
|
||||
|
||||
type EntityType = UmbMediaDetailModel;
|
||||
export class UmbMediaWorkspaceContext
|
||||
@@ -26,6 +33,11 @@ export class UmbMediaWorkspaceContext
|
||||
*/
|
||||
#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;
|
||||
}
|
||||
@@ -35,6 +47,16 @@ export class UmbMediaWorkspaceContext
|
||||
readonly contentTypeCollection = this.#currentData.asObservablePart((data) => data?.mediaType.collection);
|
||||
|
||||
readonly variants = this.#currentData.asObservablePart((data) => data?.variants || []);
|
||||
readonly variantOptions = mergeObservables([this.variants, this.languages], ([variants, languages]) => {
|
||||
return languages.map((language) => {
|
||||
return {
|
||||
variant: variants.find((x) => x.culture === language.unique),
|
||||
language,
|
||||
// TODO: When including segments, this should be updated to include the segment as well. [NL]
|
||||
unique: language.unique, // This must be a variantId string!
|
||||
} as UmbMediaVariantOptionModel;
|
||||
});
|
||||
});
|
||||
readonly urls = this.#currentData.asObservablePart((data) => data?.urls || []);
|
||||
|
||||
readonly structure = new UmbContentTypePropertyStructureManager(this, new UmbMediaTypeDetailRepository(this));
|
||||
@@ -47,6 +69,11 @@ export class UmbMediaWorkspaceContext
|
||||
this.observe(this.contentTypeUnique, (unique) => this.structure.loadType(unique));
|
||||
}
|
||||
|
||||
async loadLanguages() {
|
||||
const { data } = await this.#languageRepository.requestCollection({});
|
||||
this.#languages.setValue(data?.items ?? []);
|
||||
}
|
||||
|
||||
async load(unique: string) {
|
||||
this.#getDataPromise = this.repository.requestByUnique(unique);
|
||||
const { data } = await this.#getDataPromise;
|
||||
|
||||
@@ -4,8 +4,7 @@ import type { PackageDefinitionResponseModel } from '@umbraco-cms/backoffice/ext
|
||||
import { PackageResource } from '@umbraco-cms/backoffice/external/backend-api';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources';
|
||||
import type { UmbModalManagerContext } from '@umbraco-cms/backoffice/modal';
|
||||
import { UMB_MODAL_MANAGER_CONTEXT, UMB_CONFIRM_MODAL } from '@umbraco-cms/backoffice/modal';
|
||||
import { umbConfirmModal } from '@umbraco-cms/backoffice/modal';
|
||||
|
||||
@customElement('umb-packages-created-overview')
|
||||
export class UmbPackagesCreatedOverviewElement extends UmbLitElement {
|
||||
@@ -23,19 +22,10 @@ export class UmbPackagesCreatedOverviewElement extends UmbLitElement {
|
||||
@state()
|
||||
private _total?: number;
|
||||
|
||||
private _modalContext?: UmbModalManagerContext;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this.#getPackages();
|
||||
|
||||
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => {
|
||||
this._modalContext = instance;
|
||||
});
|
||||
}
|
||||
|
||||
async #getPackages() {
|
||||
@@ -106,17 +96,13 @@ export class UmbPackagesCreatedOverviewElement extends UmbLitElement {
|
||||
|
||||
async #deletePackage(p: PackageDefinitionResponseModel) {
|
||||
if (!p.id) return;
|
||||
const modalContext = this._modalContext?.open(UMB_CONFIRM_MODAL, {
|
||||
data: {
|
||||
color: 'danger',
|
||||
headline: `Remove ${p.name}?`,
|
||||
content: 'Are you sure you want to delete this package',
|
||||
confirmLabel: 'Delete',
|
||||
},
|
||||
await umbConfirmModal(this, {
|
||||
color: 'danger',
|
||||
headline: `Remove ${p.name}?`,
|
||||
content: 'Are you sure you want to delete this package',
|
||||
confirmLabel: 'Delete',
|
||||
});
|
||||
|
||||
await modalContext?.onSubmit();
|
||||
|
||||
const { error } = await tryExecuteAndNotify(this, PackageResource.deletePackageCreatedById({ id: p.id }));
|
||||
if (error) return;
|
||||
const index = this._createdPackages.findIndex((x) => x.id === p.id);
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { html, css, nothing, ifDefined, customElement, property, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import type { UUIButtonState } from '@umbraco-cms/backoffice/external/uui';
|
||||
import { map } from '@umbraco-cms/backoffice/external/rxjs';
|
||||
import type { UmbModalManagerContext } from '@umbraco-cms/backoffice/modal';
|
||||
import { UMB_MODAL_MANAGER_CONTEXT, UMB_CONFIRM_MODAL } from '@umbraco-cms/backoffice/modal';
|
||||
import { umbConfirmModal } from '@umbraco-cms/backoffice/modal';
|
||||
import { createExtensionElement } from '@umbraco-cms/backoffice/extension-api';
|
||||
import type { ManifestPackageView } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
@@ -43,7 +42,6 @@ export class UmbInstalledPackagesSectionViewItemElement extends UmbLitElement {
|
||||
private _packageView?: ManifestPackageView;
|
||||
|
||||
#notificationContext?: UmbNotificationContext;
|
||||
#modalContext?: UmbModalManagerContext;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@@ -51,9 +49,6 @@ export class UmbInstalledPackagesSectionViewItemElement extends UmbLitElement {
|
||||
this.consumeContext(UMB_NOTIFICATION_CONTEXT, (instance) => {
|
||||
this.#notificationContext = instance;
|
||||
});
|
||||
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => {
|
||||
this.#modalContext = instance;
|
||||
});
|
||||
}
|
||||
|
||||
#observePackageView() {
|
||||
@@ -76,16 +71,13 @@ export class UmbInstalledPackagesSectionViewItemElement extends UmbLitElement {
|
||||
|
||||
async _onMigration() {
|
||||
if (!this.name) return;
|
||||
const modalContext = this.#modalContext?.open(UMB_CONFIRM_MODAL, {
|
||||
data: {
|
||||
color: 'positive',
|
||||
headline: `Run migrations for ${this.name}?`,
|
||||
content: `Do you want to start run migrations for ${this.name}`,
|
||||
confirmLabel: 'Run migrations',
|
||||
},
|
||||
});
|
||||
|
||||
await modalContext?.onSubmit();
|
||||
await umbConfirmModal(this, {
|
||||
color: 'positive',
|
||||
headline: `Run migrations for ${this.name}?`,
|
||||
content: `Do you want to start run migrations for ${this.name}`,
|
||||
confirmLabel: 'Run migrations',
|
||||
});
|
||||
|
||||
this._migrationButtonState = 'waiting';
|
||||
const { error } = await tryExecuteAndNotify(
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { UUIButtonState } from '@umbraco-cms/backoffice/external/uui';
|
||||
import { css, html, nothing, customElement, property, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import type { UmbModalManagerContext } from '@umbraco-cms/backoffice/modal';
|
||||
import { UMB_MODAL_MANAGER_CONTEXT, UMB_CONFIRM_MODAL } from '@umbraco-cms/backoffice/modal';
|
||||
import { umbConfirmModal } from '@umbraco-cms/backoffice/modal';
|
||||
import type { IndexResponseModel } from '@umbraco-cms/backoffice/external/backend-api';
|
||||
import { HealthStatusModel, IndexerResource } from '@umbraco-cms/backoffice/external/backend-api';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
@@ -24,16 +23,6 @@ export class UmbDashboardExamineIndexElement extends UmbLitElement {
|
||||
@state()
|
||||
private _loading = true;
|
||||
|
||||
private _modalContext?: UmbModalManagerContext;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (_instance) => {
|
||||
this._modalContext = _instance;
|
||||
});
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._getIndexData();
|
||||
@@ -55,22 +44,19 @@ export class UmbDashboardExamineIndexElement extends UmbLitElement {
|
||||
}
|
||||
|
||||
private async _onRebuildHandler() {
|
||||
const modalContext = this._modalContext?.open(UMB_CONFIRM_MODAL, {
|
||||
data: {
|
||||
headline: `Rebuild ${this.indexName}`,
|
||||
content: html`
|
||||
This will cause the index to be rebuilt.<br />
|
||||
Depending on how much content there is in your site this could take a while.<br />
|
||||
It is not recommended to rebuild an index during times of high website traffic or when editors are editing
|
||||
content.
|
||||
`,
|
||||
color: 'danger',
|
||||
confirmLabel: 'Rebuild',
|
||||
},
|
||||
});
|
||||
modalContext?.onSubmit().then(() => {
|
||||
this._rebuild();
|
||||
await umbConfirmModal(this, {
|
||||
headline: `Rebuild ${this.indexName}`,
|
||||
content: html`
|
||||
This will cause the index to be rebuilt.<br />
|
||||
Depending on how much content there is in your site this could take a while.<br />
|
||||
It is not recommended to rebuild an index during times of high website traffic or when editors are editing
|
||||
content.
|
||||
`,
|
||||
color: 'danger',
|
||||
confirmLabel: 'Rebuild',
|
||||
});
|
||||
|
||||
this._rebuild();
|
||||
}
|
||||
private async _rebuild() {
|
||||
this._buttonState = 'waiting';
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { UUIButtonState } from '@umbraco-cms/backoffice/external/uui';
|
||||
import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import type { UmbModalManagerContext } from '@umbraco-cms/backoffice/modal';
|
||||
import { UMB_MODAL_MANAGER_CONTEXT, UMB_CONFIRM_MODAL } from '@umbraco-cms/backoffice/modal';
|
||||
import { umbConfirmModal } from '@umbraco-cms/backoffice/modal';
|
||||
import { PublishedCacheResource } from '@umbraco-cms/backoffice/external/backend-api';
|
||||
import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
@@ -24,16 +23,6 @@ export class UmbDashboardPublishedStatusElement extends UmbLitElement {
|
||||
@state()
|
||||
private _buttonStateCollect: UUIButtonState = undefined;
|
||||
|
||||
private _modalContext?: UmbModalManagerContext;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => {
|
||||
this._modalContext = instance;
|
||||
});
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._getPublishedStatus();
|
||||
@@ -68,17 +57,14 @@ export class UmbDashboardPublishedStatusElement extends UmbLitElement {
|
||||
}
|
||||
}
|
||||
private async _onReloadCacheHandler() {
|
||||
const modalContext = this._modalContext?.open(UMB_CONFIRM_MODAL, {
|
||||
data: {
|
||||
headline: 'Reload',
|
||||
content: html` Trigger a in-memory and local file cache reload on all servers.`,
|
||||
color: 'danger',
|
||||
confirmLabel: 'Continue',
|
||||
},
|
||||
});
|
||||
modalContext?.onSubmit().then(() => {
|
||||
this._reloadMemoryCache();
|
||||
await umbConfirmModal(this, {
|
||||
headline: 'Reload',
|
||||
content: html` Trigger a in-memory and local file cache reload on all servers.`,
|
||||
color: 'danger',
|
||||
confirmLabel: 'Continue',
|
||||
});
|
||||
|
||||
this._reloadMemoryCache();
|
||||
}
|
||||
|
||||
// Rebuild
|
||||
@@ -93,17 +79,14 @@ export class UmbDashboardPublishedStatusElement extends UmbLitElement {
|
||||
}
|
||||
|
||||
private async _onRebuildCacheHandler() {
|
||||
const modalContex = this._modalContext?.open(UMB_CONFIRM_MODAL, {
|
||||
data: {
|
||||
headline: 'Rebuild',
|
||||
content: html` Rebuild content in cmsContentNu database table. Expensive.`,
|
||||
color: 'danger',
|
||||
confirmLabel: 'Continue',
|
||||
},
|
||||
});
|
||||
modalContex?.onSubmit().then(() => {
|
||||
this._rebuildDatabaseCache();
|
||||
await umbConfirmModal(this, {
|
||||
headline: 'Rebuild',
|
||||
content: html` Rebuild content in cmsContentNu database table. Expensive.`,
|
||||
color: 'danger',
|
||||
confirmLabel: 'Continue',
|
||||
});
|
||||
|
||||
this._rebuildDatabaseCache();
|
||||
}
|
||||
|
||||
//Collect
|
||||
@@ -118,17 +101,13 @@ export class UmbDashboardPublishedStatusElement extends UmbLitElement {
|
||||
}
|
||||
|
||||
private async _onSnapshotCacheHandler() {
|
||||
const modalContex = this._modalContext?.open(UMB_CONFIRM_MODAL, {
|
||||
data: {
|
||||
headline: 'Snapshot',
|
||||
content: html` Trigger a NuCache snapshots collection.`,
|
||||
color: 'danger',
|
||||
confirmLabel: 'Continue',
|
||||
},
|
||||
});
|
||||
modalContex?.onSubmit().then(() => {
|
||||
this._cacheCollect();
|
||||
await umbConfirmModal(this, {
|
||||
headline: 'Snapshot',
|
||||
content: html` Trigger a NuCache snapshots collection.`,
|
||||
color: 'danger',
|
||||
confirmLabel: 'Continue',
|
||||
});
|
||||
this._cacheCollect();
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { UMB_CURRENT_USER_MODAL } from './modals/current-user/current-user-modal.token.js';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import type { CSSResultGroup } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { css, html, customElement, state, ifDefined } from '@umbraco-cms/backoffice/external/lit';
|
||||
import type { UmbModalManagerContext } from '@umbraco-cms/backoffice/modal';
|
||||
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
@@ -12,6 +12,9 @@ export class UmbCurrentUserHeaderAppElement extends UmbLitElement {
|
||||
@state()
|
||||
private _currentUser?: UmbCurrentUserModel;
|
||||
|
||||
@state()
|
||||
private _userAvatarUrls: Array<{ url: string; scale: string }> = [];
|
||||
|
||||
#currentUserContext?: typeof UMB_CURRENT_USER_CONTEXT.TYPE;
|
||||
#modalManagerContext?: UmbModalManagerContext;
|
||||
|
||||
@@ -35,6 +38,8 @@ export class UmbCurrentUserHeaderAppElement extends UmbLitElement {
|
||||
this.#currentUserContext.currentUser,
|
||||
(currentUser) => {
|
||||
this._currentUser = currentUser;
|
||||
if (!currentUser) return;
|
||||
this.#setUserAvatarUrls(currentUser);
|
||||
},
|
||||
'umbCurrentUserObserver',
|
||||
);
|
||||
@@ -44,6 +49,41 @@ export class UmbCurrentUserHeaderAppElement extends UmbLitElement {
|
||||
this.#modalManagerContext?.open(UMB_CURRENT_USER_MODAL);
|
||||
}
|
||||
|
||||
#setUserAvatarUrls = async (user: UmbCurrentUserModel | undefined) => {
|
||||
if (!user || !user.avatarUrls || user.avatarUrls.length === 0) {
|
||||
this._userAvatarUrls = [];
|
||||
return;
|
||||
}
|
||||
|
||||
this._userAvatarUrls = [
|
||||
{
|
||||
scale: '1x',
|
||||
url: user.avatarUrls?.[0],
|
||||
},
|
||||
{
|
||||
scale: '2x',
|
||||
url: user.avatarUrls?.[1],
|
||||
},
|
||||
{
|
||||
scale: '3x',
|
||||
url: user.avatarUrls?.[2],
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
#getAvatarSrcset() {
|
||||
let string = '';
|
||||
|
||||
this._userAvatarUrls?.forEach((url) => {
|
||||
string += `${url.url} ${url.scale},`;
|
||||
});
|
||||
return string;
|
||||
}
|
||||
|
||||
#hasAvatar() {
|
||||
return this._userAvatarUrls.length > 0;
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<uui-button
|
||||
@@ -51,7 +91,11 @@ export class UmbCurrentUserHeaderAppElement extends UmbLitElement {
|
||||
look="primary"
|
||||
label="${this.localize.term('visuallyHiddenTexts_openCloseBackofficeProfileOptions')}"
|
||||
compact>
|
||||
<uui-avatar name="${this._currentUser?.name || 'Unknown'}"></uui-avatar>
|
||||
<uui-avatar
|
||||
id="Avatar"
|
||||
.name=${this._currentUser?.name || 'Unknown'}
|
||||
img-src=${ifDefined(this.#hasAvatar() ? this._userAvatarUrls[0].url : undefined)}
|
||||
img-srcset=${ifDefined(this.#hasAvatar() ? this.#getAvatarSrcset() : undefined)}></uui-avatar>
|
||||
</uui-button>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import type { UmbCurrentUserModel } from './types.js';
|
||||
import { UmbCurrentUserRepository } from './repository/index.js';
|
||||
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
|
||||
import { UmbBaseController } from '@umbraco-cms/backoffice/class-api';
|
||||
import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { firstValueFrom } from '@umbraco-cms/backoffice/external/rxjs';
|
||||
import { UMB_AUTH_CONTEXT } from '@umbraco-cms/backoffice/auth';
|
||||
import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api';
|
||||
import { umbLocalizationRegistry } from '@umbraco-cms/backoffice/localization';
|
||||
|
||||
export class UmbCurrentUserContext extends UmbBaseController {
|
||||
export class UmbCurrentUserContext extends UmbContextBase<UmbCurrentUserContext> {
|
||||
#currentUser = new UmbObjectState<UmbCurrentUserModel | undefined>(undefined);
|
||||
readonly currentUser = this.#currentUser.asObservable();
|
||||
|
||||
@@ -18,7 +18,7 @@ export class UmbCurrentUserContext extends UmbBaseController {
|
||||
#currentUserRepository = new UmbCurrentUserRepository(this);
|
||||
|
||||
constructor(host: UmbControllerHost) {
|
||||
super(host);
|
||||
super(host, UMB_CURRENT_USER_CONTEXT);
|
||||
|
||||
this.consumeContext(UMB_AUTH_CONTEXT, (instance) => {
|
||||
this.#authContext = instance;
|
||||
@@ -29,16 +29,18 @@ export class UmbCurrentUserContext extends UmbBaseController {
|
||||
if (!currentLanguageIsoCode) return;
|
||||
umbLocalizationRegistry.loadLanguage(currentLanguageIsoCode);
|
||||
});
|
||||
|
||||
this.provideContext(UMB_CURRENT_USER_CONTEXT, this);
|
||||
}
|
||||
|
||||
async requestCurrentUser() {
|
||||
const { data } = await this.#currentUserRepository.requestCurrentUser();
|
||||
/**
|
||||
* Loads the current user
|
||||
*/
|
||||
async load() {
|
||||
const { asObservable } = await this.#currentUserRepository.requestCurrentUser();
|
||||
|
||||
if (data) {
|
||||
// TODO: observe current user
|
||||
this.#currentUser.setValue(data);
|
||||
if (asObservable) {
|
||||
this.observe(asObservable(), (currentUser) => {
|
||||
this.#currentUser?.setValue(currentUser);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +59,7 @@ export class UmbCurrentUserContext extends UmbBaseController {
|
||||
if (!this.#authContext) return;
|
||||
this.observe(this.#authContext.isAuthorized, (isAuthorized) => {
|
||||
if (isAuthorized) {
|
||||
this.requestCurrentUser();
|
||||
this.load();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ import { html, customElement } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
|
||||
@customElement('umb-user-profile-app-external-login-providers')
|
||||
export class UmbUserProfileAppExternalLoginProvidersElement extends UmbLitElement {
|
||||
@customElement('umb-external-login-providers-user-profile-app')
|
||||
export class UmbExternalLoginProvidersUserProfileAppElement extends UmbLitElement {
|
||||
render() {
|
||||
return html`
|
||||
<uui-box>
|
||||
@@ -16,10 +16,10 @@ export class UmbUserProfileAppExternalLoginProvidersElement extends UmbLitElemen
|
||||
static styles = [UmbTextStyles];
|
||||
}
|
||||
|
||||
export default UmbUserProfileAppExternalLoginProvidersElement;
|
||||
export default UmbExternalLoginProvidersUserProfileAppElement;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-user-profile-app-external-login-providers': UmbUserProfileAppExternalLoginProvidersElement;
|
||||
'umb-external-login-providers-user-profile-app': UmbExternalLoginProvidersUserProfileAppElement;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import type { ManifestUserProfileApp } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
export const userProfileApps: Array<ManifestUserProfileApp> = [
|
||||
{
|
||||
type: 'userProfileApp',
|
||||
alias: 'Umb.UserProfileApp.CurrentUser.ExternalLoginProviders',
|
||||
name: 'External Login Providers User Profile App',
|
||||
element: () => import('./external-login-providers-user-profile-app.element.js'),
|
||||
weight: 800,
|
||||
meta: {
|
||||
label: 'External Login Providers User Profile App',
|
||||
pathname: 'externalLoginProviders',
|
||||
},
|
||||
},
|
||||
];
|
||||
export const manifests = [...userProfileApps];
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { UmbCurrentUserHistoryItem, UmbCurrentUserHistoryStore } from '../current-user-history.store.js';
|
||||
import { UMB_CURRENT_USER_HISTORY_STORE_CONTEXT } from '../current-user-history.store.js';
|
||||
import type { UmbCurrentUserHistoryItem, UmbCurrentUserHistoryStore } from './current-user-history.store.js';
|
||||
import { UMB_CURRENT_USER_HISTORY_STORE_CONTEXT } from './current-user-history.store.js';
|
||||
import { css, html, nothing, customElement, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
|
||||
@customElement('umb-user-profile-app-history')
|
||||
export class UmbUserProfileAppHistoryElement extends UmbLitElement {
|
||||
@customElement('umb-current-user-history-user-profile-app')
|
||||
export class UmbCurrentUserHistoryUserProfileAppElement extends UmbLitElement {
|
||||
@state()
|
||||
private _history: Array<UmbCurrentUserHistoryItem> = [];
|
||||
|
||||
@@ -109,10 +109,10 @@ export class UmbUserProfileAppHistoryElement extends UmbLitElement {
|
||||
];
|
||||
}
|
||||
|
||||
export default UmbUserProfileAppHistoryElement;
|
||||
export default UmbCurrentUserHistoryUserProfileAppElement;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-user-dashboard-test': UmbUserProfileAppHistoryElement;
|
||||
'umb-current-user-history-user-profile-app': UmbCurrentUserHistoryUserProfileAppElement;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
export const userProfileApps: Array<ManifestTypes> = [
|
||||
{
|
||||
type: 'userProfileApp',
|
||||
alias: 'Umb.UserProfileApp.CurrentUser.History',
|
||||
name: 'Current User History User Profile App',
|
||||
element: () => import('../history/current-user-history-user-profile-app.element.js'),
|
||||
weight: 100,
|
||||
meta: {
|
||||
label: 'History',
|
||||
pathname: 'history',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'store',
|
||||
alias: 'Umb.Store.CurrentUser.History',
|
||||
name: 'Current User History Store',
|
||||
api: () => import('./current-user-history.store.js'),
|
||||
},
|
||||
];
|
||||
export const manifests = [...userProfileApps];
|
||||
@@ -1,5 +1,4 @@
|
||||
// TODO:Do not export store, but instead export future repository
|
||||
export * from './current-user-history.store.js';
|
||||
export * from './history/current-user-history.store.js';
|
||||
export * from './utils/index.js';
|
||||
export * from './current-user.context.js';
|
||||
export * from './types.js';
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import { manifests as modalManifests } from './modals/manifests.js';
|
||||
import { manifests as userProfileAppsManifests } from './user-profile-apps/manifests.js';
|
||||
import { manifests as externalLoginProviderManifests } from './external-login/manifests.js';
|
||||
import { manifests as historyManifests } from './history/manifests.js';
|
||||
import { manifests as profileManifests } from './profile/manifests.js';
|
||||
import { manifests as themeManifests } from './theme/manifests.js';
|
||||
import { manifests as repositoryManifests } from './repository/manifests.js';
|
||||
import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
export const headerApps: Array<ManifestTypes> = [
|
||||
{
|
||||
type: 'store',
|
||||
alias: 'Umb.Store.CurrentUser',
|
||||
name: 'Current User Store',
|
||||
js: () => import('./current-user-history.store.js'),
|
||||
},
|
||||
{
|
||||
type: 'globalContext',
|
||||
alias: 'Umb.GlobalContext.CurrentUser',
|
||||
@@ -19,7 +17,7 @@ export const headerApps: Array<ManifestTypes> = [
|
||||
type: 'headerApp',
|
||||
alias: 'Umb.HeaderApp.CurrentUser',
|
||||
name: 'Current User',
|
||||
js: () => import('./current-user-header-app.element.js'),
|
||||
element: () => import('./current-user-header-app.element.js'),
|
||||
weight: 0,
|
||||
meta: {
|
||||
label: 'TODO: how should we enable this to not be set.',
|
||||
@@ -29,4 +27,12 @@ export const headerApps: Array<ManifestTypes> = [
|
||||
},
|
||||
];
|
||||
|
||||
export const manifests = [...headerApps, ...modalManifests, ...userProfileAppsManifests];
|
||||
export const manifests = [
|
||||
...externalLoginProviderManifests,
|
||||
...headerApps,
|
||||
...historyManifests,
|
||||
...modalManifests,
|
||||
...profileManifests,
|
||||
...repositoryManifests,
|
||||
...themeManifests,
|
||||
];
|
||||
|
||||
@@ -4,8 +4,8 @@ import type { UmbModalManagerContext } from '@umbraco-cms/backoffice/modal';
|
||||
import { UMB_CHANGE_PASSWORD_MODAL, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
|
||||
import { UMB_CURRENT_USER_CONTEXT, type UmbCurrentUserModel } from '@umbraco-cms/backoffice/current-user';
|
||||
|
||||
@customElement('umb-user-profile-app-profile')
|
||||
export class UmbUserProfileAppProfileElement extends UmbLitElement {
|
||||
@customElement('umb-current-user-profile-user-profile-app')
|
||||
export class UmbCurrentUserProfileUserProfileAppElement extends UmbLitElement {
|
||||
@state()
|
||||
private _currentUser?: UmbCurrentUserModel;
|
||||
|
||||
@@ -70,10 +70,10 @@ export class UmbUserProfileAppProfileElement extends UmbLitElement {
|
||||
}
|
||||
}
|
||||
|
||||
export default UmbUserProfileAppProfileElement;
|
||||
export default UmbCurrentUserProfileUserProfileAppElement;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-user-profile-app-profile': UmbUserProfileAppProfileElement;
|
||||
'umb-current-user-profile-user-profile-app': UmbCurrentUserProfileUserProfileAppElement;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import type { ManifestUserProfileApp } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
export const userProfileApps: Array<ManifestUserProfileApp> = [
|
||||
{
|
||||
type: 'userProfileApp',
|
||||
alias: 'Umb.UserProfileApp.CurrentUser.Profile',
|
||||
name: 'Current User Profile User Profile App',
|
||||
element: () => import('./current-user-profile-user-profile-app.element.js'),
|
||||
weight: 900,
|
||||
meta: {
|
||||
label: 'Current User Profile User Profile App',
|
||||
pathname: 'profile',
|
||||
},
|
||||
},
|
||||
];
|
||||
export const manifests = [...userProfileApps];
|
||||
@@ -1,4 +1,5 @@
|
||||
import { UmbCurrentUserServerDataSource } from './current-user.server.data-source.js';
|
||||
import { UMB_CURRENT_USER_STORE_CONTEXT } from './current-user.store.js';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { UmbRepositoryBase } from '@umbraco-cms/backoffice/repository';
|
||||
|
||||
@@ -10,11 +11,19 @@ import { UmbRepositoryBase } from '@umbraco-cms/backoffice/repository';
|
||||
*/
|
||||
export class UmbCurrentUserRepository extends UmbRepositoryBase {
|
||||
#currentUserSource: UmbCurrentUserServerDataSource;
|
||||
#currentUserStore?: typeof UMB_CURRENT_USER_STORE_CONTEXT.TYPE;
|
||||
#init: Promise<unknown>;
|
||||
|
||||
constructor(host: UmbControllerHost) {
|
||||
super(host);
|
||||
|
||||
this.#currentUserSource = new UmbCurrentUserServerDataSource(host);
|
||||
|
||||
this.#init = Promise.all([
|
||||
this.consumeContext(UMB_CURRENT_USER_STORE_CONTEXT, (instance) => {
|
||||
this.#currentUserStore = instance;
|
||||
}).asPromise(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -23,8 +32,14 @@ export class UmbCurrentUserRepository extends UmbRepositoryBase {
|
||||
* @memberof UmbCurrentUserRepository
|
||||
*/
|
||||
async requestCurrentUser() {
|
||||
// TODO: add observable option
|
||||
return this.#currentUserSource.getCurrentUser();
|
||||
await this.#init;
|
||||
const { data, error } = await this.#currentUserSource.getCurrentUser();
|
||||
|
||||
if (data) {
|
||||
this.#currentUserStore?.set(data);
|
||||
}
|
||||
|
||||
return { data, error, asObservable: () => this.#currentUserStore!.data };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,8 +35,8 @@ export class UmbCurrentUserServerDataSource {
|
||||
userName: data.userName,
|
||||
name: data.name,
|
||||
languageIsoCode: data.languageIsoCode || 'en-us', // TODO: make global variable
|
||||
documentStartNodeIds: data.documentStartNodeIds,
|
||||
mediaStartNodeIds: data.mediaStartNodeIds,
|
||||
documentStartNodeUniques: data.documentStartNodeIds,
|
||||
mediaStartNodeUniques: data.mediaStartNodeIds,
|
||||
avatarUrls: data.avatarUrls,
|
||||
languages: data.languages,
|
||||
hasAccessToAllLanguages: data.hasAccessToAllLanguages,
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import type { UmbCurrentUserModel } from '../types.js';
|
||||
import type { UmbUserDetailModel } from '@umbraco-cms/backoffice/user';
|
||||
import { UMB_USER_DETAIL_STORE_CONTEXT } from '@umbraco-cms/backoffice/user';
|
||||
import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
|
||||
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
|
||||
import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api';
|
||||
|
||||
export class UmbCurrentUserStore extends UmbContextBase<UmbCurrentUserStore> {
|
||||
#data = new UmbObjectState<UmbCurrentUserModel | undefined>(undefined);
|
||||
readonly data = this.#data.asObservable();
|
||||
|
||||
constructor(host: UmbControllerHostElement) {
|
||||
super(host, UMB_CURRENT_USER_STORE_CONTEXT.toString());
|
||||
|
||||
this.consumeContext(UMB_USER_DETAIL_STORE_CONTEXT, (instance) => {
|
||||
this.observe(instance?.all(), (users) => this.#onUserDetailStoreUpdate(users));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current user
|
||||
* @readonly
|
||||
* @type {UmbCurrentUserModel}
|
||||
* @memberof UmbCurrentUserStore
|
||||
*/
|
||||
get() {
|
||||
return this.#data.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current user
|
||||
* @param {UmbCurrentUserModel} data
|
||||
* @memberof UmbCurrentUserStore
|
||||
*/
|
||||
set(data: UmbCurrentUserModel) {
|
||||
this.#data.setValue(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the current user
|
||||
* @param {Partial<UmbCurrentUserModel>} data
|
||||
* @memberof UmbCurrentUserStore
|
||||
*/
|
||||
update(data: Partial<UmbCurrentUserModel>) {
|
||||
this.#data.update(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the current user
|
||||
* @memberof UmbCurrentUserStore
|
||||
*/
|
||||
clear() {
|
||||
this.#data.setValue(undefined);
|
||||
}
|
||||
|
||||
#onUserDetailStoreUpdate = (users: Array<UmbUserDetailModel>) => {
|
||||
const currentUser = this.get();
|
||||
if (!currentUser) return;
|
||||
|
||||
const updatedCurrentUser = users.find((user) => user.unique === currentUser.unique);
|
||||
if (!updatedCurrentUser) return;
|
||||
|
||||
const mappedCurrentUser: Partial<UmbCurrentUserModel> = {
|
||||
email: updatedCurrentUser.email,
|
||||
userName: updatedCurrentUser.userName,
|
||||
name: updatedCurrentUser.name,
|
||||
languageIsoCode: updatedCurrentUser.languageIsoCode || '', // TODO: default value?
|
||||
documentStartNodeUniques: updatedCurrentUser.documentStartNodeUniques,
|
||||
mediaStartNodeUniques: updatedCurrentUser.mediaStartNodeUniques,
|
||||
avatarUrls: updatedCurrentUser.avatarUrls,
|
||||
};
|
||||
|
||||
this.update(mappedCurrentUser);
|
||||
};
|
||||
}
|
||||
|
||||
export const UMB_CURRENT_USER_STORE_CONTEXT = new UmbContextToken<UmbCurrentUserStore>('UmbCurrentUserStore');
|
||||
@@ -1,2 +1,3 @@
|
||||
export { UmbCurrentUserRepository } from './current-user.repository.js';
|
||||
export { UMB_CURRENT_USER_REPOSITORY_ALIAS } from './manifests.js';
|
||||
export { UmbCurrentUserStore, UMB_CURRENT_USER_STORE_CONTEXT } from './current-user.store.js';
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
import type { ManifestRepository } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { UmbCurrentUserStore } from './current-user.store.js';
|
||||
import type { ManifestRepository, ManifestStore } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
export const UMB_CURRENT_USER_REPOSITORY_ALIAS = 'Umb.Repository.CurrentUser';
|
||||
|
||||
const avatarRepository: ManifestRepository = {
|
||||
const repository: ManifestRepository = {
|
||||
type: 'repository',
|
||||
alias: UMB_CURRENT_USER_REPOSITORY_ALIAS,
|
||||
name: 'Current User Repository',
|
||||
api: () => import('./current-user.repository.js'),
|
||||
};
|
||||
|
||||
export const manifests = [avatarRepository];
|
||||
const store: ManifestStore = {
|
||||
type: 'store',
|
||||
alias: 'Umb.Store.CurrentUser',
|
||||
name: 'Current User Store',
|
||||
api: UmbCurrentUserStore,
|
||||
};
|
||||
|
||||
export const manifests = [repository, store];
|
||||
|
||||
@@ -6,8 +6,8 @@ import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import type { ManifestTheme } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
@customElement('umb-user-profile-app-themes')
|
||||
export class UmbUserProfileAppThemesElement extends UmbLitElement {
|
||||
@customElement('umb-current-user-theme-user-profile-app')
|
||||
export class UmbCurrentUserThemeUserProfileAppElement extends UmbLitElement {
|
||||
#themeContext?: UmbThemeContext;
|
||||
|
||||
@state()
|
||||
@@ -77,10 +77,10 @@ export class UmbUserProfileAppThemesElement extends UmbLitElement {
|
||||
];
|
||||
}
|
||||
|
||||
export default UmbUserProfileAppThemesElement;
|
||||
export default UmbCurrentUserThemeUserProfileAppElement;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-user-profile-app-themes': UmbUserProfileAppThemesElement;
|
||||
'umb-current-user-theme-user-profile-app': UmbCurrentUserThemeUserProfileAppElement;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import type { ManifestUserProfileApp } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
export const userProfileApps: Array<ManifestUserProfileApp> = [
|
||||
{
|
||||
type: 'userProfileApp',
|
||||
alias: 'Umb.UserProfileApp.CurrentUser.Theme',
|
||||
name: 'Current User Theme User Profile App',
|
||||
element: () => import('./current-user-theme-user-profile-app.element.js'),
|
||||
weight: 200,
|
||||
meta: {
|
||||
label: 'Current User Theme User Profile App',
|
||||
pathname: 'themes',
|
||||
},
|
||||
},
|
||||
];
|
||||
export const manifests = [...userProfileApps];
|
||||
@@ -4,8 +4,8 @@ export interface UmbCurrentUserModel {
|
||||
userName: string;
|
||||
name: string;
|
||||
languageIsoCode: string;
|
||||
documentStartNodeIds: Array<string>;
|
||||
mediaStartNodeIds: Array<string>;
|
||||
documentStartNodeUniques: Array<string>;
|
||||
mediaStartNodeUniques: Array<string>;
|
||||
avatarUrls: Array<string>;
|
||||
languages: Array<string>;
|
||||
hasAccessToAllLanguages: boolean;
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
import type { ManifestUserProfileApp } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
export const userProfileApps: Array<ManifestUserProfileApp> = [
|
||||
{
|
||||
type: 'userProfileApp',
|
||||
alias: 'Umb.UserProfileApp.profile',
|
||||
name: 'Profile User Profile App',
|
||||
js: () => import('./user-profile-app-profile.element.js'),
|
||||
weight: 900,
|
||||
meta: {
|
||||
label: 'Profile User Profile App',
|
||||
pathname: 'profile',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'userProfileApp',
|
||||
alias: 'Umb.UserProfileApp.ExternalLoginProviders',
|
||||
name: 'External Login Providers User Profile App',
|
||||
js: () => import('./user-profile-app-external-login-providers.element.js'),
|
||||
weight: 800,
|
||||
meta: {
|
||||
label: 'External Login Providers User Profile App',
|
||||
pathname: 'externalLoginProviders',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'userProfileApp',
|
||||
alias: 'Umb.UserProfileApp.Themes',
|
||||
name: 'Themes User Profile App',
|
||||
js: () => import('./user-profile-app-themes.element.js'),
|
||||
weight: 200,
|
||||
meta: {
|
||||
label: 'Themes User Profile App',
|
||||
pathname: 'themes',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'userProfileApp',
|
||||
alias: 'Umb.UserProfileApp.History',
|
||||
name: 'History User Profile App',
|
||||
js: () => import('./user-profile-app-history.element.js'),
|
||||
weight: 100,
|
||||
meta: {
|
||||
label: 'History User Profile App',
|
||||
pathname: 'history',
|
||||
},
|
||||
},
|
||||
];
|
||||
export const manifests = [...userProfileApps];
|
||||
@@ -1,36 +1,19 @@
|
||||
import type { UmbUserGroupDetailRepository } from '../../repository/index.js';
|
||||
import { html } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbEntityBulkActionBase } from '@umbraco-cms/backoffice/entity-bulk-action';
|
||||
import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { UmbContextConsumerController } from '@umbraco-cms/backoffice/context-api';
|
||||
import type { UmbModalManagerContext } from '@umbraco-cms/backoffice/modal';
|
||||
import { UMB_MODAL_MANAGER_CONTEXT, UMB_CONFIRM_MODAL } from '@umbraco-cms/backoffice/modal';
|
||||
import { umbConfirmModal } from '@umbraco-cms/backoffice/modal';
|
||||
|
||||
export class UmbDeleteUserGroupEntityBulkAction extends UmbEntityBulkActionBase<UmbUserGroupDetailRepository> {
|
||||
#modalContext?: UmbModalManagerContext;
|
||||
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, selection: Array<string>) {
|
||||
super(host, repositoryAlias, selection);
|
||||
|
||||
new UmbContextConsumerController(host, UMB_MODAL_MANAGER_CONTEXT, (instance) => {
|
||||
this.#modalContext = instance;
|
||||
});
|
||||
}
|
||||
|
||||
async execute() {
|
||||
if (!this.#modalContext || this.selection.length === 0) return;
|
||||
if (this.selection.length === 0) return;
|
||||
|
||||
const modalContext = this.#modalContext.open(UMB_CONFIRM_MODAL, {
|
||||
data: {
|
||||
color: 'danger',
|
||||
headline: `Delete user groups?`,
|
||||
content: html`Are you sure you want to delete selected user groups?`,
|
||||
confirmLabel: 'Delete',
|
||||
},
|
||||
await umbConfirmModal(this._host, {
|
||||
color: 'danger',
|
||||
headline: `Delete user groups?`,
|
||||
content: html`Are you sure you want to delete selected user groups?`,
|
||||
confirmLabel: 'Delete',
|
||||
});
|
||||
|
||||
await modalContext.onSubmit();
|
||||
|
||||
//TODO: How should we handle bulk actions? right now we send a request per item we want to change.
|
||||
//TODO: For now we have to reload the page to see the update
|
||||
for (let index = 0; index < this.selection.length; index++) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getDisplayStateFromUserStatus } from '../../../../utils.js';
|
||||
import { getDisplayStateFromUserStatus } from '../../../utils.js';
|
||||
import type { UmbUserCollectionContext } from '../../user-collection.context.js';
|
||||
import type { UmbUserDetailModel } from '../../../types.js';
|
||||
import { css, html, nothing, customElement, state, repeat, ifDefined } from '@umbraco-cms/backoffice/external/lit';
|
||||
@@ -76,6 +76,27 @@ export class UmbUserGridCollectionViewElement extends UmbLitElement {
|
||||
}
|
||||
|
||||
#renderUserCard(user: UmbUserDetailModel) {
|
||||
const avatarUrls = [
|
||||
{
|
||||
scale: '1x',
|
||||
url: user.avatarUrls?.[0],
|
||||
},
|
||||
{
|
||||
scale: '2x',
|
||||
url: user.avatarUrls?.[1],
|
||||
},
|
||||
{
|
||||
scale: '3x',
|
||||
url: user.avatarUrls?.[2],
|
||||
},
|
||||
];
|
||||
|
||||
let avatarSrcset = '';
|
||||
|
||||
avatarUrls.forEach((url) => {
|
||||
avatarSrcset += `${url.url} ${url.scale},`;
|
||||
});
|
||||
|
||||
return html`
|
||||
<uui-card-user
|
||||
.name=${user.name ?? 'Unnamed user'}
|
||||
@@ -86,6 +107,12 @@ export class UmbUserGridCollectionViewElement extends UmbLitElement {
|
||||
@selected=${() => this.#onSelect(user)}
|
||||
@deselected=${() => this.#onDeselect(user)}>
|
||||
${this.#renderUserTag(user)} ${this.#renderUserGroupNames(user)} ${this.#renderUserLoginDate(user)}
|
||||
|
||||
<uui-avatar
|
||||
slot="avatar"
|
||||
.name=${user.name || 'Unknown'}
|
||||
img-src=${ifDefined(user.avatarUrls.length > 0 ? avatarUrls[0].url : undefined)}
|
||||
img-srcset=${ifDefined(user.avatarUrls.length > 0 ? avatarSrcset : undefined)}></uui-avatar>
|
||||
</uui-card-user>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { html, LitElement, customElement, property } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { html, LitElement, customElement, property, ifDefined } from '@umbraco-cms/backoffice/external/lit';
|
||||
import type { UmbTableColumn, UmbTableItem } from '@umbraco-cms/backoffice/components';
|
||||
|
||||
@customElement('umb-user-table-name-column-layout')
|
||||
@@ -13,9 +13,34 @@ export class UmbUserTableNameColumnLayoutElement extends LitElement {
|
||||
value!: any;
|
||||
|
||||
render() {
|
||||
const avatarUrls = [
|
||||
{
|
||||
scale: '1x',
|
||||
url: this.value.avatarUrls?.[0],
|
||||
},
|
||||
{
|
||||
scale: '2x',
|
||||
url: this.value.avatarUrls?.[1],
|
||||
},
|
||||
{
|
||||
scale: '3x',
|
||||
url: this.value.avatarUrls?.[2],
|
||||
},
|
||||
];
|
||||
|
||||
let avatarSrcset = '';
|
||||
|
||||
avatarUrls.forEach((url) => {
|
||||
avatarSrcset += `${url.url} ${url.scale},`;
|
||||
});
|
||||
|
||||
return html` <div style="display: flex; align-items: center;">
|
||||
<uui-avatar name="${this.value.name}" style="margin-right: var(--uui-size-space-3);"></uui-avatar>
|
||||
<a style="font-weight: bold;" href="section/user-management/view/users/user/${this.item.id}"
|
||||
<uui-avatar
|
||||
style="margin-right: var(--uui-size-space-3);"
|
||||
.name=${this.value.name || 'Unknown'}
|
||||
img-src=${ifDefined(this.value.avatarUrls.length > 0 ? avatarUrls[0].url : undefined)}
|
||||
img-srcset=${ifDefined(this.value.avatarUrls.length > 0 ? avatarSrcset : undefined)}></uui-avatar>
|
||||
<a style="font-weight: bold;" href="section/user-management/view/users/user/${this.value.unique}"
|
||||
>${this.value.name}</a
|
||||
>
|
||||
</div>`;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user