Merge branch 'main' into feature/dynamic-root
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import type { UmbAppErrorElement } from './app-error.element.js';
|
||||
import { UmbAppContext } from './app.context.js';
|
||||
import { UmbServerConnection } from './server-connection.js';
|
||||
import type { UMB_AUTH_CONTEXT} from '@umbraco-cms/backoffice/auth';
|
||||
import { UmbAuthContext } from '@umbraco-cms/backoffice/auth';
|
||||
import type { UMB_AUTH_CONTEXT } from '@umbraco-cms/backoffice/auth';
|
||||
import { UMB_STORAGE_REDIRECT_URL, UmbAuthContext } from '@umbraco-cms/backoffice/auth';
|
||||
import { css, html, customElement, property } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UUIIconRegistryEssential } from '@umbraco-cms/backoffice/external/uui';
|
||||
import { UmbIconRegistry } from '@umbraco-cms/backoffice/icon';
|
||||
@@ -29,13 +29,12 @@ export class UmbAppElement extends UmbLitElement {
|
||||
* @attr
|
||||
*/
|
||||
@property({ type: String })
|
||||
// TODO: get from server config
|
||||
// TODO: get from base element or maybe move to UmbAuthContext.#getRedirectUrl since it is only used there
|
||||
backofficePath = '/umbraco';
|
||||
|
||||
/**
|
||||
* Bypass authentication.
|
||||
*/
|
||||
// TODO: this might not be the right solution
|
||||
@property({ type: Boolean })
|
||||
bypassAuth = false;
|
||||
|
||||
@@ -140,6 +139,15 @@ export class UmbAppElement extends UmbLitElement {
|
||||
}
|
||||
|
||||
#redirect() {
|
||||
// If there is a ?code parameter in the url, then we are in the middle of the oauth flow
|
||||
// and we need to complete the login (the authorization notifier will redirect after this is done
|
||||
// essentially hitting this method again)
|
||||
const queryParams = new URLSearchParams(window.location.search);
|
||||
if (queryParams.has('code')) {
|
||||
this.#authContext?.completeAuthorizationRequest();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (this.#serverConnection?.getStatus()) {
|
||||
case RuntimeLevelModel.INSTALL:
|
||||
history.replaceState(null, '', 'install');
|
||||
@@ -156,17 +164,15 @@ export class UmbAppElement extends UmbLitElement {
|
||||
case RuntimeLevelModel.RUN: {
|
||||
const pathname = pathWithoutBasePath({ start: true, end: false });
|
||||
|
||||
// If we are on the installer or upgrade page, redirect to the root
|
||||
// but if not, keep the current path but replace state anyway to initialize the router
|
||||
let currentRoute = location.href;
|
||||
const savedRoute = sessionStorage.getItem('umb:auth:redirect');
|
||||
if (savedRoute) {
|
||||
sessionStorage.removeItem('umb:auth:redirect');
|
||||
currentRoute = savedRoute;
|
||||
// If we are on installer or upgrade page, redirect to the root since we are in the RUN state
|
||||
if (pathname === '/install' || pathname === '/upgrade') {
|
||||
history.replaceState(null, '', '/');
|
||||
break;
|
||||
}
|
||||
const finalPath = pathname === '/install' || pathname === '/upgrade' ? '/' : currentRoute;
|
||||
|
||||
history.replaceState(null, '', finalPath);
|
||||
// Keep the current path but replace state anyway to initialize the router
|
||||
// because the router will not initialize a wildcard route by itself
|
||||
history.replaceState(null, '', location.href);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -187,11 +193,10 @@ export class UmbAppElement extends UmbLitElement {
|
||||
}
|
||||
|
||||
// Save location.href so we can redirect to it after login
|
||||
window.sessionStorage.setItem('umb:auth:redirect', location.href);
|
||||
window.sessionStorage.setItem(UMB_STORAGE_REDIRECT_URL, location.href);
|
||||
|
||||
// Make a request to the auth server to start the auth flow
|
||||
// TODO: find better name for this method
|
||||
this.#authContext.login();
|
||||
this.#authContext.makeAuthorizationRequest();
|
||||
|
||||
// Return false to prevent the route from being rendered
|
||||
return false;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { UmbLocalizeController } from '@umbraco-cms/backoffice/localization-api';
|
||||
import { UmbLocalizationController } from '@umbraco-cms/backoffice/localization-api';
|
||||
import type { Observable } from '@umbraco-cms/backoffice/external/rxjs';
|
||||
import type { HTMLElementConstructor } from '@umbraco-cms/backoffice/extension-api';
|
||||
import { UmbControllerHostElementMixin, type UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
|
||||
@@ -34,14 +34,14 @@ export declare class UmbElement extends UmbControllerHostElement {
|
||||
): UmbContextConsumerController<BaseType, ResultType>;
|
||||
/**
|
||||
* Use the UmbLocalizeController to localize your element.
|
||||
* @see UmbLocalizeController
|
||||
* @see UmbLocalizationController
|
||||
*/
|
||||
localize: UmbLocalizeController;
|
||||
localize: UmbLocalizationController;
|
||||
}
|
||||
|
||||
export const UmbElementMixin = <T extends HTMLElementConstructor>(superClass: T) => {
|
||||
class UmbElementMixinClass extends UmbControllerHostElementMixin(superClass) implements UmbElement {
|
||||
localize: UmbLocalizeController = new UmbLocalizeController(this);
|
||||
localize: UmbLocalizationController = new UmbLocalizationController(this);
|
||||
|
||||
/**
|
||||
* @description Observe a RxJS source of choice.
|
||||
|
||||
@@ -73,7 +73,7 @@ export abstract class UmbBaseExtensionInitializer<
|
||||
}
|
||||
protected _init() {
|
||||
this.#manifestObserver = this.observe(
|
||||
this.#extensionRegistry.getByAlias<ManifestType>(this.#alias),
|
||||
this.#extensionRegistry.byAlias<ManifestType>(this.#alias),
|
||||
async (extensionManifest) => {
|
||||
this.#clearPermittedState();
|
||||
this.#manifest = extensionManifest;
|
||||
@@ -143,7 +143,7 @@ export abstract class UmbBaseExtensionInitializer<
|
||||
if (conditionConfigs.length > 0) {
|
||||
// Observes the conditions and initialize as they come in.
|
||||
this.observe(
|
||||
this.#extensionRegistry.getByTypeAndAliases('condition', conditionAliases),
|
||||
this.#extensionRegistry.byTypeAndAliases('condition', conditionAliases),
|
||||
this.#gotConditions,
|
||||
'_observeConditions',
|
||||
);
|
||||
|
||||
@@ -56,8 +56,8 @@ export abstract class UmbBaseExtensionsInitializer<
|
||||
}
|
||||
protected _init() {
|
||||
let source = Array.isArray(this.#type)
|
||||
? this.#extensionRegistry.extensionsOfTypes<ManifestType>(this.#type as string[])
|
||||
: this.#extensionRegistry.extensionsOfType<ManifestTypeName, ManifestType>(this.#type as ManifestTypeName);
|
||||
? this.#extensionRegistry.byTypes<ManifestType>(this.#type as string[])
|
||||
: this.#extensionRegistry.byType<ManifestTypeName, ManifestType>(this.#type as ManifestTypeName);
|
||||
if (this.#filter) {
|
||||
source = source.pipe(map((extensions: Array<ManifestType>) => extensions.filter(this.#filter!)));
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ export class UmbBundleExtensionInitializer extends UmbBaseController {
|
||||
constructor(host: UmbControllerHostElement, extensionRegistry: UmbExtensionRegistry<ManifestBundle>) {
|
||||
super(host);
|
||||
this.#extensionRegistry = extensionRegistry;
|
||||
this.observe(extensionRegistry.extensionsOfType('bundle'), (bundles) => {
|
||||
this.observe(extensionRegistry.byType('bundle'), (bundles) => {
|
||||
// Unregister removed bundles:
|
||||
this.#bundleMap.forEach((existingBundle) => {
|
||||
if (!bundles.find((b) => b.alias === existingBundle.alias)) {
|
||||
|
||||
@@ -13,7 +13,7 @@ export class UmbEntryPointExtensionInitializer extends UmbBaseController {
|
||||
super(host);
|
||||
this.#host = host;
|
||||
this.#extensionRegistry = extensionRegistry;
|
||||
this.observe(extensionRegistry.extensionsOfType('entryPoint'), (entryPoints) => {
|
||||
this.observe(extensionRegistry.byType('entryPoint'), (entryPoints) => {
|
||||
entryPoints.forEach((entryPoint) => {
|
||||
if (this.#entryPointMap.has(entryPoint.alias)) return;
|
||||
this.#entryPointMap.set(entryPoint.alias, entryPoint);
|
||||
|
||||
@@ -47,6 +47,7 @@ describe('UmbExtensionRegistry', () => {
|
||||
type: 'workspace',
|
||||
name: 'test-editor-1',
|
||||
alias: 'Umb.Test.Editor.1',
|
||||
weight: 2,
|
||||
meta: {
|
||||
entityType: 'testEntity',
|
||||
},
|
||||
@@ -66,10 +67,20 @@ describe('UmbExtensionRegistry', () => {
|
||||
expect(extensionRegistry.isRegistered('Umb.Test.Section.1')).to.be.true;
|
||||
});
|
||||
|
||||
it('should get several extensions by type', (done) => {
|
||||
extensionRegistry
|
||||
.byType('section')
|
||||
.subscribe((extensions) => {
|
||||
expect(extensions.length).to.eq(3);
|
||||
done();
|
||||
})
|
||||
.unsubscribe();
|
||||
});
|
||||
|
||||
it('should get an extension by alias', (done) => {
|
||||
const alias = 'Umb.Test.Section.1';
|
||||
extensionRegistry
|
||||
.getByTypeAndAlias('section', alias)
|
||||
.byAlias(alias)
|
||||
.subscribe((extension) => {
|
||||
expect(extension?.alias).to.eq(alias);
|
||||
done();
|
||||
@@ -77,10 +88,32 @@ describe('UmbExtensionRegistry', () => {
|
||||
.unsubscribe();
|
||||
});
|
||||
|
||||
it('should get an extension by type and alias', (done) => {
|
||||
const alias = 'Umb.Test.Section.1';
|
||||
extensionRegistry
|
||||
.byTypeAndAlias('section', alias)
|
||||
.subscribe((extension) => {
|
||||
expect(extension?.alias).to.eq(alias);
|
||||
done();
|
||||
})
|
||||
.unsubscribe();
|
||||
});
|
||||
|
||||
it('should get an extension by type and filter', (done) => {
|
||||
extensionRegistry
|
||||
.byTypeAndFilter('section', (ext) => ext.weight === 25)
|
||||
.subscribe((extensions) => {
|
||||
expect(extensions.length).to.eq(1);
|
||||
expect(extensions[0].alias).to.eq('Umb.Test.Section.3');
|
||||
done();
|
||||
})
|
||||
.unsubscribe();
|
||||
});
|
||||
|
||||
it('should get an extension by aliases', (done) => {
|
||||
const aliases = ['Umb.Test.Section.1', 'Umb.Test.Section.2'];
|
||||
extensionRegistry
|
||||
.getByTypeAndAliases('section', aliases)
|
||||
.byTypeAndAliases('section', aliases)
|
||||
.subscribe((extensions) => {
|
||||
expect(extensions[0]?.alias).to.eq(aliases[1]);
|
||||
expect(extensions[1]?.alias).to.eq(aliases[0]);
|
||||
@@ -89,12 +122,12 @@ describe('UmbExtensionRegistry', () => {
|
||||
.unsubscribe();
|
||||
});
|
||||
|
||||
describe('getByType', () => {
|
||||
describe('byType', () => {
|
||||
const type = 'section';
|
||||
|
||||
it('should get all extensions by type', (done) => {
|
||||
extensionRegistry
|
||||
.extensionsOfType(type)
|
||||
.byType(type)
|
||||
.subscribe((extensions) => {
|
||||
expect(extensions).to.have.lengthOf(3);
|
||||
expect(extensions?.[0]?.type).to.eq(type);
|
||||
@@ -106,7 +139,7 @@ describe('UmbExtensionRegistry', () => {
|
||||
|
||||
it('should return extensions ordered by weight', (done) => {
|
||||
extensionRegistry
|
||||
.extensionsOfType(type)
|
||||
.byType(type)
|
||||
.subscribe((extensions) => {
|
||||
expect(extensions?.[0]?.weight).to.eq(200);
|
||||
expect(extensions?.[1]?.weight).to.eq(25);
|
||||
@@ -121,7 +154,7 @@ describe('UmbExtensionRegistry', () => {
|
||||
let lastAmount = 0;
|
||||
|
||||
extensionRegistry
|
||||
.extensionsOfType('section')
|
||||
.byType('section')
|
||||
.subscribe((extensions) => {
|
||||
amountOfTimesTriggered++;
|
||||
const newAmount = extensions?.length ?? 0;
|
||||
@@ -169,6 +202,37 @@ describe('UmbExtensionRegistry', () => {
|
||||
.unsubscribe();
|
||||
});
|
||||
});
|
||||
|
||||
describe('byTypes', () => {
|
||||
const types = ['section', 'workspace'];
|
||||
|
||||
it('should get all extensions of the given types', (done) => {
|
||||
extensionRegistry
|
||||
.byTypes(types)
|
||||
.subscribe((extensions) => {
|
||||
expect(extensions).to.have.lengthOf(4);
|
||||
expect(extensions?.[0]?.type).to.eq('section');
|
||||
expect(extensions?.[1]?.type).to.eq('section');
|
||||
expect(extensions?.[2]?.type).to.eq('workspace');
|
||||
expect(extensions?.[3]?.type).to.eq('section');
|
||||
done();
|
||||
})
|
||||
.unsubscribe();
|
||||
});
|
||||
|
||||
it('should return extensions ordered by weight', (done) => {
|
||||
extensionRegistry
|
||||
.byTypes(types)
|
||||
.subscribe((extensions) => {
|
||||
expect(extensions?.[0]?.weight).to.eq(200);
|
||||
expect(extensions?.[1]?.weight).to.eq(25);
|
||||
expect(extensions?.[2]?.weight).to.eq(2);
|
||||
expect(extensions?.[3]?.weight).to.eq(1);
|
||||
done();
|
||||
})
|
||||
.unsubscribe();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('UmbExtensionRegistry with kinds', () => {
|
||||
@@ -244,7 +308,7 @@ describe('UmbExtensionRegistry with kinds', () => {
|
||||
|
||||
it('should merge with kinds', (done) => {
|
||||
extensionRegistry
|
||||
.extensionsOfType('section')
|
||||
.byType('section')
|
||||
.subscribe((extensions) => {
|
||||
expect(extensions).to.have.lengthOf(3);
|
||||
expect(extensions?.[0]?.elementName).to.not.eq('my-kind-element');
|
||||
@@ -265,7 +329,7 @@ describe('UmbExtensionRegistry with kinds', () => {
|
||||
extensionRegistry.unregister('Umb.Test.Kind');
|
||||
|
||||
extensionRegistry
|
||||
.extensionsOfType('section')
|
||||
.byType('section')
|
||||
.subscribe((extensions) => {
|
||||
amountOfTimesTriggered++;
|
||||
expect(extensions).to.have.lengthOf(3);
|
||||
@@ -287,7 +351,7 @@ describe('UmbExtensionRegistry with kinds', () => {
|
||||
let amountOfTimesTriggered = -1;
|
||||
|
||||
extensionRegistry
|
||||
.extensionsOfType('section')
|
||||
.byType('section')
|
||||
.subscribe((extensions) => {
|
||||
amountOfTimesTriggered++;
|
||||
expect(extensions).to.have.lengthOf(3);
|
||||
|
||||
@@ -1,15 +1,8 @@
|
||||
import type { ManifestBase, ManifestKind } from '../types/index.js';
|
||||
import type { ManifestTypeMap, SpecificManifestTypeOrManifestBase } from '../types/map.types.js';
|
||||
import { UmbBasicState } from '@umbraco-cms/backoffice/observable-api';
|
||||
import type {
|
||||
Observable} from '@umbraco-cms/backoffice/external/rxjs';
|
||||
import {
|
||||
map,
|
||||
distinctUntilChanged,
|
||||
combineLatest,
|
||||
of,
|
||||
switchMap,
|
||||
} from '@umbraco-cms/backoffice/external/rxjs';
|
||||
import type { Observable } from '@umbraco-cms/backoffice/external/rxjs';
|
||||
import { map, distinctUntilChanged, combineLatest, of, switchMap } from '@umbraco-cms/backoffice/external/rxjs';
|
||||
|
||||
function extensionArrayMemoization<T extends Pick<ManifestBase, 'alias'>>(
|
||||
previousValue: Array<T>,
|
||||
@@ -113,7 +106,7 @@ export class UmbExtensionRegistry<
|
||||
}
|
||||
|
||||
register(manifest: ManifestTypes | ManifestKind<ManifestTypes>): void {
|
||||
const isValid = this.checkExtension(manifest);
|
||||
const isValid = this.#checkExtension(manifest);
|
||||
if (!isValid) {
|
||||
return;
|
||||
}
|
||||
@@ -150,14 +143,7 @@ export class UmbExtensionRegistry<
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
getByAlias(alias: string) {
|
||||
// TODO: make pipes prettier/simpler/reuseable
|
||||
return this.extensions.pipe(map((extensions) => extensions.find((extension) => extension.alias === alias) || null));
|
||||
}
|
||||
*/
|
||||
|
||||
private checkExtension(manifest: ManifestTypes | ManifestKind<ManifestTypes>): boolean {
|
||||
#checkExtension(manifest: ManifestTypes | ManifestKind<ManifestTypes>): boolean {
|
||||
if (!manifest.type) {
|
||||
console.error(`Extension is missing type`, manifest);
|
||||
return false;
|
||||
@@ -184,19 +170,23 @@ export class UmbExtensionRegistry<
|
||||
return true;
|
||||
}
|
||||
|
||||
private _kindsOfType<Key extends keyof ManifestTypeMap<ManifestTypes> | string>(type: Key) {
|
||||
#kindsOfType<Key extends keyof ManifestTypeMap<ManifestTypes> | string>(type: Key) {
|
||||
return this.kinds.pipe(
|
||||
map((kinds) => kinds.filter((kind) => kind.matchType === type)),
|
||||
distinctUntilChanged(extensionArrayMemoization),
|
||||
);
|
||||
}
|
||||
private _extensionsOfType<Key extends keyof ManifestTypeMap<ManifestTypes> | string>(type: Key) {
|
||||
|
||||
#extensionsOfType<
|
||||
Key extends keyof ManifestTypeMap<ManifestTypes> | string,
|
||||
T extends ManifestBase = SpecificManifestTypeOrManifestBase<ManifestTypes, Key>,
|
||||
>(type: Key) {
|
||||
return this.extensions.pipe(
|
||||
map((exts) => exts.filter((ext) => ext.type === type)),
|
||||
map((exts) => exts.filter((ext) => ext.type === type) as unknown as T[]),
|
||||
distinctUntilChanged(extensionArrayMemoization),
|
||||
);
|
||||
}
|
||||
private _kindsOfTypes(types: string[]) {
|
||||
#kindsOfTypes(types: string[]) {
|
||||
return this.kinds.pipe(
|
||||
map((kinds) => kinds.filter((kind) => types.indexOf(kind.matchType) !== -1)),
|
||||
distinctUntilChanged(extensionArrayMemoization),
|
||||
@@ -204,39 +194,67 @@ export class UmbExtensionRegistry<
|
||||
}
|
||||
|
||||
// TODO: can we get rid of as unknown here
|
||||
private _extensionsOfTypes<ExtensionType extends ManifestBase = ManifestBase>(
|
||||
#extensionsOfTypes<ExtensionType extends ManifestBase = ManifestBase>(
|
||||
types: Array<ExtensionType['type']>,
|
||||
): Observable<Array<ExtensionType>> {
|
||||
return this.extensions.pipe(
|
||||
map((exts) => exts.filter((ext) => types.indexOf(ext.type) !== -1)),
|
||||
map((exts) => exts.filter((ext) => types.indexOf(ext.type) !== -1) as unknown as Array<ExtensionType>),
|
||||
distinctUntilChanged(extensionArrayMemoization),
|
||||
) as unknown as Observable<Array<ExtensionType>>;
|
||||
);
|
||||
}
|
||||
|
||||
getByAlias<T extends ManifestBase = ManifestBase>(alias: string) {
|
||||
#mergeExtensionWithKinds<ExtensionType extends ManifestBase, KindType extends ManifestKind<ManifestTypes>>([
|
||||
ext,
|
||||
kinds,
|
||||
]: [ExtensionType | undefined, Array<KindType>]): ExtensionType | undefined {
|
||||
// Specific Extension Meta merge (does not merge conditions)
|
||||
if (ext) {
|
||||
// Since we don't have the type up front in this request, we will just get all kinds here and find the matching one:
|
||||
const baseManifest = kinds.find((kind) => kind.matchKind === ext.kind)?.manifest;
|
||||
// TODO: This check can go away when making a find kind based on type and kind.
|
||||
if (baseManifest) {
|
||||
const merged = { __isMatchedWithKind: true, ...baseManifest, ...ext };
|
||||
if ((baseManifest as any).meta) {
|
||||
(merged as any).meta = { ...(baseManifest as any).meta, ...(ext as any).meta };
|
||||
}
|
||||
return merged as ExtensionType;
|
||||
}
|
||||
}
|
||||
return ext;
|
||||
}
|
||||
|
||||
#mergeExtensionsWithKinds<ExtensionType extends ManifestBase, KindType extends ManifestKind<ManifestTypes>>([
|
||||
exts,
|
||||
kinds,
|
||||
]: [Array<ExtensionType>, Array<KindType>]): ExtensionType[] {
|
||||
return exts
|
||||
.map((ext) => {
|
||||
// Specific Extension Meta merge (does not merge conditions)
|
||||
const baseManifest = kinds.find((kind) => kind.matchKind === ext.kind)?.manifest;
|
||||
if (baseManifest) {
|
||||
const merged = { __isMatchedWithKind: true, ...baseManifest, ...ext } as any;
|
||||
if ((baseManifest as any).meta) {
|
||||
merged.meta = { ...(baseManifest as any).meta, ...(ext as any).meta };
|
||||
}
|
||||
return merged;
|
||||
}
|
||||
return ext;
|
||||
})
|
||||
.sort(sortExtensions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an observable that provides extensions matching the given alias.
|
||||
* @param alias {string} - The alias of the extensions to get.
|
||||
* @returns {Observable<T | undefined>} - An observable of the extension that matches the alias.
|
||||
*/
|
||||
byAlias<T extends ManifestBase = ManifestBase>(alias: string) {
|
||||
return this.extensions.pipe(
|
||||
map((exts) => exts.find((ext) => ext.alias === alias)),
|
||||
distinctUntilChanged(extensionSingleMemoization),
|
||||
switchMap((ext) => {
|
||||
if (ext?.kind) {
|
||||
return this._kindsOfType(ext.type).pipe(
|
||||
map((kinds) => {
|
||||
// Specific Extension Meta merge (does not merge conditions)
|
||||
if (ext) {
|
||||
// Since we dont have the type up front in this request, we will just get all kinds here and find the matching one:
|
||||
const baseManifest = kinds.find((kind) => kind.matchKind === ext.kind)?.manifest;
|
||||
// TODO: This check can go away when making a find kind based on type and kind.
|
||||
if (baseManifest) {
|
||||
const merged = { __isMatchedWithKind: true, ...baseManifest, ...ext } as any;
|
||||
if ((baseManifest as any).meta) {
|
||||
merged.meta = { ...(baseManifest as any).meta, ...(ext as any).meta };
|
||||
}
|
||||
return merged;
|
||||
}
|
||||
}
|
||||
return ext;
|
||||
}),
|
||||
);
|
||||
return this.#kindsOfType(ext.type).pipe(map((kinds) => this.#mergeExtensionWithKinds([ext, kinds])));
|
||||
}
|
||||
return of(ext);
|
||||
}),
|
||||
@@ -244,8 +262,18 @@ export class UmbExtensionRegistry<
|
||||
distinctUntilChanged(extensionAndKindMatchSingleMemoization),
|
||||
) as Observable<T | undefined>;
|
||||
}
|
||||
/**
|
||||
* @deprecated Use `byAlias` instead.
|
||||
*/
|
||||
getByAlias = this.byAlias.bind(this);
|
||||
|
||||
getByTypeAndAlias<
|
||||
/**
|
||||
* Get an observable that provides extensions matching the given type and alias.
|
||||
* @param type {string} - The type of the extensions to get.
|
||||
* @param alias {string} - The alias of the extensions to get.
|
||||
* @returns {Observable<T | undefined>} - An observable of the extensions that matches the type and alias.
|
||||
*/
|
||||
byTypeAndAlias<
|
||||
Key extends keyof ManifestTypeMap<ManifestTypes> | string,
|
||||
T extends ManifestBase = SpecificManifestTypeOrManifestBase<ManifestTypes, Key>,
|
||||
>(type: Key, alias: string) {
|
||||
@@ -254,106 +282,94 @@ export class UmbExtensionRegistry<
|
||||
map((exts) => exts.find((ext) => ext.type === type && ext.alias === alias)),
|
||||
distinctUntilChanged(extensionSingleMemoization),
|
||||
),
|
||||
this._kindsOfType(type),
|
||||
this.#kindsOfType(type),
|
||||
]).pipe(
|
||||
map(([ext, kinds]) => {
|
||||
// TODO: share one merge function between the different methods of this class:
|
||||
// Specific Extension Meta merge (does not merge conditions)
|
||||
if (ext) {
|
||||
const baseManifest = kinds.find((kind) => kind.matchKind === ext.kind)?.manifest;
|
||||
if (baseManifest) {
|
||||
const merged = { __isMatchedWithKind: true, ...baseManifest, ...ext } as any;
|
||||
if ((baseManifest as any).meta) {
|
||||
merged.meta = { ...(baseManifest as any).meta, ...(ext as any).meta };
|
||||
}
|
||||
return merged;
|
||||
}
|
||||
}
|
||||
return ext;
|
||||
}),
|
||||
map(this.#mergeExtensionWithKinds),
|
||||
distinctUntilChanged(extensionAndKindMatchSingleMemoization),
|
||||
) as Observable<T | undefined>;
|
||||
}
|
||||
/**
|
||||
* @deprecated Use `byTypeAndAlias` instead.
|
||||
*/
|
||||
getByTypeAndAlias = this.byTypeAndAlias.bind(this);
|
||||
|
||||
getByTypeAndAliases<
|
||||
byTypeAndAliases<
|
||||
Key extends keyof ManifestTypeMap<ManifestTypes> | string,
|
||||
T extends ManifestBase = SpecificManifestTypeOrManifestBase<ManifestTypes, Key>,
|
||||
>(type: Key, aliases: Array<string>) {
|
||||
return combineLatest([
|
||||
this.extensions.pipe(
|
||||
map((exts) => exts.filter((ext) => ext.type === type && aliases.indexOf(ext.alias) !== -1)),
|
||||
map((exts) => exts.filter((ext) => ext.type === type && aliases.indexOf(ext.alias) !== -1) as unknown as T[]),
|
||||
distinctUntilChanged(extensionArrayMemoization),
|
||||
),
|
||||
this._kindsOfType(type),
|
||||
this.#kindsOfType(type),
|
||||
]).pipe(
|
||||
map(([exts, kinds]) =>
|
||||
exts
|
||||
.map((ext) => {
|
||||
// Specific Extension Meta merge (does not merge conditions)
|
||||
const baseManifest = kinds.find((kind) => kind.matchKind === ext.kind)?.manifest;
|
||||
if (baseManifest) {
|
||||
const merged = { __isMatchedWithKind: true, ...baseManifest, ...ext } as any;
|
||||
if ((baseManifest as any).meta) {
|
||||
merged.meta = { ...(baseManifest as any).meta, ...(ext as any).meta };
|
||||
}
|
||||
return merged;
|
||||
}
|
||||
return ext;
|
||||
})
|
||||
.sort(sortExtensions),
|
||||
map(this.#mergeExtensionsWithKinds),
|
||||
distinctUntilChanged(extensionAndKindMatchArrayMemoization),
|
||||
) as Observable<Array<T>>;
|
||||
}
|
||||
/**
|
||||
* @deprecated Use `byTypeAndAliases` instead.
|
||||
*/
|
||||
getByTypeAndAliases = this.byTypeAndAliases.bind(this);
|
||||
|
||||
/**
|
||||
* Get an observable of extensions by type and a given filter method.
|
||||
* This will return the all extensions that matches the type and which filter method returns true.
|
||||
* The filter method will be called for each extension manifest of the given type, and the first argument to it is the extension manifest.
|
||||
* @param type {string} - The type of the extension to get
|
||||
* @param filter {(ext: T): void} - The filter method to use to filter the extensions
|
||||
* @returns {Observable<Array<T>>} - An observable of the extensions that matches the type and filter method
|
||||
*/
|
||||
byTypeAndFilter<
|
||||
Key extends keyof ManifestTypeMap<ManifestTypes> | string,
|
||||
T extends ManifestBase = SpecificManifestTypeOrManifestBase<ManifestTypes, Key>,
|
||||
>(type: Key, filter: (ext: T) => boolean) {
|
||||
return combineLatest([
|
||||
this.extensions.pipe(
|
||||
map((exts) => exts.filter((ext) => ext.type === type && filter(ext as unknown as T)) as unknown as T[]),
|
||||
distinctUntilChanged(extensionArrayMemoization),
|
||||
),
|
||||
this.#kindsOfType(type),
|
||||
]).pipe(
|
||||
map(this.#mergeExtensionsWithKinds),
|
||||
distinctUntilChanged(extensionAndKindMatchArrayMemoization),
|
||||
) as Observable<Array<T>>;
|
||||
}
|
||||
|
||||
extensionsOfType<
|
||||
/**
|
||||
* Get an observable that provides extensions matching the given type.
|
||||
* @param type {string} - The type of the extensions to get.
|
||||
* @returns {Observable<T | undefined>} - An observable of the extensions that matches the type.
|
||||
*/
|
||||
byType<
|
||||
Key extends keyof ManifestTypeMap<ManifestTypes> | string,
|
||||
T extends ManifestBase = SpecificManifestTypeOrManifestBase<ManifestTypes, Key>,
|
||||
>(type: Key) {
|
||||
return combineLatest([this._extensionsOfType(type), this._kindsOfType(type)]).pipe(
|
||||
map(([exts, kinds]) =>
|
||||
exts
|
||||
.map((ext) => {
|
||||
// Specific Extension Meta merge (does not merge conditions)
|
||||
const baseManifest = kinds.find((kind) => kind.matchKind === ext.kind)?.manifest;
|
||||
if (baseManifest) {
|
||||
const merged = { __isMatchedWithKind: true, ...baseManifest, ...ext } as any;
|
||||
if ((baseManifest as any).meta) {
|
||||
merged.meta = { ...(baseManifest as any).meta, ...(ext as any).meta };
|
||||
}
|
||||
return merged;
|
||||
}
|
||||
return ext;
|
||||
})
|
||||
.sort(sortExtensions),
|
||||
),
|
||||
return combineLatest([this.#extensionsOfType(type), this.#kindsOfType(type)]).pipe(
|
||||
map(this.#mergeExtensionsWithKinds),
|
||||
distinctUntilChanged(extensionAndKindMatchArrayMemoization),
|
||||
) as Observable<Array<T>>;
|
||||
}
|
||||
/**
|
||||
* @deprecated Use `byType` instead.
|
||||
*/
|
||||
extensionsOfType = this.byType.bind(this);
|
||||
|
||||
extensionsOfTypes<ExtensionTypes extends ManifestBase = ManifestBase>(
|
||||
types: string[],
|
||||
): Observable<Array<ExtensionTypes>> {
|
||||
return combineLatest([this._extensionsOfTypes(types), this._kindsOfTypes(types)]).pipe(
|
||||
map(([exts, kinds]) =>
|
||||
exts
|
||||
.map((ext) => {
|
||||
// Specific Extension Meta merge (does not merge conditions)
|
||||
if (ext) {
|
||||
const baseManifest = kinds.find((kind) => kind.matchKind === ext.kind)?.manifest;
|
||||
if (baseManifest) {
|
||||
const merged = { __isMatchedWithKind: true, ...baseManifest, ...ext } as any;
|
||||
if ((baseManifest as any).meta) {
|
||||
merged.meta = { ...(baseManifest as any).meta, ...(ext as any).meta };
|
||||
}
|
||||
return merged;
|
||||
}
|
||||
}
|
||||
return ext;
|
||||
})
|
||||
.sort(sortExtensions),
|
||||
),
|
||||
/**
|
||||
* Get an observable that provides extensions matching given types.
|
||||
* @param type {Array<string>} - The types of the extensions to get.
|
||||
* @returns {Observable<T | undefined>} - An observable of the extensions that matches the types.
|
||||
*/
|
||||
byTypes<ExtensionTypes extends ManifestBase = ManifestBase>(types: string[]): Observable<Array<ExtensionTypes>> {
|
||||
return combineLatest([this.#extensionsOfTypes<ExtensionTypes>(types), this.#kindsOfTypes(types)]).pipe(
|
||||
map(this.#mergeExtensionsWithKinds),
|
||||
distinctUntilChanged(extensionAndKindMatchArrayMemoization),
|
||||
) as Observable<Array<ExtensionTypes>>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use `byTypes` instead.
|
||||
*/
|
||||
extensionsOfTypes = this.byTypes.bind(this);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export * from './localize.controller.js';
|
||||
export * from './localization.controller.js';
|
||||
export * from './types/localization.js';
|
||||
export * from './manager.js';
|
||||
export * from './localization.manager.js';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { aTimeout, elementUpdated, expect, fixture, html } from '@open-wc/testing';
|
||||
import type { DefaultLocalizationSet, LocalizationSet} from './manager.js';
|
||||
import { registerLocalization, localizations } from './manager.js';
|
||||
import { UmbLocalizeController } from './localize.controller.js';
|
||||
import type { UmbLocalizationSet, UmbLocalizationSetBase } from './localization.manager.js';
|
||||
import { umbLocalizationManager } from './localization.manager.js';
|
||||
import { UmbLocalizationController } from './localization.controller.js';
|
||||
import { LitElement, customElement, property } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
@@ -11,7 +11,21 @@ class UmbLocalizeControllerHostElement extends UmbElementMixin(LitElement) {
|
||||
@property() lang = 'en-us';
|
||||
}
|
||||
|
||||
interface TestLocalization extends LocalizationSet {
|
||||
@customElement('umb-localization-render-count')
|
||||
class UmbLocalizationRenderCountElement extends UmbElementMixin(LitElement) {
|
||||
amountOfUpdates = 0;
|
||||
|
||||
requestUpdate() {
|
||||
super.requestUpdate();
|
||||
this.amountOfUpdates++;
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`${this.localize.term('logout')}`;
|
||||
}
|
||||
}
|
||||
|
||||
interface TestLocalization extends UmbLocalizationSetBase {
|
||||
close: string;
|
||||
logout: string;
|
||||
withInlineToken: any;
|
||||
@@ -36,20 +50,26 @@ const english: TestLocalization = {
|
||||
},
|
||||
};
|
||||
|
||||
const englishOverride: DefaultLocalizationSet = {
|
||||
const englishOverride: UmbLocalizationSet = {
|
||||
$code: 'en-us',
|
||||
$dir: 'ltr',
|
||||
close: 'Close 2',
|
||||
};
|
||||
|
||||
const danish: DefaultLocalizationSet = {
|
||||
const englishOverrideLogout: UmbLocalizationSet = {
|
||||
$code: 'en-us',
|
||||
$dir: 'ltr',
|
||||
logout: 'Log out 2',
|
||||
};
|
||||
|
||||
const danish: UmbLocalizationSet = {
|
||||
$code: 'da',
|
||||
$dir: 'ltr',
|
||||
close: 'Luk',
|
||||
notOnRegional: 'Not on regional',
|
||||
};
|
||||
|
||||
const danishRegional: DefaultLocalizationSet = {
|
||||
const danishRegional: UmbLocalizationSet = {
|
||||
$code: 'da-dk',
|
||||
$dir: 'ltr',
|
||||
close: 'Luk',
|
||||
@@ -57,10 +77,10 @@ const danishRegional: DefaultLocalizationSet = {
|
||||
//#endregion
|
||||
|
||||
describe('UmbLocalizeController', () => {
|
||||
let controller: UmbLocalizeController<TestLocalization>;
|
||||
let controller: UmbLocalizationController;
|
||||
|
||||
beforeEach(async () => {
|
||||
registerLocalization(english, danish, danishRegional);
|
||||
umbLocalizationManager.registerManyLocalizations([english, danish, danishRegional]);
|
||||
document.documentElement.lang = english.$code;
|
||||
document.documentElement.dir = english.$dir;
|
||||
await aTimeout(0);
|
||||
@@ -72,12 +92,12 @@ describe('UmbLocalizeController', () => {
|
||||
getControllers: () => [],
|
||||
removeControllerByAlias: () => {},
|
||||
} satisfies UmbControllerHost;
|
||||
controller = new UmbLocalizeController(host);
|
||||
controller = new UmbLocalizationController(host);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
controller.destroy();
|
||||
localizations.clear();
|
||||
umbLocalizationManager.localizations.clear();
|
||||
});
|
||||
|
||||
it('should have a default language', () => {
|
||||
@@ -130,9 +150,9 @@ describe('UmbLocalizeController', () => {
|
||||
expect(controller.term('logout')).to.equal('Log out'); // Fallback
|
||||
});
|
||||
|
||||
it('should override a term if new translation is registered', () => {
|
||||
it('should override a term if new localization is registered', () => {
|
||||
// Let the registry load the new extension
|
||||
registerLocalization(englishOverride);
|
||||
umbLocalizationManager.registerLocalization(englishOverride);
|
||||
|
||||
expect(controller.term('close')).to.equal('Close 2');
|
||||
});
|
||||
@@ -152,6 +172,42 @@ describe('UmbLocalizeController', () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
expect((controller.term as any)('logout', 'Hello', 'World')).to.equal('Log out');
|
||||
});
|
||||
|
||||
it('only reacts to changes of its own localization-keys', async () => {
|
||||
const element: UmbLocalizationRenderCountElement = await fixture(
|
||||
html`<umb-localization-render-count></umb-localization-render-count>`,
|
||||
);
|
||||
|
||||
// Something triggers multiple updates initially, and it varies how many it is. So we wait for a timeout to ensure that we have a clean slate and then reset the counter:
|
||||
await aTimeout(20);
|
||||
element.amountOfUpdates = 0;
|
||||
|
||||
expect(element.shadowRoot!.textContent).to.equal('Log out');
|
||||
|
||||
// Let the registry load the new extension
|
||||
umbLocalizationManager.registerLocalization(englishOverride);
|
||||
|
||||
// Wait three frames is safe:
|
||||
await new Promise((resolve) => requestAnimationFrame(resolve));
|
||||
await new Promise((resolve) => requestAnimationFrame(resolve));
|
||||
await new Promise((resolve) => requestAnimationFrame(resolve));
|
||||
|
||||
// This should still be the same (cause it should not be affected as the change did not change our localization key)
|
||||
expect(element.amountOfUpdates).to.equal(0);
|
||||
expect(element.shadowRoot!.textContent).to.equal('Log out');
|
||||
|
||||
// Let the registry load the new extension
|
||||
umbLocalizationManager.registerLocalization(englishOverrideLogout);
|
||||
|
||||
// Wait three frames is safe:
|
||||
await new Promise((resolve) => requestAnimationFrame(resolve));
|
||||
await new Promise((resolve) => requestAnimationFrame(resolve));
|
||||
await new Promise((resolve) => requestAnimationFrame(resolve));
|
||||
|
||||
// Now we should have gotten one update and the text should be different
|
||||
expect(element.amountOfUpdates).to.equal(1);
|
||||
expect(element.shadowRoot!.textContent).to.equal('Log out 2');
|
||||
});
|
||||
});
|
||||
|
||||
describe('date', () => {
|
||||
@@ -226,7 +282,7 @@ describe('UmbLocalizeController', () => {
|
||||
});
|
||||
|
||||
it('should have a localize controller', () => {
|
||||
expect(element.localize).to.be.instanceOf(UmbLocalizeController);
|
||||
expect(element.localize).to.be.instanceOf(UmbLocalizationController);
|
||||
});
|
||||
|
||||
it('should update the term when the language changes', async () => {
|
||||
@@ -12,19 +12,16 @@ The above copyright notice and this permission notice shall be included in all c
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import type {
|
||||
DefaultLocalizationSet,
|
||||
UmbLocalizationSet,
|
||||
FunctionParams,
|
||||
LocalizationSet} from './manager.js';
|
||||
import {
|
||||
connectedElements,
|
||||
documentDirection,
|
||||
documentLanguage,
|
||||
fallback,
|
||||
localizations,
|
||||
} from './manager.js';
|
||||
UmbLocalizationSetBase,
|
||||
UmbLocalizationSetKey,
|
||||
} from './localization.manager.js';
|
||||
import { umbLocalizationManager } from './localization.manager.js';
|
||||
import type { LitElement } from '@umbraco-cms/backoffice/external/lit';
|
||||
import type { UmbController, UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
|
||||
const LocalizeControllerAlias = Symbol();
|
||||
const LocalizationControllerAlias = Symbol();
|
||||
/**
|
||||
* The UmbLocalizeController enables localization for your element.
|
||||
*
|
||||
@@ -43,12 +40,13 @@ const LocalizeControllerAlias = Symbol();
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export class UmbLocalizeController<LocalizationType extends LocalizationSet = DefaultLocalizationSet>
|
||||
export class UmbLocalizationController<LocalizationSetType extends UmbLocalizationSetBase = UmbLocalizationSet>
|
||||
implements UmbController
|
||||
{
|
||||
#host;
|
||||
#hostEl;
|
||||
controllerAlias = LocalizeControllerAlias;
|
||||
#hostEl?: HTMLElement & Partial<Pick<LitElement, 'requestUpdate'>>;
|
||||
readonly controllerAlias = LocalizationControllerAlias;
|
||||
#usedKeys = new Array<UmbLocalizationSetKey>();
|
||||
|
||||
constructor(host: UmbControllerHost) {
|
||||
this.#host = host;
|
||||
@@ -57,15 +55,11 @@ export class UmbLocalizeController<LocalizationType extends LocalizationSet = De
|
||||
}
|
||||
|
||||
hostConnected(): void {
|
||||
if (connectedElements.has(this.#hostEl)) {
|
||||
return;
|
||||
}
|
||||
|
||||
connectedElements.add(this.#hostEl);
|
||||
umbLocalizationManager.appendConsumer(this);
|
||||
}
|
||||
|
||||
hostDisconnected(): void {
|
||||
connectedElements.delete(this.#hostEl);
|
||||
umbLocalizationManager.removeConsumer(this);
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
@@ -73,12 +67,24 @@ export class UmbLocalizeController<LocalizationType extends LocalizationSet = De
|
||||
this.#hostEl = undefined as any;
|
||||
}
|
||||
|
||||
documentUpdate() {
|
||||
this.#hostEl?.requestUpdate?.();
|
||||
}
|
||||
|
||||
keysChanged(changedKeys: Set<UmbLocalizationSetKey>) {
|
||||
const hasOneOfTheseKeys = this.#usedKeys.find((key) => changedKeys.has(key));
|
||||
|
||||
if (hasOneOfTheseKeys) {
|
||||
this.#hostEl?.requestUpdate?.();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the host element's directionality as determined by the `dir` attribute. The return value is transformed to
|
||||
* lowercase.
|
||||
*/
|
||||
dir() {
|
||||
return `${this.#hostEl.dir || documentDirection}`.toLowerCase();
|
||||
return `${this.#hostEl?.dir || umbLocalizationManager.documentDirection}`.toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -86,21 +92,25 @@ export class UmbLocalizeController<LocalizationType extends LocalizationSet = De
|
||||
* lowercase.
|
||||
*/
|
||||
lang() {
|
||||
return `${this.#hostEl.lang || documentLanguage}`.toLowerCase();
|
||||
return `${this.#hostEl?.lang || umbLocalizationManager.documentLanguage}`.toLowerCase();
|
||||
}
|
||||
|
||||
private getLocalizationData(lang: string) {
|
||||
const locale = new Intl.Locale(lang);
|
||||
const language = locale?.language.toLowerCase();
|
||||
const region = locale?.region?.toLowerCase() ?? '';
|
||||
const primary = <LocalizationType>localizations.get(`${language}-${region}`);
|
||||
const secondary = <LocalizationType>localizations.get(language);
|
||||
const primary = umbLocalizationManager.localizations.get(`${language}-${region}`) as LocalizationSetType;
|
||||
const secondary = umbLocalizationManager.localizations.get(language) as LocalizationSetType;
|
||||
|
||||
return { locale, language, region, primary, secondary };
|
||||
}
|
||||
|
||||
/** Outputs a translated term. */
|
||||
term<K extends keyof LocalizationType>(key: K, ...args: FunctionParams<LocalizationType[K]>): string {
|
||||
term<K extends keyof LocalizationSetType>(key: K, ...args: FunctionParams<LocalizationSetType[K]>): string {
|
||||
if (!this.#usedKeys.includes(key)) {
|
||||
this.#usedKeys.push(key);
|
||||
}
|
||||
|
||||
const { primary, secondary } = this.getLocalizationData(this.lang());
|
||||
let term: any;
|
||||
|
||||
@@ -109,8 +119,8 @@ export class UmbLocalizeController<LocalizationType extends LocalizationSet = De
|
||||
term = primary[key];
|
||||
} else if (secondary && secondary[key]) {
|
||||
term = secondary[key];
|
||||
} else if (fallback && fallback[key as keyof LocalizationSet]) {
|
||||
term = fallback[key as keyof LocalizationSet];
|
||||
} else if (umbLocalizationManager.fallback && umbLocalizationManager.fallback[key]) {
|
||||
term = umbLocalizationManager.fallback[key];
|
||||
} else {
|
||||
return String(key);
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
This module is a modified copy of the original Shoelace localize package: https://github.com/shoelace-style/localize
|
||||
|
||||
The original license is included below.
|
||||
|
||||
Copyright (c) 2020 A Beautiful Site, LLC
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import type { UmbLocalizationController } from './localization.controller.js';
|
||||
import type { UmbLocalizationEntry } from './types/localization.js';
|
||||
|
||||
export type FunctionParams<T> = T extends (...args: infer U) => string ? U : [];
|
||||
|
||||
export interface UmbLocalizationSetBase {
|
||||
$code: string; // e.g. en, en-GB
|
||||
$dir: 'ltr' | 'rtl';
|
||||
}
|
||||
|
||||
export type UmbLocalizationSetKey = string | number | symbol;
|
||||
|
||||
export interface UmbLocalizationSet extends UmbLocalizationSetBase {
|
||||
[key: UmbLocalizationSetKey]: UmbLocalizationEntry;
|
||||
}
|
||||
|
||||
export const UMB_DEFAULT_LOCALIZATION_CULTURE = 'en-us';
|
||||
|
||||
export class UmbLocalizationManager {
|
||||
connectedControllers = new Set<UmbLocalizationController<UmbLocalizationSetBase>>();
|
||||
#documentElementObserver: MutationObserver;
|
||||
|
||||
#changedKeys: Set<UmbLocalizationSetKey> = new Set();
|
||||
#requestUpdateChangedKeysId?: number = undefined;
|
||||
|
||||
localizations: Map<string, UmbLocalizationSetBase> = new Map();
|
||||
documentDirection = document.documentElement.dir || 'ltr';
|
||||
documentLanguage = document.documentElement.lang || navigator.language;
|
||||
|
||||
get fallback(): UmbLocalizationSet | undefined {
|
||||
return this.localizations.get(UMB_DEFAULT_LOCALIZATION_CULTURE) as UmbLocalizationSet;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.#documentElementObserver = new MutationObserver(this.updateAll);
|
||||
this.#documentElementObserver.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ['dir', 'lang'],
|
||||
});
|
||||
}
|
||||
|
||||
appendConsumer(consumer: UmbLocalizationController<UmbLocalizationSetBase>) {
|
||||
if (this.connectedControllers.has(consumer)) return;
|
||||
this.connectedControllers.add(consumer);
|
||||
}
|
||||
removeConsumer(consumer: UmbLocalizationController<UmbLocalizationSetBase>) {
|
||||
this.connectedControllers.delete(consumer);
|
||||
}
|
||||
|
||||
/** Registers one or more translations */
|
||||
registerLocalization(t: UmbLocalizationSetBase) {
|
||||
const code = t.$code.toLowerCase();
|
||||
|
||||
if (this.localizations.has(code)) {
|
||||
// Merge translations that share the same language code
|
||||
this.localizations.set(code, { ...this.localizations.get(code), ...t });
|
||||
} else {
|
||||
this.localizations.set(code, t);
|
||||
}
|
||||
|
||||
// Declare what keys have been changed:
|
||||
const keys = Object.keys(t);
|
||||
for (const key of keys) {
|
||||
this.#changedKeys.add(key);
|
||||
}
|
||||
this.#requestChangedKeysUpdate();
|
||||
}
|
||||
#registerLocalizationBind = this.registerLocalization.bind(this);
|
||||
|
||||
registerManyLocalizations(translations: Array<UmbLocalizationSetBase>) {
|
||||
translations.map(this.#registerLocalizationBind);
|
||||
}
|
||||
|
||||
/** Updates all localized elements that are currently connected */
|
||||
updateAll = () => {
|
||||
const newDir = document.documentElement.dir || 'ltr';
|
||||
const newLang = document.documentElement.lang || navigator.language;
|
||||
|
||||
if (this.documentDirection === newDir && this.documentLanguage === newLang) return;
|
||||
|
||||
// The document direction or language did changed, so lets move on:
|
||||
this.documentDirection = newDir;
|
||||
this.documentLanguage = newLang;
|
||||
|
||||
// Check if there was any changed.
|
||||
this.connectedControllers.forEach((ctrl) => {
|
||||
ctrl.documentUpdate();
|
||||
});
|
||||
|
||||
if (this.#requestUpdateChangedKeysId) {
|
||||
cancelAnimationFrame(this.#requestUpdateChangedKeysId);
|
||||
this.#requestUpdateChangedKeysId = undefined;
|
||||
}
|
||||
this.#changedKeys.clear();
|
||||
};
|
||||
|
||||
#updateChangedKeys = () => {
|
||||
this.#requestUpdateChangedKeysId = undefined;
|
||||
|
||||
this.connectedControllers.forEach((ctrl) => {
|
||||
ctrl.keysChanged(this.#changedKeys);
|
||||
});
|
||||
|
||||
this.#changedKeys.clear();
|
||||
};
|
||||
|
||||
/**
|
||||
* Request an update of all consumers of the keys defined in #changedKeys.
|
||||
* This waits one frame, which ensures that multiple changes are collected into one.
|
||||
*/
|
||||
#requestChangedKeysUpdate() {
|
||||
if (this.#requestUpdateChangedKeysId) return;
|
||||
this.#requestUpdateChangedKeysId = requestAnimationFrame(this.#updateChangedKeys);
|
||||
}
|
||||
}
|
||||
|
||||
export const umbLocalizationManager = new UmbLocalizationManager();
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
This module is a modified copy of the original Shoelace localize package: https://github.com/shoelace-style/localize
|
||||
|
||||
The original license is included below.
|
||||
|
||||
Copyright (c) 2020 A Beautiful Site, LLC
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import type { UmbLocalizationEntry } from './types/localization.js';
|
||||
import type { LitElement } from '@umbraco-cms/backoffice/external/lit';
|
||||
|
||||
export type FunctionParams<T> = T extends (...args: infer U) => string ? U : [];
|
||||
|
||||
export interface LocalizationSet {
|
||||
$code: string; // e.g. en, en-GB
|
||||
$dir: 'ltr' | 'rtl';
|
||||
}
|
||||
|
||||
export interface DefaultLocalizationSet extends LocalizationSet {
|
||||
[key: string]: UmbLocalizationEntry;
|
||||
}
|
||||
|
||||
export const connectedElements = new Set<HTMLElement>();
|
||||
const documentElementObserver = new MutationObserver(update);
|
||||
export const localizations: Map<string, LocalizationSet> = new Map();
|
||||
export let documentDirection = document.documentElement.dir || 'ltr';
|
||||
export let documentLanguage = document.documentElement.lang || navigator.language;
|
||||
export let fallback: LocalizationSet;
|
||||
|
||||
// Watch for changes on <html lang>
|
||||
documentElementObserver.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ['dir', 'lang'],
|
||||
});
|
||||
|
||||
/** Registers one or more translations */
|
||||
export function registerLocalization(...translation: LocalizationSet[]) {
|
||||
translation.map((t) => {
|
||||
const code = t.$code.toLowerCase();
|
||||
|
||||
if (localizations.has(code)) {
|
||||
// Merge translations that share the same language code
|
||||
localizations.set(code, { ...localizations.get(code), ...t });
|
||||
} else {
|
||||
localizations.set(code, t);
|
||||
}
|
||||
|
||||
// The first translation that's registered is the fallback
|
||||
if (!fallback) {
|
||||
fallback = t;
|
||||
}
|
||||
});
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
/** Updates all localized elements that are currently connected */
|
||||
export function update() {
|
||||
documentDirection = document.documentElement.dir || 'ltr';
|
||||
documentLanguage = document.documentElement.lang || navigator.language;
|
||||
|
||||
[...connectedElements.keys()].map((el) => {
|
||||
if (typeof (el as LitElement).requestUpdate === 'function') {
|
||||
// TODO: We might want to implement a specific Umbraco method for informing about this. and then make the default UmbLitElement call requestUpdate..? Cause then others can implement their own solution?
|
||||
(el as LitElement).requestUpdate();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -706,6 +706,13 @@ export const data: Array<UmbMockDataTypeModel> = [
|
||||
icon: 'icon-book-alt',
|
||||
groupKey: 'demo-block-group-id',
|
||||
},
|
||||
{
|
||||
label: 'Test broken group key',
|
||||
contentElementTypeKey: 'test-block-id',
|
||||
editorSize: 'medium',
|
||||
icon: 'icon-war',
|
||||
groupKey: 'group-id-that-does-not-exist',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -1488,4 +1488,30 @@ export const data: Array<UmbMockDocumentTypeModel> = [
|
||||
properties: [],
|
||||
containers: [],
|
||||
},
|
||||
{
|
||||
allowedTemplateIds: [],
|
||||
defaultTemplateId: null,
|
||||
id: 'test-block-id',
|
||||
alias: 'testBlock',
|
||||
name: 'Test broken group key',
|
||||
description: null,
|
||||
icon: 'icon-war',
|
||||
allowedAsRoot: true,
|
||||
variesByCulture: false,
|
||||
variesBySegment: false,
|
||||
isElement: true,
|
||||
hasChildren: false,
|
||||
isContainer: false,
|
||||
parentId: null,
|
||||
isFolder: false,
|
||||
allowedContentTypes: [],
|
||||
compositions: [],
|
||||
cleanup: {
|
||||
preventCleanup: false,
|
||||
keepAllVersionsNewerThanDays: null,
|
||||
keepLatestVersionPerDayForDays: null,
|
||||
},
|
||||
properties: [],
|
||||
containers: [],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
const { rest } = window.MockServiceWorker;
|
||||
import { umbDocumentTypeMockDb } from '../../data/document-type/document-type.db.js';
|
||||
import { UMB_SLUG } from './slug.js';
|
||||
import { umbracoPath } from '@umbraco-cms/backoffice/utils';
|
||||
|
||||
export const folderHandlers = [
|
||||
rest.post(umbracoPath(`${UMB_SLUG}/folder`), async (req, res, ctx) => {
|
||||
const requestBody = await req.json();
|
||||
if (!requestBody) return res(ctx.status(400, 'no body found'));
|
||||
|
||||
const id = umbDocumentTypeMockDb.folder.create(requestBody);
|
||||
|
||||
return res(
|
||||
ctx.status(201),
|
||||
ctx.set({
|
||||
Location: req.url.href + '/' + id,
|
||||
'Umb-Generated-Resource': id,
|
||||
}),
|
||||
);
|
||||
}),
|
||||
|
||||
rest.get(umbracoPath(`${UMB_SLUG}/folder/:id`), (req, res, ctx) => {
|
||||
const id = req.params.id as string;
|
||||
if (!id) return res(ctx.status(400));
|
||||
const response = umbDocumentTypeMockDb.folder.read(id);
|
||||
return res(ctx.status(200), ctx.json(response));
|
||||
}),
|
||||
|
||||
rest.put(umbracoPath(`${UMB_SLUG}/folder/:id`), async (req, res, ctx) => {
|
||||
const id = req.params.id as string;
|
||||
if (!id) return res(ctx.status(400, 'no id found'));
|
||||
const requestBody = await req.json();
|
||||
if (!requestBody) return res(ctx.status(400, 'no body found'));
|
||||
umbDocumentTypeMockDb.folder.update(id, requestBody);
|
||||
return res(ctx.status(200));
|
||||
}),
|
||||
|
||||
rest.delete(umbracoPath(`${UMB_SLUG}/folder/:id`), (req, res, ctx) => {
|
||||
const id = req.params.id as string;
|
||||
if (!id) return res(ctx.status(400));
|
||||
umbDocumentTypeMockDb.folder.delete(id);
|
||||
return res(ctx.status(200));
|
||||
}),
|
||||
];
|
||||
@@ -1,5 +1,6 @@
|
||||
import { treeHandlers } from './tree.handlers.js';
|
||||
import { detailHandlers } from './detail.handlers.js';
|
||||
import { itemHandlers } from './item.handlers.js';
|
||||
import { folderHandlers } from './folder.handlers.js';
|
||||
|
||||
export const handlers = [...treeHandlers, ...itemHandlers, ...detailHandlers];
|
||||
export const handlers = [...treeHandlers, ...itemHandlers, ...folderHandlers, ...detailHandlers];
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import type { ManifestPropertyEditorUi } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
export const manifest: ManifestPropertyEditorUi = {
|
||||
type: 'propertyEditorUi',
|
||||
alias: 'Umb.PropertyEditorUi.BlockTypeGroupConfiguration',
|
||||
name: 'Block Grid Group Configuration Property Editor UI',
|
||||
js: () => import('./property-editor-ui-block-grid-group-configuration.element.js'),
|
||||
meta: {
|
||||
label: '',
|
||||
icon: 'icon-box-alt',
|
||||
group: 'common',
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,56 @@
|
||||
import { html, customElement, property, css } from '@umbraco-cms/backoffice/external/lit';
|
||||
import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
|
||||
import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import { UmbId } from '@umbraco-cms/backoffice/id';
|
||||
import type { UmbBlockGridGroupType } from '@umbraco-cms/backoffice/block';
|
||||
|
||||
@customElement('umb-property-editor-ui-block-grid-group-configuration')
|
||||
export class UmbPropertyEditorUIBlockGridGroupConfigurationElement
|
||||
extends UmbLitElement
|
||||
implements UmbPropertyEditorUiElement
|
||||
{
|
||||
private _value: Array<UmbBlockGridGroupType> = [];
|
||||
|
||||
@property({ type: Array })
|
||||
public get value(): Array<UmbBlockGridGroupType> {
|
||||
return this._value;
|
||||
}
|
||||
public set value(value: Array<UmbBlockGridGroupType>) {
|
||||
this._value = value || [];
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
public set config(config: UmbPropertyEditorConfigCollection | undefined) {}
|
||||
|
||||
#addGroup() {
|
||||
this.value = [...this._value, { name: 'Unnamed group', key: UmbId.new() }];
|
||||
this.dispatchEvent(new CustomEvent('property-value-change'));
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<uui-button label=${this.localize.term('blockEditor_addBlockGroup')} look="placeholder" @click=${this.#addGroup}>
|
||||
${this.localize.term('blockEditor_addBlockGroup')}
|
||||
</uui-button>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = [
|
||||
UmbTextStyles,
|
||||
css`
|
||||
uui-button {
|
||||
display: block;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
export default UmbPropertyEditorUIBlockGridGroupConfigurationElement;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-property-editor-ui-block-grid-group-configuration': UmbPropertyEditorUIBlockGridGroupConfigurationElement;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import type { Meta, Story } from '@storybook/web-components';
|
||||
import type { UmbPropertyEditorUIBlockGridGroupConfigurationElement } from './property-editor-ui-block-grid-group-configuration.element.js';
|
||||
import { html } from '@umbraco-cms/backoffice/external/lit';
|
||||
|
||||
import './property-editor-ui-block-grid-group-configuration.element.js';
|
||||
|
||||
export default {
|
||||
title: 'Property Editor UIs/Block Grid Group Configuration',
|
||||
component: 'umb-property-editor-ui-block-grid-group-configuration',
|
||||
id: 'umb-property-editor-ui-block-grid-group-configuration',
|
||||
} as Meta;
|
||||
|
||||
export const AAAOverview: Story<UmbPropertyEditorUIBlockGridGroupConfigurationElement> = () =>
|
||||
html` <umb-property-editor-ui-block-grid-group-configuration></umb-property-editor-ui-block-grid-group-configuration>`;
|
||||
AAAOverview.storyName = 'Overview';
|
||||
@@ -1,10 +1,21 @@
|
||||
import type { UmbBlockTypeBaseModel, UmbInputBlockTypeElement } from '../../../block-type/index.js';
|
||||
import type { UmbBlockTypeWithGroupKey, UmbInputBlockTypeElement } from '../../../block-type/index.js';
|
||||
import '../../../block-type/components/input-block-type/index.js';
|
||||
import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { html, customElement, property } from '@umbraco-cms/backoffice/external/lit';
|
||||
import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor';
|
||||
import { html, customElement, property, state, repeat, nothing, css } from '@umbraco-cms/backoffice/external/lit';
|
||||
import {
|
||||
UmbPropertyValueChangeEvent,
|
||||
type UmbPropertyEditorConfigCollection,
|
||||
} from '@umbraco-cms/backoffice/property-editor';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
|
||||
import {
|
||||
UMB_BLOCK_GRID_TYPE,
|
||||
type UmbBlockGridGroupType,
|
||||
type UmbBlockGridGroupTypeConfiguration,
|
||||
} from '@umbraco-cms/backoffice/block';
|
||||
import type { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui';
|
||||
import { UMB_PROPERTY_DATASET_CONTEXT, type UmbPropertyDatasetContext } from '@umbraco-cms/backoffice/property';
|
||||
import { UMB_WORKSPACE_MODAL, UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/modal';
|
||||
|
||||
/**
|
||||
* @element umb-property-editor-ui-block-grid-type-configuration
|
||||
@@ -14,22 +25,150 @@ export class UmbPropertyEditorUIBlockGridTypeConfigurationElement
|
||||
extends UmbLitElement
|
||||
implements UmbPropertyEditorUiElement
|
||||
{
|
||||
#datasetContext?: UmbPropertyDatasetContext;
|
||||
#blockTypeWorkspaceModalRegistration?: UmbModalRouteRegistrationController<
|
||||
typeof UMB_WORKSPACE_MODAL.DATA,
|
||||
typeof UMB_WORKSPACE_MODAL.VALUE
|
||||
>;
|
||||
|
||||
private _value: Array<UmbBlockTypeWithGroupKey> = [];
|
||||
@property({ attribute: false })
|
||||
value: UmbBlockTypeBaseModel[] = [];
|
||||
get value() {
|
||||
return this._value;
|
||||
}
|
||||
set value(value: Array<UmbBlockTypeWithGroupKey>) {
|
||||
this._value = value ?? [];
|
||||
}
|
||||
|
||||
@property({ type: Object, attribute: false })
|
||||
public config?: UmbPropertyEditorConfigCollection;
|
||||
|
||||
render() {
|
||||
return html`<umb-input-block-type
|
||||
entity-type="block-grid-type"
|
||||
.value=${this.value}
|
||||
@change=${(e: Event) => {
|
||||
this.value = (e.target as UmbInputBlockTypeElement).value;
|
||||
}}></umb-input-block-type>`;
|
||||
@state()
|
||||
private _blockGroups: Array<UmbBlockGridGroupType> = [];
|
||||
|
||||
@state()
|
||||
private _mappedValuesAndGroups: Array<UmbBlockGridGroupTypeConfiguration> = [];
|
||||
|
||||
@state()
|
||||
private _workspacePath?: string;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.consumeContext(UMB_PROPERTY_DATASET_CONTEXT, async (instance) => {
|
||||
this.#datasetContext = instance;
|
||||
this.#observeProperties();
|
||||
});
|
||||
|
||||
this.#blockTypeWorkspaceModalRegistration?.destroy();
|
||||
|
||||
this.#blockTypeWorkspaceModalRegistration = new UmbModalRouteRegistrationController(this, UMB_WORKSPACE_MODAL)
|
||||
.addAdditionalPath(UMB_BLOCK_GRID_TYPE)
|
||||
.onSetup(() => {
|
||||
return { data: { entityType: UMB_BLOCK_GRID_TYPE, preset: {} }, modal: { size: 'large' } };
|
||||
})
|
||||
.observeRouteBuilder((routeBuilder) => {
|
||||
const newpath = routeBuilder({});
|
||||
this._workspacePath = newpath;
|
||||
});
|
||||
}
|
||||
|
||||
static styles = [UmbTextStyles];
|
||||
async #observeProperties() {
|
||||
if (!this.#datasetContext) return;
|
||||
|
||||
this.observe(await this.#datasetContext.propertyValueByAlias('blockGroups'), (value) => {
|
||||
this._blockGroups = (value as Array<UmbBlockGridGroupType>) ?? [];
|
||||
this.#mapValuesToBlockGroups();
|
||||
});
|
||||
this.observe(await this.#datasetContext.propertyValueByAlias('blocks'), () => {
|
||||
this.#mapValuesToBlockGroups();
|
||||
});
|
||||
}
|
||||
|
||||
#mapValuesToBlockGroups() {
|
||||
// What if a block is in a group that does not exist in the block groups? Should it be removed? (Right now they will never be rendered)
|
||||
const valuesWithNoGroup = this._value.filter((value) => !value.groupKey);
|
||||
|
||||
const valuesWithGroup = this._blockGroups.map((group) => {
|
||||
return { name: group.name, key: group.key, blocks: this._value.filter((value) => value.groupKey === group.key) };
|
||||
});
|
||||
|
||||
this._mappedValuesAndGroups = [{ blocks: valuesWithNoGroup }, ...valuesWithGroup];
|
||||
}
|
||||
|
||||
#onChange(e: CustomEvent, groupKey?: string) {
|
||||
const updatedValues = (e.target as UmbInputBlockTypeElement).value.map((value) => ({ ...value, groupKey }));
|
||||
const filteredValues = this.value.filter((value) => value.groupKey !== groupKey);
|
||||
this.value = [...filteredValues, ...updatedValues];
|
||||
this.dispatchEvent(new UmbPropertyValueChangeEvent());
|
||||
}
|
||||
|
||||
#onCreate(e: CustomEvent, groupKey: string | null) {
|
||||
const selectedElementType = e.detail.contentElementTypeKey;
|
||||
if (selectedElementType) {
|
||||
this.#blockTypeWorkspaceModalRegistration?.open({}, 'create/' + selectedElementType + '/' + groupKey);
|
||||
}
|
||||
}
|
||||
|
||||
#deleteGroup(groupKey: string) {
|
||||
this.#datasetContext?.setPropertyValue(
|
||||
'blockGroups',
|
||||
this._blockGroups.filter((group) => group.key !== groupKey),
|
||||
);
|
||||
|
||||
// Should blocks that belonged to the removed group be deleted as well?
|
||||
this.value = this._value.filter((block) => block.groupKey !== groupKey);
|
||||
}
|
||||
|
||||
#changeGroupName(e: UUIInputEvent, groupKey: string) {
|
||||
const groupName = e.target.value as string;
|
||||
this.#datasetContext?.setPropertyValue(
|
||||
'blockGroups',
|
||||
this._blockGroups.map((group) => (group.key === groupKey ? { ...group, name: groupName } : group)),
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`${repeat(
|
||||
this._mappedValuesAndGroups,
|
||||
(group) => group.key,
|
||||
(group) =>
|
||||
html`${group.key ? this.#renderGroupInput(group.key, group.name) : nothing}
|
||||
<umb-input-block-type
|
||||
.value=${group.blocks}
|
||||
.workspacePath=${this._workspacePath}
|
||||
@create=${(e: CustomEvent) => this.#onCreate(e, group.key ?? null)}
|
||||
@change=${(e: CustomEvent) => this.#onChange(e, group.key)}></umb-input-block-type>`,
|
||||
)}`;
|
||||
}
|
||||
|
||||
#renderGroupInput(groupKey: string, groupName?: string) {
|
||||
return html`<uui-input
|
||||
auto-width
|
||||
label="Group"
|
||||
.value=${groupName ?? ''}
|
||||
@change=${(e: UUIInputEvent) => this.#changeGroupName(e, groupKey)}>
|
||||
<uui-button compact slot="append" label="delete" @click=${() => this.#deleteGroup(groupKey)}>
|
||||
<uui-icon name="icon-trash"></uui-icon>
|
||||
</uui-button>
|
||||
</uui-input>`;
|
||||
}
|
||||
|
||||
static styles = [
|
||||
UmbTextStyles,
|
||||
css`
|
||||
uui-input {
|
||||
margin-top: var(--uui-size-6);
|
||||
margin-bottom: var(--uui-size-4);
|
||||
}
|
||||
|
||||
uui-input:not(:hover, :focus) {
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
uui-input:not(:hover, :focus) uui-button {
|
||||
opacity: 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
export default UmbPropertyEditorUIBlockGridTypeConfigurationElement;
|
||||
|
||||
@@ -2,5 +2,12 @@ import { manifest as blockGridEditor } from './block-grid-editor/manifests.js';
|
||||
import { manifest as blockGridLayoutStylesheet } from './block-grid-layout-stylesheet/manifests.js';
|
||||
import { manifest as blockGridTypeConfiguration } from './block-grid-type-configuration/manifests.js';
|
||||
import { manifest as blockGridColumnSpan } from './block-grid-column-span/manifests.js';
|
||||
import { manifest as blockGridGroupConfiguration } from './block-grid-group-configuration/manifests.js';
|
||||
|
||||
export const manifests = [blockGridTypeConfiguration, blockGridEditor, blockGridLayoutStylesheet, blockGridColumnSpan];
|
||||
export const manifests = [
|
||||
blockGridTypeConfiguration,
|
||||
blockGridEditor,
|
||||
blockGridLayoutStylesheet,
|
||||
blockGridColumnSpan,
|
||||
blockGridGroupConfiguration,
|
||||
];
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import type { UmbBlockTypeBaseModel } from '../block-type/index.js';
|
||||
import type { UmbBlockTypeBaseModel, UmbBlockTypeWithGroupKey } from '../block-type/index.js';
|
||||
|
||||
export const UMB_BLOCK_GRID_TYPE = 'block-grid-type';
|
||||
|
||||
export interface UmbBlockGridType extends UmbBlockTypeBaseModel {
|
||||
columnSpanOptions: Array<number>;
|
||||
@@ -9,5 +11,13 @@ export interface UmbBlockGridType extends UmbBlockTypeBaseModel {
|
||||
thumbnail?: string;
|
||||
areaGridColumns?: number;
|
||||
areas: Array<any>;
|
||||
groupKey: null | string;
|
||||
}
|
||||
|
||||
export interface UmbBlockGridGroupType {
|
||||
name: string;
|
||||
key: string;
|
||||
}
|
||||
|
||||
export interface UmbBlockGridGroupTypeConfiguration extends Partial<UmbBlockGridGroupType> {
|
||||
blocks: Array<UmbBlockTypeWithGroupKey>;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import type { UmbBlockTypeBaseModel, UmbInputBlockTypeElement } from '../../../block-type/index.js';
|
||||
import '../../../block-type/components/input-block-type/index.js';
|
||||
import { UMB_BLOCK_LIST_TYPE } from '../../types.js';
|
||||
import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { html, customElement, property } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
|
||||
import { UMB_WORKSPACE_MODAL, UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/modal';
|
||||
|
||||
/**
|
||||
* @element umb-property-editor-ui-block-list-type-configuration
|
||||
@@ -14,16 +16,47 @@ export class UmbPropertyEditorUIBlockListBlockConfigurationElement
|
||||
extends UmbLitElement
|
||||
implements UmbPropertyEditorUiElement
|
||||
{
|
||||
#blockTypeWorkspaceModalRegistration?: UmbModalRouteRegistrationController<
|
||||
typeof UMB_WORKSPACE_MODAL.DATA,
|
||||
typeof UMB_WORKSPACE_MODAL.VALUE
|
||||
>;
|
||||
|
||||
@state()
|
||||
private _workspacePath?: string;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.#blockTypeWorkspaceModalRegistration?.destroy();
|
||||
|
||||
this.#blockTypeWorkspaceModalRegistration = new UmbModalRouteRegistrationController(this, UMB_WORKSPACE_MODAL)
|
||||
.addAdditionalPath(UMB_BLOCK_LIST_TYPE)
|
||||
.onSetup(() => {
|
||||
return { data: { entityType: UMB_BLOCK_LIST_TYPE, preset: {} }, modal: { size: 'large' } };
|
||||
})
|
||||
.observeRouteBuilder((routeBuilder) => {
|
||||
const newpath = routeBuilder({});
|
||||
this._workspacePath = newpath;
|
||||
});
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
value: UmbBlockTypeBaseModel[] = [];
|
||||
|
||||
@property({ type: Object, attribute: false })
|
||||
public config?: UmbPropertyEditorConfigCollection;
|
||||
|
||||
#onCreate(e: CustomEvent) {
|
||||
const selectedElementType = e.detail.contentElementTypeKey;
|
||||
if (selectedElementType) {
|
||||
this.#blockTypeWorkspaceModalRegistration?.open({}, 'create/' + selectedElementType + '/null');
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<umb-input-block-type
|
||||
entity-type="block-list-type"
|
||||
.value=${this.value}
|
||||
.workspacePath=${this._workspacePath}
|
||||
@create=${this.#onCreate}
|
||||
@change=${(e: Event) => {
|
||||
this.value = (e.target as UmbInputBlockTypeElement).value;
|
||||
}}></umb-input-block-type>`;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { UmbBlockTypeBaseModel } from '../block-type/index.js';
|
||||
import type { UmbBlockLayoutBaseModel } from '../index.js';
|
||||
|
||||
export const UMB_BLOCK_LIST_TYPE = 'block-list-type';
|
||||
|
||||
export interface UmbBlockListTypeModel extends UmbBlockTypeBaseModel {}
|
||||
export interface UmbBlockListLayoutModel extends UmbBlockLayoutBaseModel {}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export * from './input-block-type/index.js';
|
||||
export * from './block-type-card/index.js';
|
||||
export * from './input-block-type/index.js';
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
import type { UmbBlockTypeBaseModel } from '../../types.js';
|
||||
import {
|
||||
UMB_DOCUMENT_TYPE_PICKER_MODAL,
|
||||
UMB_MODAL_MANAGER_CONTEXT,
|
||||
UMB_WORKSPACE_MODAL,
|
||||
UmbModalRouteRegistrationController,
|
||||
} from '@umbraco-cms/backoffice/modal';
|
||||
import { UMB_DOCUMENT_TYPE_PICKER_MODAL, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
|
||||
import '../block-type-card/index.js';
|
||||
import { css, html, customElement, property, state, repeat } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
|
||||
import type { UmbPropertyDatasetContext } from '@umbraco-cms/backoffice/property';
|
||||
import { UMB_PROPERTY_DATASET_CONTEXT } from '@umbraco-cms/backoffice/property';
|
||||
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
|
||||
|
||||
@customElement('umb-input-block-type')
|
||||
export class UmbInputBlockTypeElement<
|
||||
BlockType extends UmbBlockTypeBaseModel = UmbBlockTypeBaseModel,
|
||||
> extends UmbLitElement {
|
||||
//
|
||||
@property({ type: Array, attribute: false })
|
||||
public get value() {
|
||||
return this._items;
|
||||
@@ -23,43 +19,23 @@ export class UmbInputBlockTypeElement<
|
||||
this._items = items ?? [];
|
||||
}
|
||||
|
||||
@property({ type: String, attribute: 'entity-type' })
|
||||
public get entityType() {
|
||||
return this.#entityType;
|
||||
}
|
||||
public set entityType(entityType) {
|
||||
this.#entityType = entityType;
|
||||
|
||||
this.#blockTypeWorkspaceModalRegistration?.destroy();
|
||||
|
||||
if (entityType) {
|
||||
// TODO: Make specific modal token that requires data.
|
||||
this.#blockTypeWorkspaceModalRegistration = new UmbModalRouteRegistrationController(this, UMB_WORKSPACE_MODAL)
|
||||
.addAdditionalPath(entityType)
|
||||
.onSetup(() => {
|
||||
return { data: { entityType: entityType, preset: {} }, modal: { size: 'large' } };
|
||||
})
|
||||
.observeRouteBuilder((routeBuilder) => {
|
||||
const newpath = routeBuilder({});
|
||||
this._workspacePath = newpath;
|
||||
});
|
||||
}
|
||||
}
|
||||
#entityType?: string;
|
||||
@property({ type: String })
|
||||
workspacePath?: string;
|
||||
|
||||
@state()
|
||||
private _items: Array<BlockType> = [];
|
||||
|
||||
@state()
|
||||
private _workspacePath?: string;
|
||||
|
||||
#blockTypeWorkspaceModalRegistration?: UmbModalRouteRegistrationController<
|
||||
typeof UMB_WORKSPACE_MODAL.DATA,
|
||||
typeof UMB_WORKSPACE_MODAL.VALUE
|
||||
>;
|
||||
#datasetContext?: UmbPropertyDatasetContext;
|
||||
#filter: Array<UmbBlockTypeBaseModel> = [];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.consumeContext(UMB_PROPERTY_DATASET_CONTEXT, async (instance) => {
|
||||
this.#datasetContext = instance;
|
||||
this.observe(await this.#datasetContext?.propertyValueByAlias('blocks'), (value) => {
|
||||
this.#filter = value as Array<UmbBlockTypeBaseModel>;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
create() {
|
||||
@@ -74,23 +50,23 @@ export class UmbInputBlockTypeElement<
|
||||
// Only pick elements:
|
||||
docType.isElement &&
|
||||
// Prevent picking the an already used element type:
|
||||
this._items.find((x) => x.contentElementTypeKey === docType.unique) === undefined,
|
||||
this.#filter &&
|
||||
this.#filter.find((x) => x.contentElementTypeKey === docType.unique) === undefined,
|
||||
},
|
||||
});
|
||||
|
||||
const modalValue = await modalContext?.onSubmit();
|
||||
const selectedElementType = modalValue.selection[0];
|
||||
|
||||
if (selectedElementType) {
|
||||
this.#blockTypeWorkspaceModalRegistration?.open({}, 'create/' + selectedElementType);
|
||||
this.dispatchEvent(new CustomEvent('create', { detail: { contentElementTypeKey: selectedElementType } }));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// No need to fire a change event, as all changes are made directly to the property, via context api.
|
||||
}
|
||||
|
||||
deleteItem(contentElementTypeKey: string) {
|
||||
this._items = this._items.filter((x) => x.contentElementTypeKey !== contentElementTypeKey);
|
||||
this.value = this._items.filter((x) => x.contentElementTypeKey !== contentElementTypeKey);
|
||||
this.dispatchEvent(new UmbChangeEvent());
|
||||
}
|
||||
|
||||
@@ -99,12 +75,21 @@ export class UmbInputBlockTypeElement<
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
${this._items ? repeat(this._items, (item) => item.contentElementTypeKey, this.#renderItem) : ''}
|
||||
${this.#renderButton()}
|
||||
`;
|
||||
return html`<div>
|
||||
${repeat(this.value, (block) => block.contentElementTypeKey, this.#renderItem)} ${this.#renderButton()}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
#renderItem = (item: BlockType) => {
|
||||
return html`
|
||||
<umb-block-type-card
|
||||
.workspacePath=${this.workspacePath}
|
||||
.key=${item.contentElementTypeKey}
|
||||
@delete=${() => this.deleteItem(item.contentElementTypeKey)}>
|
||||
</umb-block-type-card>
|
||||
`;
|
||||
};
|
||||
|
||||
#renderButton() {
|
||||
return html`
|
||||
<uui-button id="add-button" look="placeholder" @click=${() => this.create()} label="open">
|
||||
@@ -114,19 +99,9 @@ export class UmbInputBlockTypeElement<
|
||||
`;
|
||||
}
|
||||
|
||||
#renderItem = (item: BlockType) => {
|
||||
return html`
|
||||
<umb-block-type-card
|
||||
.workspacePath=${this._workspacePath}
|
||||
.key=${item.contentElementTypeKey}
|
||||
@delete=${() => this.deleteItem(item.contentElementTypeKey)}>
|
||||
</umb-block-type-card>
|
||||
`;
|
||||
};
|
||||
|
||||
static styles = [
|
||||
css`
|
||||
:host {
|
||||
div {
|
||||
display: grid;
|
||||
gap: var(--uui-size-space-3);
|
||||
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
|
||||
@@ -135,6 +110,7 @@ export class UmbInputBlockTypeElement<
|
||||
|
||||
#add-button {
|
||||
text-align: center;
|
||||
min-height: 150px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@@ -142,6 +118,18 @@ export class UmbInputBlockTypeElement<
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
uui-input {
|
||||
border: none;
|
||||
margin: var(--uui-size-space-6) 0 var(--uui-size-space-4);
|
||||
}
|
||||
|
||||
uui-input:hover uui-button {
|
||||
opacity: 1;
|
||||
}
|
||||
uui-input uui-button {
|
||||
opacity: 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import type { UmbBlockTypeBaseModel } from '../types.js';
|
||||
import type { UmbBlockTypeBaseModel, UmbBlockTypeWithGroupKey } from '../types.js';
|
||||
import type { UmbPropertyDatasetContext } from '@umbraco-cms/backoffice/property';
|
||||
import { UMB_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/property';
|
||||
import type {
|
||||
UmbInvariantableWorkspaceContextInterface,
|
||||
UmbWorkspaceContextInterface} from '@umbraco-cms/backoffice/workspace';
|
||||
UmbWorkspaceContextInterface,
|
||||
} from '@umbraco-cms/backoffice/workspace';
|
||||
import {
|
||||
UmbEditableWorkspaceContextBase,
|
||||
UmbInvariantWorkspacePropertyDatasetContext,
|
||||
@@ -13,7 +14,7 @@ import type { UmbControllerHost, UmbControllerHostElement } from '@umbraco-cms/b
|
||||
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
|
||||
import type { ManifestWorkspace, PropertyEditorConfigProperty } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
export class UmbBlockTypeWorkspaceContext<BlockTypeData extends UmbBlockTypeBaseModel = UmbBlockTypeBaseModel>
|
||||
export class UmbBlockTypeWorkspaceContext<BlockTypeData extends UmbBlockTypeWithGroupKey = UmbBlockTypeWithGroupKey>
|
||||
extends UmbEditableWorkspaceContextBase<BlockTypeData>
|
||||
implements UmbInvariantableWorkspaceContextInterface
|
||||
{
|
||||
@@ -57,9 +58,11 @@ export class UmbBlockTypeWorkspaceContext<BlockTypeData extends UmbBlockTypeBase
|
||||
});
|
||||
}
|
||||
|
||||
async create(contentElementTypeId: string) {
|
||||
async create(contentElementTypeId: string, groupKey?: string | null) {
|
||||
//Only set groupKey property if it exists
|
||||
const data: BlockTypeData = {
|
||||
contentElementTypeKey: contentElementTypeId,
|
||||
...(groupKey && { groupKey: groupKey }),
|
||||
} as BlockTypeData;
|
||||
|
||||
this.setIsNew(true);
|
||||
|
||||
@@ -6,9 +6,9 @@ import type { UmbRoute } from '@umbraco-cms/backoffice/router';
|
||||
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
|
||||
|
||||
import { UmbWorkspaceIsNewRedirectController } from '@umbraco-cms/backoffice/workspace';
|
||||
import type { UmbApi} from '@umbraco-cms/backoffice/extension-api';
|
||||
import type { UmbApi } from '@umbraco-cms/backoffice/extension-api';
|
||||
import { UmbExtensionsApiInitializer, createExtensionApi } from '@umbraco-cms/backoffice/extension-api';
|
||||
import type { ManifestWorkspace} from '@umbraco-cms/backoffice/extension-registry';
|
||||
import type { ManifestWorkspace } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
@customElement('umb-block-type-workspace')
|
||||
@@ -39,11 +39,13 @@ export class UmbBlockTypeWorkspaceElement extends UmbLitElement {
|
||||
|
||||
this._routes = [
|
||||
{
|
||||
path: 'create/:elementTypeKey',
|
||||
// Would it make more sense to have groupKey before elementTypeKey?
|
||||
path: 'create/:elementTypeKey/:groupKey',
|
||||
component: this.#editorElement,
|
||||
setup: async (_component, info) => {
|
||||
const elementTypeKey = info.match.params.elementTypeKey;
|
||||
this.#workspaceContext!.create(elementTypeKey);
|
||||
const groupKey = info.match.params.groupKey === 'null' ? null : info.match.params.groupKey;
|
||||
this.#workspaceContext!.create(elementTypeKey, groupKey);
|
||||
|
||||
new UmbWorkspaceIsNewRedirectController(
|
||||
this,
|
||||
|
||||
@@ -45,7 +45,6 @@ export class UmbBlockWorkspaceContext<
|
||||
readonly name = this.#label.asObservable();
|
||||
|
||||
constructor(host: UmbControllerHost, workspaceArgs: { manifest: ManifestWorkspace }) {
|
||||
// TODO: We don't need a repo here, so maybe we should not require this of the UmbEditableWorkspaceContextBase
|
||||
super(host, workspaceArgs.manifest.alias);
|
||||
this.#entityType = workspaceArgs.manifest.meta?.entityType;
|
||||
this.workspaceAlias = workspaceArgs.manifest.alias;
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import { UmbControllerEvent } from '@umbraco-cms/backoffice/controller-api';
|
||||
|
||||
export interface UmbActionEventArgs {
|
||||
unique: string;
|
||||
parentUnique: string | null; // TODO: remove this when we have endpoints to support mapping a new item without reloading the parent tree item
|
||||
}
|
||||
|
||||
export class UmbActionEvent extends UmbControllerEvent {
|
||||
#args: UmbActionEventArgs;
|
||||
|
||||
public constructor(type: string, args: UmbActionEventArgs) {
|
||||
super(type);
|
||||
this.#args = args;
|
||||
}
|
||||
|
||||
getUnique(): string {
|
||||
return this.#args.unique;
|
||||
}
|
||||
|
||||
// TODO: this can be removed when the server supports reloading a tree item without reloading the parent
|
||||
getParentUnique(): string | null {
|
||||
return this.#args.parentUnique;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
export * from './repository-action.js';
|
||||
export * from './action.interface.js';
|
||||
export * from './action-event.context.js';
|
||||
export * from './action.event.js';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { css, html, nothing, customElement, property, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import { map } from '@umbraco-cms/backoffice/external/rxjs';
|
||||
import type { UmbSectionSidebarContext} from '@umbraco-cms/backoffice/section';
|
||||
import type { UmbSectionSidebarContext } from '@umbraco-cms/backoffice/section';
|
||||
import { UMB_SECTION_SIDEBAR_CONTEXT } from '@umbraco-cms/backoffice/section';
|
||||
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
@@ -44,7 +44,7 @@ export class UmbEntityActionsBundleElement extends UmbLitElement {
|
||||
#observeEntityActions() {
|
||||
this.observe(
|
||||
umbExtensionsRegistry
|
||||
.extensionsOfType('entityAction')
|
||||
.byType('entityAction')
|
||||
.pipe(map((actions) => actions.filter((action) => action.meta.entityTypes.includes(this.entityType!)))),
|
||||
(actions) => {
|
||||
this._hasActions = actions.length > 0;
|
||||
|
||||
@@ -2,7 +2,7 @@ import { UmbInputListBaseElement } from '../input-list-base/input-list-base.js';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import { css, html, nothing, customElement, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UMB_SECTION_PICKER_MODAL } from '@umbraco-cms/backoffice/modal';
|
||||
import type { ManifestSection} from '@umbraco-cms/backoffice/extension-registry';
|
||||
import type { ManifestSection } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
@customElement('umb-input-section')
|
||||
@@ -18,7 +18,7 @@ export class UmbInputSectionElement extends UmbInputListBaseElement {
|
||||
|
||||
private _observeSections() {
|
||||
if (this.value.length > 0) {
|
||||
this.observe(umbExtensionsRegistry.extensionsOfType('section'), (sections: Array<ManifestSection>) => {
|
||||
this.observe(umbExtensionsRegistry.byType('section'), (sections: Array<ManifestSection>) => {
|
||||
this._sections = sections.filter((section) => this.value.includes(section.alias));
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -38,6 +38,19 @@ export class UmbInputTinyMceElement extends FormControlMixin(UmbLitElement) {
|
||||
return this._editorElement?.querySelector('iframe') ?? undefined;
|
||||
}
|
||||
|
||||
set value(newValue: FormDataEntryValue | FormData) {
|
||||
super.value = newValue;
|
||||
const newContent = newValue?.toString() ?? '';
|
||||
|
||||
if(this.#editorRef && this.#editorRef.getContent() != newContent) {
|
||||
this.#editorRef.setContent(newContent);
|
||||
}
|
||||
}
|
||||
|
||||
get value(): FormDataEntryValue | FormData {
|
||||
return super.value;
|
||||
}
|
||||
|
||||
@query('#editor', true)
|
||||
private _editorElement?: HTMLElement;
|
||||
|
||||
@@ -78,7 +91,7 @@ export class UmbInputTinyMceElement extends FormControlMixin(UmbLitElement) {
|
||||
* the plugins are ready and so are not associated with the editor.
|
||||
*/
|
||||
async #loadPlugins() {
|
||||
const observable = umbExtensionsRegistry?.extensionsOfType('tinyMcePlugin');
|
||||
const observable = umbExtensionsRegistry?.byType('tinyMcePlugin');
|
||||
const manifests = (await firstValueFrom(observable)) as ManifestTinyMcePlugin[];
|
||||
|
||||
const promises = [];
|
||||
|
||||
@@ -12,8 +12,8 @@ import {
|
||||
export class UmbCopyDataTypeEntityAction extends UmbEntityActionBase<UmbCopyDataTypeRepository> {
|
||||
#modalManagerContext?: UmbModalManagerContext;
|
||||
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string) {
|
||||
super(host, repositoryAlias, unique);
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string, entityType: string) {
|
||||
super(host, repositoryAlias, unique, entityType);
|
||||
|
||||
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => {
|
||||
this.#modalManagerContext = instance;
|
||||
|
||||
@@ -8,8 +8,8 @@ import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
|
||||
export class UmbCreateDataTypeEntityAction extends UmbEntityActionBase<UmbDataTypeDetailRepository> {
|
||||
#modalManagerContext?: UmbModalManagerContext;
|
||||
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string) {
|
||||
super(host, repositoryAlias, unique);
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string, entityType: string) {
|
||||
super(host, repositoryAlias, unique, entityType);
|
||||
|
||||
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => {
|
||||
this.#modalManagerContext = instance;
|
||||
|
||||
@@ -12,8 +12,8 @@ import {
|
||||
export class UmbMoveDataTypeEntityAction extends UmbEntityActionBase<UmbMoveDataTypeRepository> {
|
||||
#modalManagerContext?: UmbModalManagerContext;
|
||||
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string) {
|
||||
super(host, repositoryAlias, unique);
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string, entityType: string) {
|
||||
super(host, repositoryAlias, unique, entityType);
|
||||
|
||||
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => {
|
||||
this.#modalManagerContext = instance;
|
||||
|
||||
@@ -5,13 +5,14 @@ import type { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui';
|
||||
import type {
|
||||
UmbDataTypePickerFlowModalData,
|
||||
UmbDataTypePickerFlowModalValue,
|
||||
UmbModalRouteBuilder} from '@umbraco-cms/backoffice/modal';
|
||||
UmbModalRouteBuilder,
|
||||
} from '@umbraco-cms/backoffice/modal';
|
||||
import {
|
||||
UMB_DATA_TYPE_PICKER_FLOW_DATA_TYPE_PICKER_MODAL,
|
||||
UmbModalBaseElement,
|
||||
UmbModalRouteRegistrationController,
|
||||
} from '@umbraco-cms/backoffice/modal';
|
||||
import type { ManifestPropertyEditorUi} from '@umbraco-cms/backoffice/extension-registry';
|
||||
import type { ManifestPropertyEditorUi } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import type { UmbEntityTreeItemModel } from '@umbraco-cms/backoffice/tree';
|
||||
import { UMB_DATATYPE_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/data-type';
|
||||
@@ -107,7 +108,7 @@ export class UmbDataTypePickerFlowModalElement extends UmbModalBaseElement<
|
||||
'_repositoryItemsObserver',
|
||||
);
|
||||
|
||||
this.observe(umbExtensionsRegistry.extensionsOfType('propertyEditorUi'), (propertyEditorUIs) => {
|
||||
this.observe(umbExtensionsRegistry.byType('propertyEditorUi'), (propertyEditorUIs) => {
|
||||
// Only include Property Editor UIs which has Property Editor Schema Alias
|
||||
this.#propertyEditorUIs = propertyEditorUIs.filter(
|
||||
(propertyEditorUi) => !!propertyEditorUi.meta.propertyEditorSchemaAlias,
|
||||
|
||||
@@ -3,11 +3,10 @@ import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import type { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui';
|
||||
import type {
|
||||
UmbPropertyEditorUIPickerModalData,
|
||||
UmbPropertyEditorUIPickerModalValue} from '@umbraco-cms/backoffice/modal';
|
||||
import {
|
||||
UmbModalBaseElement,
|
||||
UmbPropertyEditorUIPickerModalValue,
|
||||
} from '@umbraco-cms/backoffice/modal';
|
||||
import type { ManifestPropertyEditorUi} from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';
|
||||
import type { ManifestPropertyEditorUi } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
interface GroupedPropertyEditorUIs {
|
||||
@@ -38,7 +37,7 @@ export class UmbPropertyEditorUIPickerModalElement extends UmbModalBaseElement<
|
||||
#usePropertyEditorUIs() {
|
||||
if (!this.data) return;
|
||||
|
||||
this.observe(umbExtensionsRegistry.extensionsOfType('propertyEditorUi'), (propertyEditorUIs) => {
|
||||
this.observe(umbExtensionsRegistry.byType('propertyEditorUi'), (propertyEditorUIs) => {
|
||||
// Only include Property Editor UIs which has Property Editor Schema Alias
|
||||
this._propertyEditorUIs = propertyEditorUIs.filter(
|
||||
(propertyEditorUi) => !!propertyEditorUi.meta.propertyEditorSchemaAlias,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { manifests as folderManifests } from './folder/manifests.js';
|
||||
import { manifests as reloadManifests } from './reload-tree-item-children/manifests.js';
|
||||
import { UmbDataTypeTreeRepository } from './data-type-tree.repository.js';
|
||||
import { UmbDataTypeTreeStore } from './data-type-tree.store.js';
|
||||
import type {
|
||||
@@ -44,4 +45,4 @@ const treeItem: ManifestTreeItem = {
|
||||
},
|
||||
};
|
||||
|
||||
export const manifests = [treeRepository, treeStore, tree, treeItem, ...folderManifests];
|
||||
export const manifests = [treeRepository, treeStore, tree, treeItem, ...folderManifests, ...reloadManifests];
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import {
|
||||
UMB_DATA_TYPE_ENTITY_TYPE,
|
||||
UMB_DATA_TYPE_FOLDER_ENTITY_TYPE,
|
||||
UMB_DATA_TYPE_ROOT_ENTITY_TYPE,
|
||||
} from '../../entity.js';
|
||||
import { UMB_DATA_TYPE_DETAIL_REPOSITORY_ALIAS } from '../../repository/index.js';
|
||||
import { UmbReloadTreeItemChildrenEntityAction } from '@umbraco-cms/backoffice/tree';
|
||||
import { type ManifestEntityAction } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
export const manifests: Array<ManifestEntityAction> = [
|
||||
{
|
||||
type: 'entityAction',
|
||||
alias: 'Umb.EntityAction.DataType.Tree.ReloadTreeItemChildren',
|
||||
name: 'Reload Data Type Tree Item Children Entity Action',
|
||||
weight: 10,
|
||||
api: UmbReloadTreeItemChildrenEntityAction,
|
||||
meta: {
|
||||
icon: 'icon-refresh',
|
||||
label: 'Reload children...',
|
||||
repositoryAlias: UMB_DATA_TYPE_DETAIL_REPOSITORY_ALIAS,
|
||||
entityTypes: [UMB_DATA_TYPE_ENTITY_TYPE, UMB_DATA_TYPE_ROOT_ENTITY_TYPE, UMB_DATA_TYPE_FOLDER_ENTITY_TYPE],
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -2,8 +2,8 @@ import { UmbEntityActionBase } from '../../entity-action.js';
|
||||
import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
|
||||
|
||||
export class UmbCopyEntityAction<T extends { copy(): Promise<void> }> extends UmbEntityActionBase<T> {
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string) {
|
||||
super(host, repositoryAlias, unique);
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string, entityType: string) {
|
||||
super(host, repositoryAlias, unique, entityType);
|
||||
}
|
||||
|
||||
async execute() {
|
||||
|
||||
@@ -10,8 +10,8 @@ export class UmbDeleteEntityAction<
|
||||
> extends UmbEntityActionBase<T> {
|
||||
#modalManager?: UmbModalManagerContext;
|
||||
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string) {
|
||||
super(host, repositoryAlias, unique);
|
||||
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;
|
||||
|
||||
@@ -4,8 +4,8 @@ import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controlle
|
||||
// TODO: investigate what we need to finish the generic move action. We would need to open a picker, which requires a modal token,
|
||||
// maybe we can use kinds to make a specific manifest to the move action.
|
||||
export class UmbMoveEntityAction<T extends { move(): Promise<void> }> extends UmbEntityActionBase<T> {
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string) {
|
||||
super(host, repositoryAlias, unique);
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string, entityType: string) {
|
||||
super(host, repositoryAlias, unique, entityType);
|
||||
}
|
||||
|
||||
async execute() {
|
||||
|
||||
@@ -8,8 +8,8 @@ import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
|
||||
export class UmbRenameEntityAction extends UmbEntityActionBase<UmbRenameRepository<{ unique: string }>> {
|
||||
#modalManagerContext?: UmbModalManagerContext;
|
||||
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string) {
|
||||
super(host, repositoryAlias, unique);
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string, entityType: string) {
|
||||
super(host, repositoryAlias, unique, entityType);
|
||||
|
||||
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => {
|
||||
this.#modalManagerContext = instance;
|
||||
|
||||
@@ -4,8 +4,8 @@ import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controlle
|
||||
export class UmbSortChildrenOfEntityAction<
|
||||
T extends { sortChildrenOf(): Promise<void> },
|
||||
> extends UmbEntityActionBase<T> {
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string) {
|
||||
super(host, repositoryAlias, unique);
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string, entityType: string) {
|
||||
super(host, repositoryAlias, unique, entityType);
|
||||
}
|
||||
|
||||
async execute() {
|
||||
|
||||
@@ -10,8 +10,8 @@ export class UmbTrashEntityAction<
|
||||
> extends UmbEntityActionBase<T> {
|
||||
#modalContext?: UmbModalManagerContext;
|
||||
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string) {
|
||||
super(host, repositoryAlias, unique);
|
||||
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;
|
||||
|
||||
@@ -30,7 +30,7 @@ export class UmbEntityActionListElement extends UmbLitElement {
|
||||
type="entityAction"
|
||||
default-element="umb-entity-action"
|
||||
.filter=${this._filter}
|
||||
.props=${{ unique: this.unique }}></umb-extension-slot>
|
||||
.props=${{ unique: this.unique, entityType: this.entityType }}></umb-extension-slot>
|
||||
`
|
||||
: '';
|
||||
}
|
||||
|
||||
@@ -7,6 +7,20 @@ import { createExtensionApi } from '@umbraco-cms/backoffice/extension-api';
|
||||
|
||||
@customElement('umb-entity-action')
|
||||
export class UmbEntityActionElement extends UmbLitElement {
|
||||
private _entityType?: string | null;
|
||||
@property({ type: String })
|
||||
public get entityType() {
|
||||
return this._entityType;
|
||||
}
|
||||
public set entityType(value: string | undefined | null) {
|
||||
const oldValue = this._entityType;
|
||||
this._entityType = value;
|
||||
if (oldValue !== this._entityType) {
|
||||
this.#createApi();
|
||||
this.requestUpdate('entityType', oldValue);
|
||||
}
|
||||
}
|
||||
|
||||
private _unique?: string | null;
|
||||
@property({ type: String })
|
||||
public get unique() {
|
||||
@@ -37,10 +51,17 @@ export class UmbEntityActionElement extends UmbLitElement {
|
||||
}
|
||||
|
||||
async #createApi() {
|
||||
// only create the api if we have all the required properties
|
||||
if (!this._manifest) return;
|
||||
if (this._unique === undefined) return;
|
||||
if (!this._entityType) return;
|
||||
|
||||
this.#api = await createExtensionApi(this._manifest, [this, this._manifest.meta.repositoryAlias, this.unique]);
|
||||
this.#api = await createExtensionApi(this._manifest, [
|
||||
this,
|
||||
this._manifest.meta.repositoryAlias,
|
||||
this.unique,
|
||||
this.entityType,
|
||||
]);
|
||||
|
||||
// TODO: Fix so when we use a HREF it does not refresh the page?
|
||||
this._href = await this.#api.getHref?.();
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { UmbControllerEvent } from '@umbraco-cms/backoffice/controller-api';
|
||||
|
||||
export interface UmbEntityActionEventArgs {
|
||||
unique: string;
|
||||
entityType: string;
|
||||
}
|
||||
|
||||
export class UmbEntityActionEvent extends UmbControllerEvent {
|
||||
#args: UmbEntityActionEventArgs;
|
||||
|
||||
public constructor(type: string, args: UmbEntityActionEventArgs) {
|
||||
super(type);
|
||||
this.#args = args;
|
||||
}
|
||||
|
||||
getEntityType(): string {
|
||||
return this.#args.entityType;
|
||||
}
|
||||
|
||||
getUnique(): string {
|
||||
return this.#args.unique;
|
||||
}
|
||||
}
|
||||
@@ -7,11 +7,13 @@ export interface UmbEntityAction<RepositoryType> extends UmbAction<RepositoryTyp
|
||||
}
|
||||
|
||||
export class UmbEntityActionBase<RepositoryType> extends UmbActionBase<RepositoryType> {
|
||||
entityType: string;
|
||||
unique: string;
|
||||
repositoryAlias: string;
|
||||
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string) {
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string, entityType: string) {
|
||||
super(host, repositoryAlias);
|
||||
this.entityType = entityType;
|
||||
this.unique = unique;
|
||||
this.repositoryAlias = repositoryAlias;
|
||||
}
|
||||
|
||||
@@ -2,3 +2,4 @@ export * from './entity-action-list.element.js';
|
||||
export * from './entity-action.element.js';
|
||||
export * from './entity-action.js';
|
||||
export * from './common/index.js';
|
||||
export * from './entity-action.event.js';
|
||||
|
||||
@@ -3,7 +3,7 @@ import { css, html, customElement, query, state, property } from '@umbraco-cms/b
|
||||
import type { UUIComboboxElement, UUIComboboxEvent } from '@umbraco-cms/backoffice/external/uui';
|
||||
import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui';
|
||||
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
|
||||
import type { ManifestLocalization} from '@umbraco-cms/backoffice/extension-registry';
|
||||
import type { ManifestLocalization } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
interface UmbCultureInputOption {
|
||||
@@ -38,7 +38,7 @@ export class UmbUiCultureInputElement extends FormControlMixin(UmbLitElement) {
|
||||
|
||||
#observeTranslations() {
|
||||
this.observe(
|
||||
umbExtensionsRegistry.extensionsOfType('localization'),
|
||||
umbExtensionsRegistry.byType('localization'),
|
||||
(localizationManifests) => {
|
||||
this.#mapToOptions(localizationManifests);
|
||||
},
|
||||
|
||||
@@ -2,7 +2,7 @@ import { aTimeout, elementUpdated, expect, fixture, html } from '@open-wc/testin
|
||||
import { UmbLocalizeElement } from './localize.element.js';
|
||||
import { umbLocalizationRegistry } from '@umbraco-cms/backoffice/localization';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { UmbLocalizeController } from '@umbraco-cms/backoffice/localization-api';
|
||||
import { UmbLocalizationController } from '@umbraco-cms/backoffice/localization-api';
|
||||
|
||||
const english = {
|
||||
type: 'localization',
|
||||
@@ -62,7 +62,7 @@ describe('umb-localize', () => {
|
||||
});
|
||||
|
||||
it('should have a localize controller', () => {
|
||||
expect(element.localize).to.be.instanceOf(UmbLocalizeController);
|
||||
expect(element.localize).to.be.instanceOf(UmbLocalizationController);
|
||||
});
|
||||
|
||||
it('should localize a key', async () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { aTimeout, expect } from '@open-wc/testing';
|
||||
import { UmbLocalizationRegistry } from './localization.registry.js';
|
||||
import type { ManifestLocalization} from '@umbraco-cms/backoffice/extension-registry';
|
||||
import type { ManifestLocalization } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
//#region Localizations
|
||||
|
||||
@@ -1,129 +1,111 @@
|
||||
import type {
|
||||
UmbLocalizationSetBase,
|
||||
UmbLocalizationDictionary,
|
||||
UmbLocalizationFlatDictionary,
|
||||
LocalizationSet} from '@umbraco-cms/backoffice/localization-api';
|
||||
import {
|
||||
registerLocalization,
|
||||
localizations,
|
||||
} from '@umbraco-cms/backoffice/localization-api';
|
||||
import { hasDefaultExport, loadManifestPlainJs } from '@umbraco-cms/backoffice/extension-api';
|
||||
import type { UmbBackofficeExtensionRegistry} from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { umbLocalizationManager } from '@umbraco-cms/backoffice/localization-api';
|
||||
import type { ManifestLocalization, UmbBackofficeExtensionRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import {
|
||||
BehaviorSubject,
|
||||
Subject,
|
||||
combineLatest,
|
||||
map,
|
||||
distinctUntilChanged,
|
||||
filter,
|
||||
startWith,
|
||||
} from '@umbraco-cms/backoffice/external/rxjs';
|
||||
import { UmbStringState } from '@umbraco-cms/backoffice/observable-api';
|
||||
import { combineLatest } from '@umbraco-cms/backoffice/external/rxjs';
|
||||
import { hasDefaultExport, loadManifestPlainJs } from '@umbraco-cms/backoffice/extension-api';
|
||||
|
||||
function addOrUpdateDictionary(
|
||||
innerDictionary: UmbLocalizationFlatDictionary,
|
||||
dictionaryName: string,
|
||||
dictionary: UmbLocalizationDictionary['value'],
|
||||
) {
|
||||
for (const [key, value] of Object.entries(dictionary)) {
|
||||
innerDictionary[`${dictionaryName}_${key}`] = value;
|
||||
}
|
||||
}
|
||||
|
||||
export class UmbLocalizationRegistry {
|
||||
#currentLanguage = new UmbStringState(document.documentElement.lang ?? 'en-us');
|
||||
readonly currentLanguage = this.#currentLanguage.asObservable();
|
||||
|
||||
#loadedExtAliases: Array<string> = [];
|
||||
|
||||
/**
|
||||
* Get the current registered translations.
|
||||
*/
|
||||
get localizations() {
|
||||
return localizations;
|
||||
return umbLocalizationManager.localizations;
|
||||
}
|
||||
|
||||
get isDefaultLoaded() {
|
||||
return this.#isDefaultLoaded.asObservable();
|
||||
}
|
||||
|
||||
#currentLanguage = new Subject<string>();
|
||||
#isDefaultLoaded = new BehaviorSubject(false);
|
||||
|
||||
constructor(extensionRegistry: UmbBackofficeExtensionRegistry) {
|
||||
const currentLanguage$ = this.#currentLanguage.pipe(
|
||||
startWith(document.documentElement.lang || 'en-us'),
|
||||
map((x) => x.toLowerCase()),
|
||||
distinctUntilChanged(),
|
||||
);
|
||||
combineLatest([this.currentLanguage, extensionRegistry.byType('localization')]).subscribe(
|
||||
async ([currentLanguage, extensions]) => {
|
||||
const locale = new Intl.Locale(currentLanguage);
|
||||
const filteredExt = extensions.filter(
|
||||
(ext) =>
|
||||
ext.meta.culture.toLowerCase() === locale.baseName.toLowerCase() ||
|
||||
ext.meta.culture.toLowerCase() === locale.language.toLowerCase(),
|
||||
);
|
||||
|
||||
const currentExtensions$ = extensionRegistry.extensionsOfType('localization').pipe(
|
||||
filter((x) => x.length > 0),
|
||||
distinctUntilChanged((prev, curr) => prev.length === curr.length && prev.every((x) => curr.includes(x))),
|
||||
);
|
||||
// Only get the extensions that are not already loading/loaded:
|
||||
const diff = filteredExt.filter((ext) => !this.#loadedExtAliases.includes(ext.alias));
|
||||
if (diff.length !== 0) {
|
||||
// got new localizations to load:
|
||||
const translations = await Promise.all(diff.map(this.#loadExtension));
|
||||
|
||||
combineLatest([currentLanguage$, currentExtensions$]).subscribe(async ([userCulture, extensions]) => {
|
||||
const locale = new Intl.Locale(userCulture);
|
||||
const translations = await Promise.all(
|
||||
extensions
|
||||
.filter(
|
||||
(x) =>
|
||||
x.meta.culture.toLowerCase() === locale.baseName.toLowerCase() ||
|
||||
x.meta.culture.toLowerCase() === locale.language.toLowerCase(),
|
||||
)
|
||||
.map(async (extension) => {
|
||||
const innerDictionary: UmbLocalizationFlatDictionary = {};
|
||||
if (translations.length) {
|
||||
umbLocalizationManager.registerManyLocalizations(translations);
|
||||
|
||||
// If extension contains a dictionary, add it to the inner dictionary.
|
||||
if (extension.meta.localizations) {
|
||||
for (const [dictionaryName, dictionary] of Object.entries(extension.meta.localizations)) {
|
||||
this.#addOrUpdateDictionary(innerDictionary, dictionaryName, dictionary);
|
||||
}
|
||||
// Set the document language
|
||||
const newLang = locale.baseName.toLowerCase();
|
||||
if (document.documentElement.lang.toLowerCase() !== newLang) {
|
||||
document.documentElement.lang = newLang;
|
||||
}
|
||||
|
||||
// If extension contains a js file, load it and add the default dictionary to the inner dictionary.
|
||||
if (extension.js) {
|
||||
const loadedExtension = await loadManifestPlainJs(extension.js);
|
||||
|
||||
if (loadedExtension && hasDefaultExport<UmbLocalizationDictionary>(loadedExtension)) {
|
||||
for (const [dictionaryName, dictionary] of Object.entries(loadedExtension.default)) {
|
||||
this.#addOrUpdateDictionary(innerDictionary, dictionaryName, dictionary);
|
||||
}
|
||||
}
|
||||
// Set the document direction to the direction of the primary language
|
||||
const newDir = translations[0].$dir ?? 'ltr';
|
||||
if (document.documentElement.dir !== newDir) {
|
||||
document.documentElement.dir = newDir;
|
||||
}
|
||||
|
||||
// Notify subscribers that the inner dictionary has changed.
|
||||
return {
|
||||
$code: extension.meta.culture.toLowerCase(),
|
||||
$dir: extension.meta.direction ?? 'ltr',
|
||||
...innerDictionary,
|
||||
} satisfies LocalizationSet;
|
||||
}),
|
||||
);
|
||||
|
||||
if (translations.length) {
|
||||
registerLocalization(...translations);
|
||||
|
||||
// Set the document language
|
||||
const newLang = locale.baseName.toLowerCase();
|
||||
if (document.documentElement.lang.toLowerCase() !== newLang) {
|
||||
document.documentElement.lang = newLang;
|
||||
}
|
||||
}
|
||||
|
||||
// Set the document direction to the direction of the primary language
|
||||
const newDir = translations[0].$dir ?? 'ltr';
|
||||
if (document.documentElement.dir !== newDir) {
|
||||
document.documentElement.dir = newDir;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.#isDefaultLoaded.value) {
|
||||
this.#isDefaultLoaded.next(true);
|
||||
this.#isDefaultLoaded.complete();
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#loadExtension = async (extension: ManifestLocalization) => {
|
||||
this.#loadedExtAliases.push(extension.alias);
|
||||
|
||||
const innerDictionary: UmbLocalizationFlatDictionary = {};
|
||||
|
||||
// If extension contains a dictionary, add it to the inner dictionary.
|
||||
if (extension.meta.localizations) {
|
||||
for (const [dictionaryName, dictionary] of Object.entries(extension.meta.localizations)) {
|
||||
addOrUpdateDictionary(innerDictionary, dictionaryName, dictionary);
|
||||
}
|
||||
}
|
||||
|
||||
// If extension contains a js file, load it and add the default dictionary to the inner dictionary.
|
||||
if (extension.js) {
|
||||
const loadedExtension = await loadManifestPlainJs(extension.js);
|
||||
|
||||
if (loadedExtension && hasDefaultExport<UmbLocalizationDictionary>(loadedExtension)) {
|
||||
for (const [dictionaryName, dictionary] of Object.entries(loadedExtension.default)) {
|
||||
addOrUpdateDictionary(innerDictionary, dictionaryName, dictionary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Notify subscribers that the inner dictionary has changed.
|
||||
return {
|
||||
$code: extension.meta.culture.toLowerCase(),
|
||||
$dir: extension.meta.direction ?? 'ltr',
|
||||
...innerDictionary,
|
||||
} satisfies UmbLocalizationSetBase;
|
||||
};
|
||||
|
||||
/**
|
||||
* Load a language from the extension registry.
|
||||
* @param locale The locale to load.
|
||||
*/
|
||||
loadLanguage(locale: string) {
|
||||
this.#currentLanguage.next(locale);
|
||||
}
|
||||
|
||||
#addOrUpdateDictionary(
|
||||
innerDictionary: UmbLocalizationFlatDictionary,
|
||||
dictionaryName: string,
|
||||
dictionary: UmbLocalizationDictionary['value'],
|
||||
) {
|
||||
for (const [key, value] of Object.entries(dictionary)) {
|
||||
innerDictionary[`${dictionaryName}_${key}`] = value;
|
||||
}
|
||||
this.#currentLanguage.setValue(locale.toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbSelectionManager } from '@umbraco-cms/backoffice/utils';
|
||||
import type { ManifestSection} from '@umbraco-cms/backoffice/extension-registry';
|
||||
import type { ManifestSection } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import type {
|
||||
UmbSectionPickerModalData,
|
||||
UmbSectionPickerModalValue} from '@umbraco-cms/backoffice/modal';
|
||||
import {
|
||||
UmbModalBaseElement,
|
||||
} from '@umbraco-cms/backoffice/modal';
|
||||
import type { UmbSectionPickerModalData, UmbSectionPickerModalValue } from '@umbraco-cms/backoffice/modal';
|
||||
import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';
|
||||
|
||||
@customElement('umb-section-picker-modal')
|
||||
export class UmbSectionPickerModalElement extends UmbModalBaseElement<
|
||||
@@ -28,7 +24,7 @@ export class UmbSectionPickerModalElement extends UmbModalBaseElement<
|
||||
this.#selectionManager.setSelection(this.data?.selection ?? []);
|
||||
|
||||
this.observe(
|
||||
umbExtensionsRegistry.extensionsOfType('section'),
|
||||
umbExtensionsRegistry.byType('section'),
|
||||
(sections: Array<ManifestSection>) => (this._sections = sections),
|
||||
),
|
||||
'umbSectionsObserver';
|
||||
|
||||
@@ -14,6 +14,6 @@ export const UMB_DATA_TYPE_PICKER_FLOW_MODAL = new UmbModalToken<
|
||||
>('Umb.Modal.DataTypePickerFlow', {
|
||||
modal: {
|
||||
type: 'sidebar',
|
||||
size: 'small',
|
||||
size: 'medium',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import type { PropertyValueMap } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { customElement, css, html, property, map, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
|
||||
import type { UmbPropertyEditorUiElement} from '@umbraco-cms/backoffice/extension-registry';
|
||||
import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { firstValueFrom } from '@umbraco-cms/backoffice/external/rxjs';
|
||||
import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor';
|
||||
@@ -78,7 +78,7 @@ export class UmbPropertyEditorUITinyMceToolbarConfigurationElement
|
||||
|
||||
private async getToolbarPlugins(): Promise<void> {
|
||||
// Get all the toolbar plugins
|
||||
const plugin$ = umbExtensionsRegistry.extensionsOfType('tinyMcePlugin');
|
||||
const plugin$ = umbExtensionsRegistry.byType('tinyMcePlugin');
|
||||
|
||||
const plugins = await firstValueFrom(plugin$);
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { UmbObserverController } from '@umbraco-cms/backoffice/observable-a
|
||||
import { UmbStringState } from '@umbraco-cms/backoffice/observable-api';
|
||||
import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { UmbBaseController } from '@umbraco-cms/backoffice/class-api';
|
||||
import type { ManifestTheme} from '@umbraco-cms/backoffice/extension-registry';
|
||||
import type { ManifestTheme } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { loadManifestPlainCss } from '@umbraco-cms/backoffice/extension-api';
|
||||
|
||||
@@ -37,7 +37,7 @@ export class UmbThemeContext extends UmbBaseController {
|
||||
localStorage.setItem(LOCAL_STORAGE_KEY, themeAlias);
|
||||
this.#themeObserver = this.observe(
|
||||
umbExtensionsRegistry
|
||||
.extensionsOfType('theme')
|
||||
.byType('theme')
|
||||
.pipe(map((extensions) => extensions.filter((extension) => extension.alias === themeAlias))),
|
||||
async (themes) => {
|
||||
this.#styleElement?.remove();
|
||||
|
||||
@@ -7,8 +7,8 @@ import { type UmbFolderRepository, UMB_FOLDER_CREATE_MODAL } from '@umbraco-cms/
|
||||
export class UmbCreateFolderEntityAction<T extends UmbFolderRepository> extends UmbEntityActionBase<T> {
|
||||
#modalContext?: UmbModalManagerContext;
|
||||
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string) {
|
||||
super(host, repositoryAlias, unique);
|
||||
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;
|
||||
|
||||
@@ -8,8 +8,8 @@ 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) {
|
||||
super(host, repositoryAlias, unique);
|
||||
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;
|
||||
|
||||
@@ -9,8 +9,8 @@ export class UmbFolderUpdateEntityAction<
|
||||
> extends UmbEntityActionBase<T> {
|
||||
#modalContext?: UmbModalManagerContext;
|
||||
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string) {
|
||||
super(host, repositoryAlias, unique);
|
||||
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;
|
||||
|
||||
@@ -22,4 +22,10 @@ export * from './data-source/index.js';
|
||||
// Folder
|
||||
export * from './folder/index.js';
|
||||
|
||||
//
|
||||
export {
|
||||
UmbReloadTreeItemChildrenEntityAction,
|
||||
UmbReloadTreeItemChildrenRequestEntityActionEvent,
|
||||
} from './reload-tree-item-children/index.js';
|
||||
|
||||
export { UmbTreeRepositoryBase } from './tree-repository-base.js';
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export { UmbReloadTreeItemChildrenEntityAction } from './reload-tree-item-children.action.js';
|
||||
export { UmbReloadTreeItemChildrenRequestEntityActionEvent } from './reload-tree-item-children-request.event.js';
|
||||
@@ -0,0 +1,9 @@
|
||||
import { UmbEntityActionEvent, type UmbEntityActionEventArgs } from '@umbraco-cms/backoffice/entity-action';
|
||||
|
||||
export class UmbReloadTreeItemChildrenRequestEntityActionEvent extends UmbEntityActionEvent {
|
||||
static readonly TYPE = 'reload-tree-item-children-request';
|
||||
|
||||
constructor(args: UmbEntityActionEventArgs) {
|
||||
super(UmbReloadTreeItemChildrenRequestEntityActionEvent.TYPE, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import type { UmbCopyDataTypeRepository } from '../../data-type/repository/copy/data-type-copy.repository.js';
|
||||
import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action';
|
||||
import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
|
||||
import type { UmbActionEventContext } from '@umbraco-cms/backoffice/action';
|
||||
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
|
||||
import { UmbReloadTreeItemChildrenRequestEntityActionEvent } from '@umbraco-cms/backoffice/tree';
|
||||
|
||||
export class UmbReloadTreeItemChildrenEntityAction extends UmbEntityActionBase<UmbCopyDataTypeRepository> {
|
||||
#actionEventContext?: UmbActionEventContext;
|
||||
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string, entityType: string) {
|
||||
super(host, repositoryAlias, unique, entityType);
|
||||
|
||||
this.consumeContext(UMB_ACTION_EVENT_CONTEXT, (instance) => {
|
||||
this.#actionEventContext = instance;
|
||||
});
|
||||
}
|
||||
|
||||
async execute() {
|
||||
if (!this.#actionEventContext) throw new Error('Action Event context is not available');
|
||||
this.#actionEventContext.dispatchEvent(
|
||||
new UmbReloadTreeItemChildrenRequestEntityActionEvent({
|
||||
unique: this.unique,
|
||||
entityType: this.entityType,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,9 @@ import { UmbBooleanState, UmbDeepState, UmbStringState } from '@umbraco-cms/back
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { UmbBaseController } from '@umbraco-cms/backoffice/class-api';
|
||||
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
|
||||
import { UMB_ACTION_EVENT_CONTEXT, type UmbActionEventContext } from '@umbraco-cms/backoffice/action';
|
||||
import type { UmbEntityActionEvent } from '@umbraco-cms/backoffice/entity-action';
|
||||
import { UmbReloadTreeItemChildrenRequestEntityActionEvent } from '@umbraco-cms/backoffice/tree';
|
||||
|
||||
export type UmbTreeItemUniqueFunction<TreeItemType extends UmbTreeItemModelBase> = (
|
||||
x: TreeItemType,
|
||||
@@ -52,6 +55,7 @@ export class UmbTreeItemContextBase<TreeItemType extends UmbTreeItemModelBase>
|
||||
treeContext?: UmbTreeContextBase<TreeItemType>;
|
||||
#sectionContext?: UmbSectionContext;
|
||||
#sectionSidebarContext?: UmbSectionSidebarContext;
|
||||
#actionEventContext?: UmbActionEventContext;
|
||||
#getUniqueFunction: UmbTreeItemUniqueFunction<TreeItemType>;
|
||||
|
||||
constructor(host: UmbControllerHost, getUniqueFunction: UmbTreeItemUniqueFunction<TreeItemType>) {
|
||||
@@ -129,6 +133,18 @@ export class UmbTreeItemContextBase<TreeItemType extends UmbTreeItemModelBase>
|
||||
this.#observeIsSelected();
|
||||
this.#observeHasChildren();
|
||||
});
|
||||
|
||||
this.consumeContext(UMB_ACTION_EVENT_CONTEXT, (instance) => {
|
||||
this.#actionEventContext = instance;
|
||||
this.#actionEventContext.removeEventListener(
|
||||
UmbReloadTreeItemChildrenRequestEntityActionEvent.TYPE,
|
||||
this.#onReloadRequest as EventListener,
|
||||
);
|
||||
this.#actionEventContext.addEventListener(
|
||||
UmbReloadTreeItemChildrenRequestEntityActionEvent.TYPE,
|
||||
this.#onReloadRequest as EventListener,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
getTreeItem() {
|
||||
@@ -181,7 +197,7 @@ export class UmbTreeItemContextBase<TreeItemType extends UmbTreeItemModelBase>
|
||||
#observeActions() {
|
||||
this.observe(
|
||||
umbExtensionsRegistry
|
||||
.extensionsOfType('entityAction')
|
||||
.byType('entityAction')
|
||||
.pipe(map((actions) => actions.filter((action) => action.meta.entityTypes.includes(this.entityType!)))),
|
||||
(actions) => {
|
||||
this.#hasActions.setValue(actions.length > 0);
|
||||
@@ -206,10 +222,26 @@ export class UmbTreeItemContextBase<TreeItemType extends UmbTreeItemModelBase>
|
||||
});
|
||||
}
|
||||
|
||||
#onReloadRequest = (event: UmbEntityActionEvent) => {
|
||||
// Only handle children request here. Root request is handled by the tree context
|
||||
if (!this.unique) return;
|
||||
if (event.getUnique() !== this.unique) return;
|
||||
if (event.getEntityType() !== this.entityType) return;
|
||||
this.requestChildren();
|
||||
};
|
||||
|
||||
// TODO: use router context
|
||||
constructPath(pathname: string, entityType: string, unique: string | null) {
|
||||
return `section/${pathname}/workspace/${entityType}/edit/${unique}`;
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
this.#actionEventContext?.removeEventListener(
|
||||
UmbReloadTreeItemChildrenRequestEntityActionEvent.TYPE,
|
||||
this.#onReloadRequest as EventListener,
|
||||
);
|
||||
super.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
export const UMB_TREE_ITEM_CONTEXT = new UmbContextToken<UmbTreeItemContext<any>>('UmbTreeItemContext');
|
||||
|
||||
@@ -7,9 +7,6 @@ import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
|
||||
|
||||
@customElement('umb-tree-item-base')
|
||||
export class UmbTreeItemBaseElement extends UmbLitElement {
|
||||
@state()
|
||||
private _iconAlias?: string;
|
||||
|
||||
@state()
|
||||
private _item?: UmbTreeItemModelBase;
|
||||
|
||||
|
||||
@@ -3,10 +3,9 @@ import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
|
||||
import type {
|
||||
ManifestMenuItemTreeKind,
|
||||
UmbBackofficeManifestKind,
|
||||
UmbMenuItemElement} from '@umbraco-cms/backoffice/extension-registry';
|
||||
import {
|
||||
umbExtensionsRegistry,
|
||||
UmbMenuItemElement,
|
||||
} from '@umbraco-cms/backoffice/extension-registry';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
// TODO: Move to separate file:
|
||||
const manifest: UmbBackofficeManifestKind = {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { UmbReloadTreeItemChildrenRequestEntityActionEvent } from './reload-tree-item-children/index.js';
|
||||
import type { UmbTreeItemModelBase } from './types.js';
|
||||
import type { UmbTreeRepository } from './tree-repository.interface.js';
|
||||
import { type UmbActionEventContext, UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
|
||||
import type { Observable } from '@umbraco-cms/backoffice/external/rxjs';
|
||||
import type { UmbPagedData } from '@umbraco-cms/backoffice/repository';
|
||||
import {
|
||||
@@ -12,6 +14,8 @@ import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controlle
|
||||
import { UmbExtensionApiInitializer } from '@umbraco-cms/backoffice/extension-api';
|
||||
import type { ProblemDetails } from '@umbraco-cms/backoffice/backend-api';
|
||||
import { UmbSelectionManager } from '@umbraco-cms/backoffice/utils';
|
||||
import type { UmbEntityActionEvent } from '@umbraco-cms/backoffice/entity-action';
|
||||
import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api';
|
||||
|
||||
// TODO: update interface
|
||||
export interface UmbTreeContext<TreeItemType extends UmbTreeItemModelBase> extends UmbBaseController {
|
||||
@@ -27,14 +31,16 @@ export class UmbTreeContextBase<TreeItemType extends UmbTreeItemModelBase>
|
||||
extends UmbBaseController
|
||||
implements UmbTreeContext<TreeItemType>
|
||||
{
|
||||
#treeRoot = new UmbObjectState<TreeItemType | undefined>(undefined);
|
||||
treeRoot = this.#treeRoot.asObservable();
|
||||
|
||||
public repository?: UmbTreeRepository<TreeItemType>;
|
||||
public selectableFilter?: (item: TreeItemType) => boolean = () => true;
|
||||
|
||||
public filter?: (item: TreeItemType) => boolean = () => true;
|
||||
|
||||
public readonly selection = new UmbSelectionManager(this._host);
|
||||
|
||||
#treeAlias?: string;
|
||||
#actionEventContext?: UmbActionEventContext;
|
||||
|
||||
#initResolver?: () => void;
|
||||
#initialized = false;
|
||||
@@ -46,6 +52,20 @@ export class UmbTreeContextBase<TreeItemType extends UmbTreeItemModelBase>
|
||||
constructor(host: UmbControllerHostElement) {
|
||||
super(host);
|
||||
this.provideContext('umbTreeContext', this);
|
||||
|
||||
this.consumeContext(UMB_ACTION_EVENT_CONTEXT, (instance) => {
|
||||
this.#actionEventContext = instance;
|
||||
this.#actionEventContext.removeEventListener(
|
||||
UmbReloadTreeItemChildrenRequestEntityActionEvent.TYPE,
|
||||
this.#onReloadRequest as EventListener,
|
||||
);
|
||||
this.#actionEventContext.addEventListener(
|
||||
UmbReloadTreeItemChildrenRequestEntityActionEvent.TYPE,
|
||||
this.#onReloadRequest as EventListener,
|
||||
);
|
||||
});
|
||||
|
||||
this.requestTreeRoot();
|
||||
}
|
||||
|
||||
// TODO: find a generic way to do this
|
||||
@@ -69,7 +89,13 @@ export class UmbTreeContextBase<TreeItemType extends UmbTreeItemModelBase>
|
||||
|
||||
public async requestTreeRoot() {
|
||||
await this.#init;
|
||||
return this.repository!.requestTreeRoot();
|
||||
const { data } = await this.repository!.requestTreeRoot();
|
||||
|
||||
if (data) {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
this.#treeRoot.setValue(data);
|
||||
}
|
||||
}
|
||||
|
||||
public async requestRootItems() {
|
||||
@@ -121,4 +147,23 @@ export class UmbTreeContextBase<TreeItemType extends UmbTreeItemModelBase>
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#onReloadRequest = (event: UmbEntityActionEvent) => {
|
||||
// Only handle root request here. Items are handled by the tree item context
|
||||
const treeRoot = this.#treeRoot.getValue();
|
||||
if (treeRoot === undefined) return;
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
if (event.getUnique() !== treeRoot.unique) return;
|
||||
if (event.getEntityType() !== treeRoot.entityType) return;
|
||||
this.requestRootItems();
|
||||
};
|
||||
|
||||
destroy(): void {
|
||||
this.#actionEventContext?.removeEventListener(
|
||||
UmbReloadTreeItemChildrenRequestEntityActionEvent.TYPE,
|
||||
this.#onReloadRequest as EventListener,
|
||||
);
|
||||
super.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,19 +79,21 @@ export class UmbTreeElement extends UmbLitElement {
|
||||
private _treeRoot?: UmbTreeItemModelBase;
|
||||
|
||||
#treeContext = new UmbTreeContextBase<UmbTreeItemModelBase>(this);
|
||||
|
||||
#rootItemsObserver?: UmbObserverController<Array<UmbTreeItemModelBase>>;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.#requestTreeRoot();
|
||||
this.#observeTreeRoot();
|
||||
}
|
||||
|
||||
async #requestTreeRoot() {
|
||||
if (!this.#treeContext?.requestTreeRoot) throw new Error('Tree does not support root');
|
||||
|
||||
const { data } = await this.#treeContext.requestTreeRoot();
|
||||
this._treeRoot = data;
|
||||
#observeTreeRoot() {
|
||||
this.observe(
|
||||
this.#treeContext.treeRoot,
|
||||
(treeRoot) => {
|
||||
this._treeRoot = treeRoot;
|
||||
},
|
||||
'umbTreeRootObserver',
|
||||
);
|
||||
}
|
||||
|
||||
async #observeRootItems() {
|
||||
|
||||
@@ -42,6 +42,7 @@ export class UmbDashboardTranslationDictionaryElement extends UmbLitElement {
|
||||
|
||||
const { data } = await this.#repo.list(0, 1000);
|
||||
this.#dictionaryItems = data?.items ?? [];
|
||||
|
||||
this.#setTableColumns();
|
||||
this.#setTableItems();
|
||||
}
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
export const UMB_DICTIONARY_ROOT_ENTITY_TYPE = 'dictionary-root';
|
||||
export const UMB_DICTIONARY_ENTITY_TYPE = 'dictionary-item';
|
||||
@@ -6,8 +6,8 @@ import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controlle
|
||||
export default class UmbCreateDictionaryEntityAction extends UmbEntityActionBase<UmbDictionaryRepository> {
|
||||
static styles = [UmbTextStyles];
|
||||
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string) {
|
||||
super(host, repositoryAlias, unique);
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string, entityType: string) {
|
||||
super(host, repositoryAlias, unique, entityType);
|
||||
}
|
||||
|
||||
async execute() {
|
||||
|
||||
@@ -14,8 +14,8 @@ export default class UmbExportDictionaryEntityAction extends UmbEntityActionBase
|
||||
|
||||
#modalContext?: UmbModalManagerContext;
|
||||
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string) {
|
||||
super(host, repositoryAlias, unique);
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string, entityType: string) {
|
||||
super(host, repositoryAlias, unique, entityType);
|
||||
|
||||
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => {
|
||||
this.#modalContext = instance;
|
||||
|
||||
@@ -17,8 +17,8 @@ export default class UmbImportDictionaryEntityAction extends UmbEntityActionBase
|
||||
#modalContext?: UmbModalManagerContext;
|
||||
#treeStore?: UmbDictionaryTreeStore;
|
||||
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string) {
|
||||
super(host, repositoryAlias, unique);
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string, entityType: string) {
|
||||
super(host, repositoryAlias, unique, entityType);
|
||||
|
||||
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => {
|
||||
this.#modalContext = instance;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { UMB_DICTIONARY_REPOSITORY_ALIAS } from '../repository/manifests.js';
|
||||
import { UMB_DICTIONARY_ENTITY_TYPE, UMB_DICTIONARY_ROOT_ENTITY_TYPE } from '../entities.js';
|
||||
import UmbReloadDictionaryEntityAction from './reload.action.js';
|
||||
import { UMB_DICTIONARY_ENTITY_TYPE, UMB_DICTIONARY_ROOT_ENTITY_TYPE } from '../entity.js';
|
||||
import UmbImportDictionaryEntityAction from './import/import.action.js';
|
||||
import UmbExportDictionaryEntityAction from './export/export.action.js';
|
||||
import UmbCreateDictionaryEntityAction from './create/create.action.js';
|
||||
@@ -60,19 +59,6 @@ const entityActions: Array<ManifestEntityAction> = [
|
||||
entityTypes: [UMB_DICTIONARY_ENTITY_TYPE, UMB_DICTIONARY_ROOT_ENTITY_TYPE],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'entityAction',
|
||||
alias: 'Umb.EntityAction.Dictionary.Reload',
|
||||
name: 'Reload Dictionary Entity Action',
|
||||
weight: 200,
|
||||
api: UmbReloadDictionaryEntityAction,
|
||||
meta: {
|
||||
icon: 'icon-refresh',
|
||||
label: 'Reload',
|
||||
repositoryAlias: UMB_DICTIONARY_REPOSITORY_ALIAS,
|
||||
entityTypes: [UMB_DICTIONARY_ENTITY_TYPE, UMB_DICTIONARY_ROOT_ENTITY_TYPE],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'entityAction',
|
||||
alias: 'Umb.EntityAction.Dictionary.Delete',
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
import type { UmbDictionaryRepository } from '../repository/dictionary.repository.js';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action';
|
||||
import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
|
||||
|
||||
export default class UmbReloadDictionaryEntityAction extends UmbEntityActionBase<UmbDictionaryRepository> {
|
||||
static styles = [UmbTextStyles];
|
||||
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string) {
|
||||
super(host, repositoryAlias, unique);
|
||||
}
|
||||
|
||||
async execute() {
|
||||
alert('refresh');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export const UMB_DICTIONARY_ROOT_ENTITY_TYPE = 'dictionary-root';
|
||||
export const UMB_DICTIONARY_ENTITY_TYPE = 'dictionary-item';
|
||||
|
||||
export type UmbDictionaryEntityType = typeof UMB_DICTIONARY_ENTITY_TYPE;
|
||||
export type UmbDictionaryRootEntityType = typeof UMB_DICTIONARY_ROOT_ENTITY_TYPE;
|
||||
@@ -1,4 +1,4 @@
|
||||
import { UMB_DICTIONARY_ENTITY_TYPE } from '../entities.js';
|
||||
import { UMB_DICTIONARY_ENTITY_TYPE } from '../entity.js';
|
||||
import { UMB_DICTIONARY_TREE_ALIAS } from '../tree/index.js';
|
||||
import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { UMB_DICTIONARY_ROOT_ENTITY_TYPE } from '../entities.js';
|
||||
import { UMB_DICTIONARY_ROOT_ENTITY_TYPE } from '../entity.js';
|
||||
import { UmbDictionaryTreeServerDataSource } from './dictionary-tree.server.data-source.js';
|
||||
import type { UmbDictionaryTreeItemModel, UmbDictionaryTreeRootModel } from './types.js';
|
||||
import { UMB_DICTIONARY_TREE_STORE_CONTEXT } from './dictionary-tree.store.js';
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { UMB_DICTIONARY_ENTITY_TYPE, UMB_DICTIONARY_ROOT_ENTITY_TYPE } from '../entities.js';
|
||||
import { UMB_DICTIONARY_ENTITY_TYPE, UMB_DICTIONARY_ROOT_ENTITY_TYPE } from '../entity.js';
|
||||
import { UmbDictionaryTreeRepository } from './dictionary-tree.repository.js';
|
||||
import { UmbDictionaryTreeStore } from './dictionary-tree.store.js';
|
||||
import { manifests as reloadTreeItemChildrenManifests } from './reload-tree-item-children/manifests.js';
|
||||
import type {
|
||||
ManifestRepository,
|
||||
ManifestTree,
|
||||
@@ -45,4 +46,4 @@ const treeItem: ManifestTreeItem = {
|
||||
},
|
||||
};
|
||||
|
||||
export const manifests = [treeRepository, treeStore, tree, treeItem];
|
||||
export const manifests = [treeRepository, treeStore, tree, treeItem, ...reloadTreeItemChildrenManifests];
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { UMB_DICTIONARY_ROOT_ENTITY_TYPE, UMB_DICTIONARY_ENTITY_TYPE } from '../../entity.js';
|
||||
import { UMB_DICTIONARY_REPOSITORY_ALIAS } from '../../repository/manifests.js';
|
||||
import { UmbReloadTreeItemChildrenEntityAction } from '@umbraco-cms/backoffice/tree';
|
||||
import { type ManifestEntityAction } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
export const manifests: Array<ManifestEntityAction> = [
|
||||
{
|
||||
type: 'entityAction',
|
||||
alias: 'Umb.EntityAction.Dictionary.Tree.ReloadTreeItemChildren',
|
||||
name: 'Reload Dictionary Tree Item Children Entity Action',
|
||||
weight: 10,
|
||||
api: UmbReloadTreeItemChildrenEntityAction,
|
||||
meta: {
|
||||
icon: 'icon-refresh',
|
||||
label: 'Reload children...',
|
||||
repositoryAlias: UMB_DICTIONARY_REPOSITORY_ALIAS,
|
||||
entityTypes: [UMB_DICTIONARY_ROOT_ENTITY_TYPE, UMB_DICTIONARY_ENTITY_TYPE],
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -1,4 +1,4 @@
|
||||
import { UMB_DICTIONARY_ROOT_ENTITY_TYPE } from './dictionary/entities.js';
|
||||
import { UMB_DICTIONARY_ROOT_ENTITY_TYPE } from './dictionary/entity.js';
|
||||
import type { ManifestDashboard, ManifestSection, ManifestTypes } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
const sectionAlias = 'Umb.Section.Dictionary';
|
||||
|
||||
@@ -8,8 +8,8 @@ import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
|
||||
export class UmbCreateDataTypeEntityAction extends UmbEntityActionBase<UmbDocumentTypeDetailRepository> {
|
||||
#modalManagerContext?: UmbModalManagerContext;
|
||||
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string) {
|
||||
super(host, repositoryAlias, unique);
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string, entityType: string) {
|
||||
super(host, repositoryAlias, unique, entityType);
|
||||
|
||||
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => {
|
||||
this.#modalManagerContext = instance;
|
||||
@@ -23,6 +23,7 @@ export class UmbCreateDataTypeEntityAction extends UmbEntityActionBase<UmbDocume
|
||||
this.#modalManagerContext?.open(UMB_DOCUMENT_TYPE_CREATE_OPTIONS_MODAL, {
|
||||
data: {
|
||||
parentUnique: this.unique,
|
||||
entityType: this.entityType,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ export class UmbDataTypeCreateOptionsModalElement extends UmbModalBaseElement<Um
|
||||
// @ts-ignore
|
||||
// TODO: allow null for entity actions. Some actions can be executed on the root item
|
||||
this.data.parentUnique,
|
||||
this.data.entityType,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { UmbModalToken } from '@umbraco-cms/backoffice/modal';
|
||||
|
||||
export interface UmbDocumentTypeCreateOptionsModalData {
|
||||
parentUnique: string | null;
|
||||
entityType: string;
|
||||
}
|
||||
|
||||
export const UMB_DOCUMENT_TYPE_CREATE_OPTIONS_MODAL = new UmbModalToken<UmbDocumentTypeCreateOptionsModalData>(
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
import { UmbDocumentTypeTreeRepository } from './document-type-tree.repository.js';
|
||||
import { UmbDocumentTypeTreeStore } from './document-type.tree.store.js';
|
||||
import { manifests as folderManifests } from './folder/manifests.js';
|
||||
import { manifests as reloadManifests } from './reload-tree-item-children/manifests.js';
|
||||
import type {
|
||||
ManifestRepository,
|
||||
ManifestTree,
|
||||
@@ -54,4 +55,4 @@ const treeItem: ManifestTreeItem = {
|
||||
},
|
||||
};
|
||||
|
||||
export const manifests = [treeRepository, treeStore, tree, treeItem, ...folderManifests];
|
||||
export const manifests = [treeRepository, treeStore, tree, treeItem, ...folderManifests, ...reloadManifests];
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import {
|
||||
UMB_DOCUMENT_TYPE_ROOT_ENTITY_TYPE,
|
||||
UMB_DOCUMENT_TYPE_ENTITY_TYPE,
|
||||
UMB_DOCUMENT_TYPE_FOLDER_ENTITY_TYPE,
|
||||
} from '../../entity.js';
|
||||
import { UMB_DOCUMENT_TYPE_DETAIL_REPOSITORY_ALIAS } from '../../repository/detail/manifests.js';
|
||||
import { UmbReloadTreeItemChildrenEntityAction } from '@umbraco-cms/backoffice/tree';
|
||||
import { type ManifestEntityAction } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
export const manifests: Array<ManifestEntityAction> = [
|
||||
{
|
||||
type: 'entityAction',
|
||||
alias: 'Umb.EntityAction.DocumentType.Tree.ReloadTreeItemChildren',
|
||||
name: 'Reload Document Type Tree Item Children Entity Action',
|
||||
weight: 10,
|
||||
api: UmbReloadTreeItemChildrenEntityAction,
|
||||
meta: {
|
||||
icon: 'icon-refresh',
|
||||
label: 'Reload children...',
|
||||
repositoryAlias: UMB_DOCUMENT_TYPE_DETAIL_REPOSITORY_ALIAS,
|
||||
entityTypes: [
|
||||
UMB_DOCUMENT_TYPE_ROOT_ENTITY_TYPE,
|
||||
UMB_DOCUMENT_TYPE_ENTITY_TYPE,
|
||||
UMB_DOCUMENT_TYPE_FOLDER_ENTITY_TYPE,
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -11,12 +11,12 @@ import type {
|
||||
PropertyTypeContainerModelBaseModel,
|
||||
} from '@umbraco-cms/backoffice/backend-api';
|
||||
import { UMB_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace';
|
||||
import type { UmbRoute , UmbRouterSlotChangeEvent, UmbRouterSlotInitEvent } from '@umbraco-cms/backoffice/router';
|
||||
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 { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import type { UmbSorterConfig} from '@umbraco-cms/backoffice/sorter';
|
||||
import type { UmbSorterConfig } from '@umbraco-cms/backoffice/sorter';
|
||||
import { UmbSorterController } from '@umbraco-cms/backoffice/sorter';
|
||||
|
||||
const SORTER_CONFIG: UmbSorterConfig<PropertyTypeContainerModelBaseModel> = {
|
||||
@@ -216,7 +216,7 @@ export class UmbDocumentTypeWorkspaceViewEditElement extends UmbLitElement imple
|
||||
if (!tabId) return;
|
||||
this._workspaceContext?.structure.removeContainer(null, tabId);
|
||||
this._tabsStructureHelper?.isOwnerContainer(tabId)
|
||||
? window.history.replaceState(null, '', this._routerPath + this._routes[0]?.path ?? '/root')
|
||||
? window.history.replaceState(null, '', this._routerPath + (this._routes[0]?.path ?? '/root'))
|
||||
: '';
|
||||
}
|
||||
async #addTab() {
|
||||
|
||||
@@ -3,8 +3,8 @@ import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action';
|
||||
import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
|
||||
|
||||
export class UmbCreateDocumentBlueprintEntityAction extends UmbEntityActionBase<UmbDocumentRepository> {
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string) {
|
||||
super(host, repositoryAlias, unique);
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string, entityType: string) {
|
||||
super(host, repositoryAlias, unique, entityType);
|
||||
}
|
||||
|
||||
async execute() {
|
||||
|
||||
@@ -11,8 +11,8 @@ import {
|
||||
export class UmbCreateDocumentEntityAction extends UmbEntityActionBase<UmbDocumentRepository> {
|
||||
#modalContext?: UmbModalManagerContext;
|
||||
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string) {
|
||||
super(host, repositoryAlias, unique);
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string, entityType: string) {
|
||||
super(host, repositoryAlias, unique, entityType);
|
||||
|
||||
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => {
|
||||
this.#modalContext = instance;
|
||||
|
||||
@@ -3,8 +3,8 @@ import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action';
|
||||
import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
|
||||
|
||||
export class UmbDocumentCultureAndHostnamesEntityAction extends UmbEntityActionBase<UmbDocumentRepository> {
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string) {
|
||||
super(host, repositoryAlias, unique);
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string, entityType: string) {
|
||||
super(host, repositoryAlias, unique, entityType);
|
||||
}
|
||||
|
||||
async execute() {
|
||||
|
||||
@@ -11,8 +11,8 @@ import {
|
||||
export class UmbDocumentPermissionsEntityAction extends UmbEntityActionBase<UmbDocumentRepository> {
|
||||
#modalManagerContext?: UmbModalManagerContext;
|
||||
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string) {
|
||||
super(host, repositoryAlias, unique);
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string, entityType: string) {
|
||||
super(host, repositoryAlias, unique, entityType);
|
||||
|
||||
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => {
|
||||
this.#modalManagerContext = instance;
|
||||
|
||||
@@ -3,8 +3,8 @@ import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action';
|
||||
import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
|
||||
|
||||
export class UmbDocumentPublicAccessEntityAction extends UmbEntityActionBase<UmbDocumentRepository> {
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string) {
|
||||
super(host, repositoryAlias, unique);
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string, entityType: string) {
|
||||
super(host, repositoryAlias, unique, entityType);
|
||||
}
|
||||
|
||||
async execute() {
|
||||
|
||||
@@ -3,8 +3,8 @@ import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action';
|
||||
import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
|
||||
|
||||
export class UmbPublishDocumentEntityAction extends UmbEntityActionBase<UmbDocumentRepository> {
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string) {
|
||||
super(host, repositoryAlias, unique);
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string, entityType: string) {
|
||||
super(host, repositoryAlias, unique, entityType);
|
||||
}
|
||||
|
||||
async execute() {
|
||||
|
||||
@@ -3,8 +3,8 @@ import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action';
|
||||
import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
|
||||
|
||||
export class UmbRollbackDocumentEntityAction extends UmbEntityActionBase<UmbDocumentRepository> {
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string) {
|
||||
super(host, repositoryAlias, unique);
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string, entityType: string) {
|
||||
super(host, repositoryAlias, unique, entityType);
|
||||
}
|
||||
|
||||
async execute() {
|
||||
|
||||
@@ -3,8 +3,8 @@ import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action';
|
||||
import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
|
||||
|
||||
export class UmbUnpublishDocumentEntityAction extends UmbEntityActionBase<UmbDocumentRepository> {
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string) {
|
||||
super(host, repositoryAlias, unique);
|
||||
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string, entityType: string) {
|
||||
super(host, repositoryAlias, unique, entityType);
|
||||
}
|
||||
|
||||
async execute() {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user