Merge remote-tracking branch 'origin/main' into docs/add-draft-concept-docs

# Conflicts:
#	src/stories/umb-element.mdx
This commit is contained in:
Niels Lyngsø
2023-03-09 09:51:45 +01:00
241 changed files with 16984 additions and 3172 deletions

View File

@@ -2,7 +2,6 @@
"ignorePatterns": ["vite.*.ts"], "ignorePatterns": ["vite.*.ts"],
"root": true, "root": true,
"plugins": ["eslint-plugin-local-rules"], "plugins": ["eslint-plugin-local-rules"],
"extends": ["eslint:recommended", "plugin:import/recommended", "prettier"],
"overrides": [ "overrides": [
{ {
"files": ["**/*.ts"], "files": ["**/*.ts"],
@@ -49,6 +48,27 @@
} }
} }
} }
},
{
"files": ["**/*.js"],
"extends": ["eslint:recommended", "plugin:import/recommended", "prettier"],
"env": {
"node": true,
"browser": true,
"es6": true
},
"parserOptions": {
"sourceType": "module",
"ecmaVersion": "latest"
},
"settings": {
"import/resolver": {
"node": {
"extensions": [".js"],
"moduleDirectory": ["node_modules"]
}
}
}
} }
] ]
} }

View File

@@ -11,6 +11,7 @@ node_modules
dist dist
dist-ssr dist-ssr
types types
*.tsbuildinfo
*.local *.local
*.tgz *.tgz

View File

@@ -19,7 +19,7 @@ import { UmbIconStore } from '../libs/store/icon/icon.store';
import { onUnhandledRequest } from '../src/core/mocks/browser'; import { onUnhandledRequest } from '../src/core/mocks/browser';
import { handlers } from '../src/core/mocks/browser-handlers'; import { handlers } from '../src/core/mocks/browser-handlers';
import { LitElement } from 'lit'; import { LitElement } from 'lit';
import { UMB_MODAL_SERVICE_CONTEXT_TOKEN, UmbModalService } from '../src/core/modal'; import { UMB_MODAL_CONTEXT_TOKEN, UmbModalContext } from '../src/core/modal';
// TODO: Fix storybook manifest registrations. // TODO: Fix storybook manifest registrations.
@@ -65,11 +65,11 @@ const documentTypeStoreProvider = (story) => html`
<umb-controller-host-test .create=${(host) => new UmbDocumentTypeStore(host)}>${story()}</umb-controller-host-test> <umb-controller-host-test .create=${(host) => new UmbDocumentTypeStore(host)}>${story()}</umb-controller-host-test>
`; `;
const modalServiceProvider = (story) => html` const modalContextProvider = (story) => html`
<umb-context-provider <umb-context-provider
style="display: block; padding: 32px;" style="display: block; padding: 32px;"
key="${UMB_MODAL_SERVICE_CONTEXT_TOKEN}" key="${UMB_MODAL_CONTEXT_TOKEN}"
.value=${new UmbModalService()}> .value=${new UmbModalContext()}>
${story()} ${story()}
<umb-backoffice-modal-container></umb-backoffice-modal-container> <umb-backoffice-modal-container></umb-backoffice-modal-container>
</umb-context-provider> </umb-context-provider>
@@ -84,7 +84,7 @@ export const decorators = [
storybookProvider, storybookProvider,
dataTypeStoreProvider, dataTypeStoreProvider,
documentTypeStoreProvider, documentTypeStoreProvider,
modalServiceProvider, modalContextProvider,
]; ];
export const parameters = { export const parameters = {
@@ -129,6 +129,19 @@ export const parameters = {
global: handlers, global: handlers,
}, },
}, },
backgrounds: {
default: 'Greyish',
values: [
{
name: 'Greyish',
value: '#F3F3F5',
},
{
name: 'White',
value: '#ffffff',
},
],
},
}; };
setCustomElements(customElementManifests); setCustomElements(customElementManifests);

View File

@@ -2,127 +2,122 @@
/* tslint:disable */ /* tslint:disable */
/* eslint-disable */ /* eslint-disable */
export class CancelError extends Error { export class CancelError extends Error {
constructor(message: string) {
super(message);
this.name = 'CancelError';
}
constructor(message: string) { public get isCancelled(): boolean {
super(message); return true;
this.name = 'CancelError'; }
}
public get isCancelled(): boolean {
return true;
}
} }
export interface OnCancel { export interface OnCancel {
readonly isResolved: boolean; readonly isResolved: boolean;
readonly isRejected: boolean; readonly isRejected: boolean;
readonly isCancelled: boolean; readonly isCancelled: boolean;
(cancelHandler: () => void): void; (cancelHandler: () => void): void;
} }
export class CancelablePromise<T> implements Promise<T> { export class CancelablePromise<T> implements Promise<T> {
readonly [Symbol.toStringTag]!: string; readonly [Symbol.toStringTag]!: string;
private _isResolved: boolean; private _isResolved: boolean;
private _isRejected: boolean; private _isRejected: boolean;
private _isCancelled: boolean; private _isCancelled: boolean;
private readonly _cancelHandlers: (() => void)[]; private readonly _cancelHandlers: (() => void)[];
private readonly _promise: Promise<T>; private readonly _promise: Promise<T>;
private _resolve?: (value: T | PromiseLike<T>) => void; private _resolve?: (value: T | PromiseLike<T>) => void;
private _reject?: (reason?: any) => void; private _reject?: (reason?: any) => void;
constructor( constructor(
executor: ( executor: (resolve: (value: T | PromiseLike<T>) => void, reject: (reason?: any) => void, onCancel: OnCancel) => void
resolve: (value: T | PromiseLike<T>) => void, ) {
reject: (reason?: any) => void, this._isResolved = false;
onCancel: OnCancel this._isRejected = false;
) => void this._isCancelled = false;
) { this._cancelHandlers = [];
this._isResolved = false; this._promise = new Promise<T>((resolve, reject) => {
this._isRejected = false; this._resolve = resolve;
this._isCancelled = false; this._reject = reject;
this._cancelHandlers = [];
this._promise = new Promise<T>((resolve, reject) => {
this._resolve = resolve;
this._reject = reject;
const onResolve = (value: T | PromiseLike<T>): void => { const onResolve = (value: T | PromiseLike<T>): void => {
if (this._isResolved || this._isRejected || this._isCancelled) { if (this._isResolved || this._isRejected || this._isCancelled) {
return; return;
} }
this._isResolved = true; this._isResolved = true;
this._resolve?.(value); this._resolve?.(value);
}; };
const onReject = (reason?: any): void => { const onReject = (reason?: any): void => {
if (this._isResolved || this._isRejected || this._isCancelled) { if (this._isResolved || this._isRejected || this._isCancelled) {
return; return;
} }
this._isRejected = true; this._isRejected = true;
this._reject?.(reason); this._reject?.(reason);
}; };
const onCancel = (cancelHandler: () => void): void => { const onCancel = (cancelHandler: () => void): void => {
if (this._isResolved || this._isRejected || this._isCancelled) { if (this._isResolved || this._isRejected || this._isCancelled) {
return; return;
} }
this._cancelHandlers.push(cancelHandler); this._cancelHandlers.push(cancelHandler);
}; };
Object.defineProperty(onCancel, 'isResolved', { Object.defineProperty(onCancel, 'isResolved', {
get: (): boolean => this._isResolved, get: (): boolean => this._isResolved,
}); });
Object.defineProperty(onCancel, 'isRejected', { Object.defineProperty(onCancel, 'isRejected', {
get: (): boolean => this._isRejected, get: (): boolean => this._isRejected,
}); });
Object.defineProperty(onCancel, 'isCancelled', { Object.defineProperty(onCancel, 'isCancelled', {
get: (): boolean => this._isCancelled, get: (): boolean => this._isCancelled,
}); });
return executor(onResolve, onReject, onCancel as OnCancel); return executor(onResolve, onReject, onCancel as OnCancel);
}); });
} }
public then<TResult1 = T, TResult2 = never>( public then<TResult1 = T, TResult2 = never>(
onFulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null, onFulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
onRejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null onRejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null
): Promise<TResult1 | TResult2> { ): Promise<TResult1 | TResult2> {
return this._promise.then(onFulfilled, onRejected); return this._promise.then(onFulfilled, onRejected);
} }
public catch<TResult = never>( public catch<TResult = never>(
onRejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null onRejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null
): Promise<T | TResult> { ): Promise<T | TResult> {
return this._promise.catch(onRejected); return this._promise.catch(onRejected);
} }
public finally(onFinally?: (() => void) | null): Promise<T> { public finally(onFinally?: (() => void) | null): Promise<T> {
return this._promise.finally(onFinally); return this._promise.finally(onFinally);
} }
public cancel(): void { public cancel(): void {
if (this._isResolved || this._isRejected || this._isCancelled) { if (this._isResolved || this._isRejected || this._isCancelled) {
return; return;
} }
this._isCancelled = true; this._isCancelled = true;
if (this._cancelHandlers.length) { if (this._cancelHandlers.length) {
try { try {
for (const cancelHandler of this._cancelHandlers) { for (const cancelHandler of this._cancelHandlers) {
cancelHandler(); cancelHandler();
} }
} catch (error) { } catch (error) {
console.warn('Cancellation threw an error', error); console.warn('Cancellation threw an error', error);
return; return;
} }
} }
this._cancelHandlers.length = 0; this._cancelHandlers.length = 0;
this._reject?.(new CancelError('Request aborted')); this._reject?.(new CancelError('Request aborted'));
} }
public get isCancelled(): boolean { public get isCancelled(): boolean {
return this._isCancelled; return this._isCancelled;
} }
} }

View File

@@ -6,6 +6,10 @@ export { CancelablePromise, CancelError } from './core/CancelablePromise';
export { OpenAPI } from './core/OpenAPI'; export { OpenAPI } from './core/OpenAPI';
export type { OpenAPIConfig } from './core/OpenAPI'; export type { OpenAPIConfig } from './core/OpenAPI';
export type { AuditLogBaseModel } from './models/AuditLogBaseModel';
export type { AuditLogResponseModel } from './models/AuditLogResponseModel';
export type { AuditLogWithUsernameResponseModel } from './models/AuditLogWithUsernameResponseModel';
export { AuditTypeModel } from './models/AuditTypeModel';
export type { ConsentLevelModel } from './models/ConsentLevelModel'; export type { ConsentLevelModel } from './models/ConsentLevelModel';
export { ContentStateModel } from './models/ContentStateModel'; export { ContentStateModel } from './models/ContentStateModel';
export type { ContentTreeItemModel } from './models/ContentTreeItemModel'; export type { ContentTreeItemModel } from './models/ContentTreeItemModel';
@@ -74,6 +78,7 @@ export type { LanguageModel } from './models/LanguageModel';
export type { LanguageModelBaseModel } from './models/LanguageModelBaseModel'; export type { LanguageModelBaseModel } from './models/LanguageModelBaseModel';
export type { LanguageUpdateModel } from './models/LanguageUpdateModel'; export type { LanguageUpdateModel } from './models/LanguageUpdateModel';
export type { LoggerModel } from './models/LoggerModel'; export type { LoggerModel } from './models/LoggerModel';
export type { LogLevelCountsModel } from './models/LogLevelCountsModel';
export { LogLevelModel } from './models/LogLevelModel'; export { LogLevelModel } from './models/LogLevelModel';
export type { LogMessageModel } from './models/LogMessageModel'; export type { LogMessageModel } from './models/LogMessageModel';
export type { LogMessagePropertyModel } from './models/LogMessagePropertyModel'; export type { LogMessagePropertyModel } from './models/LogMessagePropertyModel';
@@ -84,7 +89,14 @@ export type { OkResultModel } from './models/OkResultModel';
export { OperatorModel } from './models/OperatorModel'; export { OperatorModel } from './models/OperatorModel';
export type { OutOfDateStatusModel } from './models/OutOfDateStatusModel'; export type { OutOfDateStatusModel } from './models/OutOfDateStatusModel';
export { OutOfDateTypeModel } from './models/OutOfDateTypeModel'; export { OutOfDateTypeModel } from './models/OutOfDateTypeModel';
export type { PackageCreateModel } from './models/PackageCreateModel';
export type { PackageDefinitionModel } from './models/PackageDefinitionModel';
export type { PackageManifestModel } from './models/PackageManifestModel';
export type { PackageMigrationStatusModel } from './models/PackageMigrationStatusModel'; export type { PackageMigrationStatusModel } from './models/PackageMigrationStatusModel';
export type { PackageModelBaseModel } from './models/PackageModelBaseModel';
export type { PackageUpdateModel } from './models/PackageUpdateModel';
export type { PagedAuditLogResponseModel } from './models/PagedAuditLogResponseModel';
export type { PagedAuditLogWithUsernameResponseModel } from './models/PagedAuditLogWithUsernameResponseModel';
export type { PagedContentTreeItemModel } from './models/PagedContentTreeItemModel'; export type { PagedContentTreeItemModel } from './models/PagedContentTreeItemModel';
export type { PagedCultureModel } from './models/PagedCultureModel'; export type { PagedCultureModel } from './models/PagedCultureModel';
export type { PagedDictionaryOverviewModel } from './models/PagedDictionaryOverviewModel'; export type { PagedDictionaryOverviewModel } from './models/PagedDictionaryOverviewModel';
@@ -101,6 +113,7 @@ export type { PagedLanguageModel } from './models/PagedLanguageModel';
export type { PagedLoggerModel } from './models/PagedLoggerModel'; export type { PagedLoggerModel } from './models/PagedLoggerModel';
export type { PagedLogMessageModel } from './models/PagedLogMessageModel'; export type { PagedLogMessageModel } from './models/PagedLogMessageModel';
export type { PagedLogTemplateModel } from './models/PagedLogTemplateModel'; export type { PagedLogTemplateModel } from './models/PagedLogTemplateModel';
export type { PagedPackageDefinitionModel } from './models/PagedPackageDefinitionModel';
export type { PagedPackageMigrationStatusModel } from './models/PagedPackageMigrationStatusModel'; export type { PagedPackageMigrationStatusModel } from './models/PagedPackageMigrationStatusModel';
export type { PagedRecycleBinItemModel } from './models/PagedRecycleBinItemModel'; export type { PagedRecycleBinItemModel } from './models/PagedRecycleBinItemModel';
export type { PagedRedirectUrlModel } from './models/PagedRedirectUrlModel'; export type { PagedRedirectUrlModel } from './models/PagedRedirectUrlModel';
@@ -157,6 +170,7 @@ export type { ValueViewModelBaseModel } from './models/ValueViewModelBaseModel';
export type { VariantViewModelBaseModel } from './models/VariantViewModelBaseModel'; export type { VariantViewModelBaseModel } from './models/VariantViewModelBaseModel';
export type { VersionModel } from './models/VersionModel'; export type { VersionModel } from './models/VersionModel';
export { AuditLogResource } from './services/AuditLogResource';
export { CultureResource } from './services/CultureResource'; export { CultureResource } from './services/CultureResource';
export { DataTypeResource } from './services/DataTypeResource'; export { DataTypeResource } from './services/DataTypeResource';
export { DictionaryResource } from './services/DictionaryResource'; export { DictionaryResource } from './services/DictionaryResource';

View File

@@ -0,0 +1,16 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { AuditTypeModel } from './AuditTypeModel';
export type AuditLogBaseModel = {
userKey?: string;
entityKey?: string | null;
timestamp?: string;
logType?: AuditTypeModel;
entityType?: string | null;
comment?: string | null;
parameters?: string | null;
};

View File

@@ -0,0 +1,8 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { AuditLogBaseModel } from './AuditLogBaseModel';
export type AuditLogResponseModel = AuditLogBaseModel;

View File

@@ -0,0 +1,11 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { AuditLogBaseModel } from './AuditLogBaseModel';
export type AuditLogWithUsernameResponseModel = (AuditLogBaseModel & {
userName?: string | null;
userAvatars?: Array<string> | null;
});

View File

@@ -0,0 +1,30 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export enum AuditTypeModel {
NEW = 'New',
SAVE = 'Save',
SAVE_VARIANT = 'SaveVariant',
OPEN = 'Open',
DELETE = 'Delete',
PUBLISH = 'Publish',
PUBLISH_VARIANT = 'PublishVariant',
SEND_TO_PUBLISH = 'SendToPublish',
SEND_TO_PUBLISH_VARIANT = 'SendToPublishVariant',
UNPUBLISH = 'Unpublish',
UNPUBLISH_VARIANT = 'UnpublishVariant',
MOVE = 'Move',
COPY = 'Copy',
ASSIGN_DOMAIN = 'AssignDomain',
PUBLIC_ACCESS = 'PublicAccess',
SORT = 'Sort',
NOTIFY = 'Notify',
SYSTEM = 'System',
ROLL_BACK = 'RollBack',
PACKAGER_INSTALL = 'PackagerInstall',
PACKAGER_UNINSTALL = 'PackagerUninstall',
CUSTOM = 'Custom',
CONTENT_VERSION_PREVENT_CLEANUP = 'ContentVersionPreventCleanup',
CONTENT_VERSION_ENABLE_CLEANUP = 'ContentVersionEnableCleanup',
}

View File

@@ -0,0 +1,12 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type LogLevelCountsModel = {
information?: number;
debug?: number;
warning?: number;
error?: number;
fatal?: number;
};

View File

@@ -0,0 +1,10 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type PackageManifestModel = {
name?: string;
version?: string | null;
extensions?: Array<any>;
};

View File

@@ -0,0 +1,11 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { AuditLogResponseModel } from './AuditLogResponseModel';
export type PagedAuditLogResponseModel = {
total: number;
items: Array<AuditLogResponseModel>;
};

View File

@@ -0,0 +1,11 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { AuditLogWithUsernameResponseModel } from './AuditLogWithUsernameResponseModel';
export type PagedAuditLogWithUsernameResponseModel = {
total: number;
items: Array<AuditLogWithUsernameResponseModel>;
};

View File

@@ -0,0 +1,103 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import type { AuditTypeModel } from '../models/AuditTypeModel';
import type { DirectionModel } from '../models/DirectionModel';
import type { PagedAuditLogResponseModel } from '../models/PagedAuditLogResponseModel';
import type { PagedAuditLogWithUsernameResponseModel } from '../models/PagedAuditLogWithUsernameResponseModel';
import type { CancelablePromise } from '../core/CancelablePromise';
import { OpenAPI } from '../core/OpenAPI';
import { request as __request } from '../core/request';
export class AuditLogResource {
/**
* @returns PagedAuditLogWithUsernameResponseModel Success
* @throws ApiError
*/
public static getAuditLog({
orderDirection,
sinceDate,
skip,
take = 100,
}: {
orderDirection?: DirectionModel,
sinceDate?: string,
skip?: number,
take?: number,
}): CancelablePromise<PagedAuditLogWithUsernameResponseModel> {
return __request(OpenAPI, {
method: 'GET',
url: '/umbraco/management/api/v1/audit-log',
query: {
'orderDirection': orderDirection,
'sinceDate': sinceDate,
'skip': skip,
'take': take,
},
});
}
/**
* @returns PagedAuditLogResponseModel Success
* @throws ApiError
*/
public static getAuditLogByKey({
key,
orderDirection,
sinceDate,
skip,
take = 100,
}: {
key: string,
orderDirection?: DirectionModel,
sinceDate?: string,
skip?: number,
take?: number,
}): CancelablePromise<PagedAuditLogResponseModel> {
return __request(OpenAPI, {
method: 'GET',
url: '/umbraco/management/api/v1/audit-log/{key}',
path: {
'key': key,
},
query: {
'orderDirection': orderDirection,
'sinceDate': sinceDate,
'skip': skip,
'take': take,
},
});
}
/**
* @returns PagedAuditLogResponseModel Success
* @throws ApiError
*/
public static getAuditLogTypeByLogType({
logType,
sinceDate,
skip,
take = 100,
}: {
logType: AuditTypeModel,
sinceDate?: string,
skip?: number,
take?: number,
}): CancelablePromise<PagedAuditLogResponseModel> {
return __request(OpenAPI, {
method: 'GET',
url: '/umbraco/management/api/v1/audit-log/type/{logType}',
path: {
'logType': logType,
},
query: {
'sinceDate': sinceDate,
'skip': skip,
'take': take,
},
});
}
}

View File

@@ -2,6 +2,7 @@
/* tslint:disable */ /* tslint:disable */
/* eslint-disable */ /* eslint-disable */
import type { DirectionModel } from '../models/DirectionModel'; import type { DirectionModel } from '../models/DirectionModel';
import type { LogLevelCountsModel } from '../models/LogLevelCountsModel';
import type { LogLevelModel } from '../models/LogLevelModel'; import type { LogLevelModel } from '../models/LogLevelModel';
import type { PagedLoggerModel } from '../models/PagedLoggerModel'; import type { PagedLoggerModel } from '../models/PagedLoggerModel';
import type { PagedLogMessageModel } from '../models/PagedLogMessageModel'; import type { PagedLogMessageModel } from '../models/PagedLogMessageModel';
@@ -46,7 +47,7 @@ export class LogViewerResource {
}: { }: {
startDate?: string, startDate?: string,
endDate?: string, endDate?: string,
}): CancelablePromise<any> { }): CancelablePromise<LogLevelCountsModel> {
return __request(OpenAPI, { return __request(OpenAPI, {
method: 'GET', method: 'GET',
url: '/umbraco/management/api/v1/log-viewer/level-count', url: '/umbraco/management/api/v1/log-viewer/level-count',

View File

@@ -3,6 +3,7 @@
/* eslint-disable */ /* eslint-disable */
import type { PackageCreateModel } from '../models/PackageCreateModel'; import type { PackageCreateModel } from '../models/PackageCreateModel';
import type { PackageDefinitionModel } from '../models/PackageDefinitionModel'; import type { PackageDefinitionModel } from '../models/PackageDefinitionModel';
import type { PackageManifestModel } from '../models/PackageManifestModel';
import type { PackageUpdateModel } from '../models/PackageUpdateModel'; import type { PackageUpdateModel } from '../models/PackageUpdateModel';
import type { PagedPackageDefinitionModel } from '../models/PagedPackageDefinitionModel'; import type { PagedPackageDefinitionModel } from '../models/PagedPackageDefinitionModel';
import type { PagedPackageMigrationStatusModel } from '../models/PagedPackageMigrationStatusModel'; import type { PagedPackageMigrationStatusModel } from '../models/PagedPackageMigrationStatusModel';
@@ -166,6 +167,17 @@ export class PackageResource {
}); });
} }
/**
* @returns any Success
* @throws ApiError
*/
public static getPackageManifest(): CancelablePromise<Array<PackageManifestModel>> {
return __request(OpenAPI, {
method: 'GET',
url: '/umbraco/management/api/v1/package/manifest',
});
}
/** /**
* @returns PagedPackageMigrationStatusModel Success * @returns PagedPackageMigrationStatusModel Success
* @throws ApiError * @throws ApiError

View File

@@ -20,4 +20,21 @@ export class ProfilingResource {
}); });
} }
/**
* @returns any Success
* @throws ApiError
*/
public static putProfilingStatus({
requestBody,
}: {
requestBody?: ProfilingStatusModel,
}): CancelablePromise<any> {
return __request(OpenAPI, {
method: 'PUT',
url: '/umbraco/management/api/v1/profiling/status',
body: requestBody,
mediaType: 'application/json',
});
}
} }

View File

@@ -46,7 +46,7 @@ export class TrackedReferenceResource {
parentKey, parentKey,
skip, skip,
take, take,
filterMustBeIsDependency, filterMustBeIsDependency = true,
}: { }: {
parentKey: string, parentKey: string,
skip?: number, skip?: number,

View File

@@ -1,4 +1,15 @@
export class UmbContextToken<T = unknown> { export class UmbContextToken<T = unknown> {
/**
* Get the type of the token
*
* @public
* @type {T}
* @memberOf UmbContextToken
* @example `typeof MyToken.TYPE`
* @returns undefined
*/
readonly TYPE: T = undefined as never;
/** /**
* @param alias Unique identifier for the token, * @param alias Unique identifier for the token,
* @param _desc Description for the token, * @param _desc Description for the token,
@@ -7,13 +18,6 @@ export class UmbContextToken<T = unknown> {
*/ */
constructor(protected alias: string, protected _desc?: string) {} constructor(protected alias: string, protected _desc?: string) {}
/**
* @internal
*/
get multi(): UmbContextToken<Array<T>> {
return this as UmbContextToken<Array<T>>;
}
/** /**
* This method must always return the unique alias of the token since that * This method must always return the unique alias of the token since that
* will be used to look up the token in the injector. * will be used to look up the token in the injector.

View File

@@ -0,0 +1 @@
export * from './element.mixin';

View File

@@ -1,4 +1,5 @@
import config from '../../utils/rollup.config.js'; import config from '../../utils/rollup.config.js';
export default { export default {
...config, ...config,
input: 'index.out.ts'
}; };

View File

@@ -1,30 +1,30 @@
import { UmbEntityActionBase } from '@umbraco-cms/entity-action'; import { UmbEntityActionBase } from '@umbraco-cms/entity-action';
import { UmbContextConsumerController } from '@umbraco-cms/context-api'; import { UmbContextConsumerController } from '@umbraco-cms/context-api';
import { UmbControllerHostInterface } from '@umbraco-cms/controller'; import { UmbControllerHostInterface } from '@umbraco-cms/controller';
import { UmbModalService, UMB_MODAL_SERVICE_CONTEXT_TOKEN } from '@umbraco-cms/modal'; import { UmbModalContext, UMB_MODAL_CONTEXT_TOKEN } from '@umbraco-cms/modal';
export class UmbDeleteEntityAction< export class UmbDeleteEntityAction<
T extends { delete(unique: string): Promise<void>; requestItems(uniques: Array<string>): any } T extends { delete(unique: string): Promise<void>; requestItems(uniques: Array<string>): any }
> extends UmbEntityActionBase<T> { > extends UmbEntityActionBase<T> {
#modalService?: UmbModalService; #modalContext?: UmbModalContext;
constructor(host: UmbControllerHostInterface, repositoryAlias: string, unique: string) { constructor(host: UmbControllerHostInterface, repositoryAlias: string, unique: string) {
super(host, repositoryAlias, unique); super(host, repositoryAlias, unique);
new UmbContextConsumerController(this.host, UMB_MODAL_SERVICE_CONTEXT_TOKEN, (instance) => { new UmbContextConsumerController(this.host, UMB_MODAL_CONTEXT_TOKEN, (instance) => {
this.#modalService = instance; this.#modalContext = instance;
}); });
} }
async execute() { async execute() {
if (!this.repository || !this.#modalService) return; if (!this.repository || !this.#modalContext) return;
const { data } = await this.repository.requestItems([this.unique]); const { data } = await this.repository.requestItems([this.unique]);
if (data) { if (data) {
const item = data[0]; const item = data[0];
const modalHandler = this.#modalService.confirm({ const modalHandler = this.#modalContext.confirm({
headline: `Delete ${item.name}`, headline: `Delete ${item.name}`,
content: 'Are you sure you want to delete this item?', content: 'Are you sure you want to delete this item?',
color: 'danger', color: 'danger',

View File

@@ -1,18 +1,18 @@
import { UmbEntityActionBase } from '@umbraco-cms/entity-action'; import { UmbEntityActionBase } from '@umbraco-cms/entity-action';
import { UmbContextConsumerController } from '@umbraco-cms/context-api'; import { UmbContextConsumerController } from '@umbraco-cms/context-api';
import { UmbControllerHostInterface } from '@umbraco-cms/controller'; import { UmbControllerHostInterface } from '@umbraco-cms/controller';
import { UmbModalService, UMB_MODAL_SERVICE_CONTEXT_TOKEN } from '@umbraco-cms/modal'; import { UmbModalContext, UMB_MODAL_CONTEXT_TOKEN } from '@umbraco-cms/modal';
export class UmbTrashEntityAction< export class UmbTrashEntityAction<
T extends { trash(unique: Array<string>): Promise<void>; requestTreeItems(uniques: Array<string>): any } T extends { trash(unique: Array<string>): Promise<void>; requestTreeItems(uniques: Array<string>): any }
> extends UmbEntityActionBase<T> { > extends UmbEntityActionBase<T> {
#modalService?: UmbModalService; #modalContext?: UmbModalContext;
constructor(host: UmbControllerHostInterface, repositoryAlias: string, unique: string) { constructor(host: UmbControllerHostInterface, repositoryAlias: string, unique: string) {
super(host, repositoryAlias, unique); super(host, repositoryAlias, unique);
new UmbContextConsumerController(this.host, UMB_MODAL_SERVICE_CONTEXT_TOKEN, (instance) => { new UmbContextConsumerController(this.host, UMB_MODAL_CONTEXT_TOKEN, (instance) => {
this.#modalService = instance; this.#modalContext = instance;
}); });
} }
@@ -24,7 +24,7 @@ export class UmbTrashEntityAction<
if (data) { if (data) {
const item = data[0]; const item = data[0];
const modalHandler = this.#modalService?.confirm({ const modalHandler = this.#modalContext?.confirm({
headline: `Trash ${item.name}`, headline: `Trash ${item.name}`,
content: 'Are you sure you want to move this item to the recycle bin?', content: 'Are you sure you want to move this item to the recycle bin?',
color: 'danger', color: 'danger',

View File

@@ -22,7 +22,7 @@ export async function createExtensionElement(manifest: ManifestElement): Promise
console.error('-- Extension did not succeed creating an element, missing a default export of the served JavaScript file', manifest); console.error('-- Extension did not succeed creating an element, missing a default export of the served JavaScript file', manifest);
// If some JS was loaded and it did not at least contain a default export, then we are safe to assume that it executed as a module and does not need to be returned // If some JS was loaded and it did not at least contain a default export, then we are safe to assume that it executed its side effects and does not need to be returned
return undefined; return undefined;
} }

View File

@@ -0,0 +1,8 @@
import type { UmbEntrypointModule } from "./umb-lifecycle.interface";
/**
* Validate if an ESModule exports a known init function called 'onInit'
*/
export function hasInitExport(obj: unknown): obj is Pick<UmbEntrypointModule, 'onInit'> {
return obj !== null && typeof obj === 'object' && 'onInit' in obj;
}

View File

@@ -3,6 +3,7 @@ import { UmbExtensionRegistry } from './registry/extension.registry';
export * from './registry/extension.registry'; export * from './registry/extension.registry';
export * from './create-extension-element.function'; export * from './create-extension-element.function';
export * from './has-default-export.function'; export * from './has-default-export.function';
export * from './has-init-export.function';
export * from './is-manifest-element-name-type.function'; export * from './is-manifest-element-name-type.function';
export * from './is-manifest-elementable-type.function'; export * from './is-manifest-elementable-type.function';
export * from './is-manifest-js-type.function'; export * from './is-manifest-js-type.function';
@@ -10,5 +11,6 @@ export * from './is-manifest-loader-type.function';
export * from './load-extension.function'; export * from './load-extension.function';
export * from './create-extension-element-or-fallback.function'; export * from './create-extension-element-or-fallback.function';
export * from './create-extension-class.function'; export * from './create-extension-class.function';
export * from './umb-lifecycle.interface';
export const umbExtensionsRegistry = new UmbExtensionRegistry(); export const umbExtensionsRegistry = new UmbExtensionRegistry();

View File

@@ -14,10 +14,10 @@ export async function loadExtension(manifest: ManifestElement): Promise<object |
if (isManifestJSType(manifest) && manifest.js) { if (isManifestJSType(manifest) && manifest.js) {
return await import(/* @vite-ignore */ manifest.js); return await import(/* @vite-ignore */ manifest.js);
} }
} catch { } catch (err: any) {
console.warn('-- Extension failed to load script', manifest); console.warn('-- Extension failed to load script', manifest, err);
return Promise.resolve(null); return Promise.resolve(null);
} }
return Promise.resolve(null); return Promise.resolve(null);
} }

View File

@@ -1,7 +1,9 @@
import { BehaviorSubject, map, Observable } from 'rxjs'; import { BehaviorSubject, map, Observable } from 'rxjs';
import type { ManifestTypes, ManifestTypeMap, ManifestBase, ManifestWithLoader, ManifestEntrypoint, HTMLElementConstructor } from '../../models'; import { UmbContextToken } from "@umbraco-cms/context-api";
import { hasDefaultExport } from '../has-default-export.function'; import type { UmbControllerHostInterface } from "@umbraco-cms/controller";
import type { ManifestTypes, ManifestTypeMap, ManifestBase, ManifestEntrypoint } from '../../models';
import { loadExtension } from '../load-extension.function'; import { loadExtension } from '../load-extension.function';
import { hasInitExport } from "../has-init-export.function";
type SpecificManifestTypeOrManifestBase<T extends keyof ManifestTypeMap | string> = T extends keyof ManifestTypeMap type SpecificManifestTypeOrManifestBase<T extends keyof ManifestTypeMap | string> = T extends keyof ManifestTypeMap
? ManifestTypeMap[T] ? ManifestTypeMap[T]
@@ -13,7 +15,7 @@ export class UmbExtensionRegistry {
private _extensions = new BehaviorSubject<Array<ManifestBase>>([]); private _extensions = new BehaviorSubject<Array<ManifestBase>>([]);
public readonly extensions = this._extensions.asObservable(); public readonly extensions = this._extensions.asObservable();
register(manifest: ManifestTypes): void { register(manifest: ManifestTypes, rootHost?: UmbControllerHostInterface): void {
const extensionsValues = this._extensions.getValue(); const extensionsValues = this._extensions.getValue();
const extension = extensionsValues.find((extension) => extension.alias === manifest.alias); const extension = extensionsValues.find((extension) => extension.alias === manifest.alias);
@@ -27,12 +29,9 @@ export class UmbExtensionRegistry {
// If entrypoint extension, we should load and run it immediately // If entrypoint extension, we should load and run it immediately
if (manifest.type === 'entrypoint') { if (manifest.type === 'entrypoint') {
loadExtension(manifest as ManifestEntrypoint).then((js) => { loadExtension(manifest as ManifestEntrypoint).then((js) => {
if (hasDefaultExport<HTMLElementConstructor>(js)) { // If the extension has an onInit export, be sure to run that or else let the module handle itself
new js.default(); if (hasInitExport(js)) {
} else { js.onInit(rootHost!, this);
console.error(
`Extension with alias '${manifest.alias}' of type 'entrypoint' must have a default export of its JavaScript module.`
);
} }
}); });
} }
@@ -96,3 +95,5 @@ export class UmbExtensionRegistry {
) as Observable<Array<ExtensionType>>; ) as Observable<Array<ExtensionType>>;
} }
} }
export const UMB_EXTENSION_REGISTRY_TOKEN = new UmbContextToken<UmbExtensionRegistry>('UmbExtensionRegistry');

View File

@@ -0,0 +1,9 @@
import type { UmbControllerHostInterface } from "@umbraco-cms/controller";
import type { UmbExtensionRegistry } from "./registry/extension.registry";
/**
* Interface containing supported life-cycle functions for ESModule entrypoints
*/
export interface UmbEntrypointModule {
onInit: (host: UmbControllerHostInterface, extensionRegistry: UmbExtensionRegistry) => void
}

View File

@@ -16,13 +16,13 @@ import type { ManifestMenu } from './menu.models';
import type { ManifestMenuItem } from './menu-item.models'; import type { ManifestMenuItem } from './menu-item.models';
import type { ManifestTheme } from './theme.models'; import type { ManifestTheme } from './theme.models';
import type { ManifestTree } from './tree.models'; import type { ManifestTree } from './tree.models';
import type { ManifestTreeItemAction } from './tree-item-action.models';
import type { ManifestUserDashboard } from './user-dashboard.models'; import type { ManifestUserDashboard } from './user-dashboard.models';
import type { ManifestWorkspace } from './workspace.models'; import type { ManifestWorkspace } from './workspace.models';
import type { ManifestWorkspaceAction } from './workspace-action.models'; import type { ManifestWorkspaceAction } from './workspace-action.models';
import type { ManifestWorkspaceView } from './workspace-view.models'; import type { ManifestWorkspaceView } from './workspace-view.models';
import type { ManifestWorkspaceViewCollection } from './workspace-view-collection.models'; import type { ManifestWorkspaceViewCollection } from './workspace-view-collection.models';
import type { ManifestRepository } from './repository.models'; import type { ManifestRepository } from './repository.models';
import type { ManifestStore, ManifestTreeStore } from './store.models';
import type { ClassConstructor } from '@umbraco-cms/models'; import type { ClassConstructor } from '@umbraco-cms/models';
export * from './collection-view.models'; export * from './collection-view.models';
@@ -42,13 +42,13 @@ export * from './section-sidebar-app.models';
export * from './menu.models'; export * from './menu.models';
export * from './menu-item.models'; export * from './menu-item.models';
export * from './theme.models'; export * from './theme.models';
export * from './tree-item-action.models';
export * from './tree.models'; export * from './tree.models';
export * from './user-dashboard.models'; export * from './user-dashboard.models';
export * from './workspace-action.models'; export * from './workspace-action.models';
export * from './workspace-view-collection.models'; export * from './workspace-view-collection.models';
export * from './workspace-view.models'; export * from './workspace-view.models';
export * from './repository.models'; export * from './repository.models';
export * from './store.models';
export * from './workspace.models'; export * from './workspace.models';
export type ManifestTypes = export type ManifestTypes =
@@ -75,12 +75,13 @@ export type ManifestTypes =
| ManifestMenuItem | ManifestMenuItem
| ManifestTheme | ManifestTheme
| ManifestTree | ManifestTree
| ManifestTreeItemAction
| ManifestUserDashboard | ManifestUserDashboard
| ManifestWorkspace | ManifestWorkspace
| ManifestWorkspaceAction | ManifestWorkspaceAction
| ManifestWorkspaceView | ManifestWorkspaceView
| ManifestWorkspaceViewCollection | ManifestWorkspaceViewCollection
| ManifestStore
| ManifestTreeStore
| ManifestBase; | ManifestBase;
export type ManifestStandardTypes = ManifestTypes['type']; export type ManifestStandardTypes = ManifestTypes['type'];
@@ -100,11 +101,11 @@ export interface ManifestWithLoader<LoaderReturnType> extends ManifestBase {
loader?: () => Promise<LoaderReturnType>; loader?: () => Promise<LoaderReturnType>;
} }
export interface ManifestClass extends ManifestWithLoader<object> { export interface ManifestClass<T = unknown> extends ManifestWithLoader<object> {
type: ManifestStandardTypes; type: ManifestStandardTypes;
js?: string; js?: string;
className?: string; className?: string;
class?: ClassConstructor<unknown>; class?: ClassConstructor<T>;
//loader?: () => Promise<object | HTMLElement>; //loader?: () => Promise<object | HTMLElement>;
} }

View File

@@ -0,0 +1,10 @@
import type { ManifestClass } from './models';
import { UmbStoreBase, UmbTreeStoreBase } from '@umbraco-cms/store';
export interface ManifestStore extends ManifestClass<UmbStoreBase> {
type: 'store';
}
export interface ManifestTreeStore extends ManifestClass<UmbTreeStoreBase> {
type: 'treeStore';
}

View File

@@ -1,12 +0,0 @@
import type { ManifestElement } from './models';
export interface ManifestTreeItemAction extends ManifestElement {
type: 'treeItemAction';
meta: MetaTreeItemAction;
}
export interface MetaTreeItemAction {
entityType: string;
label: string;
icon: string;
}

View File

@@ -3,6 +3,7 @@ import {
DictionaryItemTranslationModel, DictionaryItemTranslationModel,
EntityTreeItemModel, EntityTreeItemModel,
FolderTreeItemModel, FolderTreeItemModel,
PackageManifestModel,
ProblemDetailsModel, ProblemDetailsModel,
} from '@umbraco-cms/backend-api'; } from '@umbraco-cms/backend-api';
@@ -152,10 +153,10 @@ export interface SwatchDetails {
value: string; value: string;
} }
export type UmbPackage = { export type UmbPackage = PackageManifestModel;
name?: string;
version?: string;
extensions?: unknown[];
};
export type PagedManifestsResponse = UmbPackage[]; export type PackageManifestResponse = UmbPackage[];
export type UmbPackageWithMigrationStatus = UmbPackage & {
hasPendingMigrations: boolean;
};

View File

@@ -1,3 +1,2 @@
export * from './notification.service'; export * from './notification.context';
export * from './notification-handler'; export * from './notification-handler';
export * from './layouts/default';

View File

@@ -1,7 +1,7 @@
import { html, LitElement } from 'lit'; import { html, LitElement } from 'lit';
import { customElement, property } from 'lit/decorators.js'; import { customElement, property } from 'lit/decorators.js';
import { ifDefined } from 'lit/directives/if-defined.js';
import { UUITextStyles } from '@umbraco-ui/uui-css'; import { UUITextStyles } from '@umbraco-ui/uui-css';
import { ifDefined } from 'lit-html/directives/if-defined.js';
import type { UmbNotificationHandler } from '../..'; import type { UmbNotificationHandler } from '../..';
export interface UmbNotificationDefaultData { export interface UmbNotificationDefaultData {

View File

@@ -4,7 +4,7 @@ import { validate as uuidValidate } from 'uuid';
import { UmbNotificationHandler } from './notification-handler'; import { UmbNotificationHandler } from './notification-handler';
import type { UmbNotificationDefaultData } from './layouts/default'; import type { UmbNotificationDefaultData } from './layouts/default';
import type { UmbNotificationOptions } from './notification.service'; import type { UmbNotificationOptions } from './notification.context';
describe('UmbNotificationHandler', () => { describe('UmbNotificationHandler', () => {
let notificationHandler: UmbNotificationHandler; let notificationHandler: UmbNotificationHandler;

View File

@@ -1,6 +1,6 @@
import { UUIToastNotificationElement } from '@umbraco-ui/uui'; import { UUIToastNotificationElement } from '@umbraco-ui/uui';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import type { UmbNotificationOptions, UmbNotificationData, UmbNotificationColor } from '.'; import type { UmbNotificationOptions, UmbNotificationColor, UmbNotificationDefaultData } from '.';
import './layouts/default'; import './layouts/default';
@@ -12,7 +12,7 @@ export class UmbNotificationHandler {
private _closeResolver: any; private _closeResolver: any;
private _closePromise: Promise<any>; private _closePromise: Promise<any>;
private _elementName?: string; private _elementName?: string;
private _data: UmbNotificationData; private _data?: UmbNotificationDefaultData;
private _defaultColor: UmbNotificationColor = 'default'; private _defaultColor: UmbNotificationColor = 'default';
private _defaultDuration = 6000; private _defaultDuration = 6000;
@@ -28,7 +28,7 @@ export class UmbNotificationHandler {
* @param {UmbNotificationOptions} options * @param {UmbNotificationOptions} options
* @memberof UmbNotificationHandler * @memberof UmbNotificationHandler
*/ */
constructor(options: UmbNotificationOptions<UmbNotificationData>) { constructor(options: UmbNotificationOptions) {
this.key = uuidv4(); this.key = uuidv4();
this.color = options.color || this._defaultColor; this.color = options.color || this._defaultColor;
this.duration = options.duration !== undefined ? options.duration : this._defaultDuration; this.duration = options.duration !== undefined ? options.duration : this._defaultDuration;

View File

@@ -1,28 +1,28 @@
import { expect } from '@open-wc/testing'; import { expect } from '@open-wc/testing';
import { UmbNotificationHandler, UmbNotificationService } from '.'; import { UmbNotificationHandler, UmbNotificationContext } from '.';
describe('UCPNotificationService', () => { describe('UmbNotificationContext', () => {
let notificationService: UmbNotificationService; let notificationContext: UmbNotificationContext;
beforeEach(async () => { beforeEach(async () => {
notificationService = new UmbNotificationService(); notificationContext = new UmbNotificationContext();
}); });
describe('Public API', () => { describe('Public API', () => {
describe('properties', () => { describe('properties', () => {
it('has a dialog property', () => { it('has a dialog property', () => {
expect(notificationService).to.have.property('notifications'); expect(notificationContext).to.have.property('notifications');
}); });
}); });
describe('methods', () => { describe('methods', () => {
it('has a peek method', () => { it('has a peek method', () => {
expect(notificationService).to.have.property('peek').that.is.a('function'); expect(notificationContext).to.have.property('peek').that.is.a('function');
}); });
it('has a stay method', () => { it('has a stay method', () => {
expect(notificationService).to.have.property('stay').that.is.a('function'); expect(notificationContext).to.have.property('stay').that.is.a('function');
}); });
}); });
}); });
@@ -36,7 +36,7 @@ describe('UCPNotificationService', () => {
data: { headline: 'Peek notification headline', message: 'Peek notification message' }, data: { headline: 'Peek notification headline', message: 'Peek notification message' },
}; };
peekNotificationHandler = notificationService.peek('positive', peekOptions); peekNotificationHandler = notificationContext.peek('positive', peekOptions);
layoutElement = peekNotificationHandler.element.querySelector('umb-notification-layout-default'); layoutElement = peekNotificationHandler.element.querySelector('umb-notification-layout-default');
}); });
@@ -64,7 +64,7 @@ describe('UCPNotificationService', () => {
data: { headline: 'Stay notification headline', message: 'Stay notification message' }, data: { headline: 'Stay notification headline', message: 'Stay notification message' },
}; };
stayNotificationHandler = notificationService.stay('danger', stayOptions); stayNotificationHandler = notificationContext.stay('danger', stayOptions);
layoutElement = stayNotificationHandler.element.querySelector('umb-notification-layout-default'); layoutElement = stayNotificationHandler.element.querySelector('umb-notification-layout-default');
}); });

View File

@@ -2,14 +2,22 @@ import { BehaviorSubject } from 'rxjs';
import { UmbNotificationHandler } from './notification-handler'; import { UmbNotificationHandler } from './notification-handler';
import { UmbContextToken } from '@umbraco-cms/context-api'; import { UmbContextToken } from '@umbraco-cms/context-api';
export type UmbNotificationData = any; /**
* The default data of notifications
* @export
* @interface UmbNotificationDefaultData
*/
export interface UmbNotificationDefaultData {
message: string;
headline?: string;
}
/** /**
* @export * @export
* @interface UmbNotificationOptions * @interface UmbNotificationOptions
* @template UmbNotificationData * @template UmbNotificationData
*/ */
export interface UmbNotificationOptions<UmbNotificationData> { export interface UmbNotificationOptions<UmbNotificationData = UmbNotificationDefaultData> {
color?: UmbNotificationColor; color?: UmbNotificationColor;
duration?: number | null; duration?: number | null;
elementName?: string; elementName?: string;
@@ -18,7 +26,7 @@ export interface UmbNotificationOptions<UmbNotificationData> {
export type UmbNotificationColor = '' | 'default' | 'positive' | 'warning' | 'danger'; export type UmbNotificationColor = '' | 'default' | 'positive' | 'warning' | 'danger';
export class UmbNotificationService { export class UmbNotificationContext {
// Notice this cannot use UniqueBehaviorSubject as it holds a HTML Element. which cannot be Serialized to JSON (it has some circular references) // Notice this cannot use UniqueBehaviorSubject as it holds a HTML Element. which cannot be Serialized to JSON (it has some circular references)
private _notifications = new BehaviorSubject(<Array<UmbNotificationHandler>>[]); private _notifications = new BehaviorSubject(<Array<UmbNotificationHandler>>[]);
public readonly notifications = this._notifications.asObservable(); public readonly notifications = this._notifications.asObservable();
@@ -27,9 +35,9 @@ export class UmbNotificationService {
* @private * @private
* @param {UmbNotificationOptions<UmbNotificationData>} options * @param {UmbNotificationOptions<UmbNotificationData>} options
* @return {*} {UmbNotificationHandler} * @return {*} {UmbNotificationHandler}
* @memberof UmbNotificationService * @memberof UmbNotificationContext
*/ */
private _open(options: UmbNotificationOptions<UmbNotificationData>): UmbNotificationHandler { private _open(options: UmbNotificationOptions): UmbNotificationHandler {
const notificationHandler = new UmbNotificationHandler(options); const notificationHandler = new UmbNotificationHandler(options);
notificationHandler.element.addEventListener('closed', () => this._handleClosed(notificationHandler)); notificationHandler.element.addEventListener('closed', () => this._handleClosed(notificationHandler));
@@ -41,7 +49,7 @@ export class UmbNotificationService {
/** /**
* @private * @private
* @param {string} key * @param {string} key
* @memberof UmbNotificationService * @memberof UmbNotificationContext
*/ */
private _close(key: string) { private _close(key: string) {
this._notifications.next(this._notifications.getValue().filter((notification) => notification.key !== key)); this._notifications.next(this._notifications.getValue().filter((notification) => notification.key !== key));
@@ -50,7 +58,7 @@ export class UmbNotificationService {
/** /**
* @private * @private
* @param {string} key * @param {string} key
* @memberof UmbNotificationService * @memberof UmbNotificationContext
*/ */
private _handleClosed(notificationHandler: UmbNotificationHandler) { private _handleClosed(notificationHandler: UmbNotificationHandler) {
notificationHandler.element.removeEventListener('closed', () => this._handleClosed(notificationHandler)); notificationHandler.element.removeEventListener('closed', () => this._handleClosed(notificationHandler));
@@ -62,11 +70,11 @@ export class UmbNotificationService {
* @param {UmbNotificationColor} color * @param {UmbNotificationColor} color
* @param {UmbNotificationOptions<UmbNotificationData>} options * @param {UmbNotificationOptions<UmbNotificationData>} options
* @return {*} * @return {*}
* @memberof UmbNotificationService * @memberof UmbNotificationContext
*/ */
public peek( public peek(
color: UmbNotificationColor, color: UmbNotificationColor,
options: UmbNotificationOptions<UmbNotificationData> options: UmbNotificationOptions
): UmbNotificationHandler { ): UmbNotificationHandler {
return this._open({ color, ...options }); return this._open({ color, ...options });
} }
@@ -76,16 +84,14 @@ export class UmbNotificationService {
* @param {UmbNotificationColor} color * @param {UmbNotificationColor} color
* @param {UmbNotificationOptions<UmbNotificationData>} options * @param {UmbNotificationOptions<UmbNotificationData>} options
* @return {*} * @return {*}
* @memberof UmbNotificationService * @memberof UmbNotificationContext
*/ */
public stay( public stay(
color: UmbNotificationColor, color: UmbNotificationColor,
options: UmbNotificationOptions<UmbNotificationData> options: UmbNotificationOptions
): UmbNotificationHandler { ): UmbNotificationHandler {
return this._open({ ...options, color, duration: null }); return this._open({ ...options, color, duration: null });
} }
} }
export const UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN = new UmbContextToken<UmbNotificationService>( export const UMB_NOTIFICATION_CONTEXT_TOKEN = new UmbContextToken<UmbNotificationContext>('UmbNotificationContext');
UmbNotificationService.name
);

View File

@@ -14,24 +14,24 @@ Stays on the screen until dismissed by the user or custom code. Stay notificatio
## Basic usage ## Basic usage
### Consume UmbNotificationService from an element ### Consume UmbNotificationContext from an element
The UmbNotification service can be used to open notifications. The UmbNotification context can be used to open notifications.
```ts ```ts
import { html, LitElement } from 'lit'; import { html, LitElement } from 'lit';
import { UmbLitElement } from '@umbraco-cms/element'; import { UmbLitElement } from '@umbraco-cms/element';
import type { UmbNotificationService, UMB_NOTIFICATION_SERVICE_CONTEXT_ALIAS } from './core/services/notification'; import type { UmbNotificationContext, UMB_NOTIFICATION_CONTEXT_ALIAS } from '@umbraco-cms/notification';
class MyElement extends UmbLitElement { class MyElement extends UmbLitElement {
private _notificationService?: UmbNotificationService; private _notificationContext?: UmbNotificationContext;
constructor() { constructor() {
super(); super();
this.consumeContext(UMB_NOTIFICATION_SERVICE_CONTEXT_ALIAS, (notificationService) => { this.consumeContext(UMB_NOTIFICATION_CONTEXT_ALIAS, (instance) => {
this._notificationService = notificationService; this._notificationContext = notificationContext;
// notificationService is now ready to be used // notificationContext is now ready to be used
}); });
} }
} }
@@ -39,33 +39,33 @@ class MyElement extends UmbLitElement {
### Open a notification ### Open a notification
A notification is opened by calling one of the helper methods on the UmbNotificationService. The methods will return an instance of UmbNotificationHandler. A notification is opened by calling one of the helper methods on the UmbNotificationContext. The methods will return an instance of UmbNotificationHandler.
```ts ```ts
import { html, LitElement } from 'lit'; import { html, LitElement } from 'lit';
import { state } from 'lit/decorators.js'; import { state } from 'lit/decorators.js';
import { UmbLitElement } from '@umbraco-cms/element'; import { UmbLitElement } from '@umbraco-cms/element';
import type { import type {
UmbNotificationService, UmbNotificationContext,
UmbNotificationDefaultData, UmbNotificationDefaultData,
UMB_NOTIFICATION_SERVICE_CONTEXT_ALIAS, UMB_NOTIFICATION_CONTEXT_ALIAS,
} from './core/services/notification'; } from '@umbraco-cms/notification';
class MyElement extends UmbLitElement { class MyElement extends UmbLitElement {
private _notificationService?: UmbNotificationService; private _notificationContext?: UmbNotificationContext;
constructor() { constructor() {
super(); super();
this.consumeContext(UMB_NOTIFICATION_SERVICE_CONTEXT_ALIAS, (notificationService) => { this.consumeContext(UMB_NOTIFICATION_CONTEXT_ALIAS, (notificationContext) => {
this._notificationService = notificationService; this._notificationContext = notificationContext;
// notificationService is now ready to be used // notificationContext is now ready to be used
}); });
} }
private _handleClick() { private _handleClick() {
const data: UmbNotificationDefaultData = { headline: 'Look at this', message: 'Something good happened' }; const data: UmbNotificationDefaultData = { headline: 'Look at this', message: 'Something good happened' };
const notificationHandler = this._notificationService?.peek('positive', { data }); const notificationHandler = this._notificationContext?.peek('positive', { data });
notificationHandler.onClose().then(() => { notificationHandler.onClose().then(() => {
// if you need any logic when the notification is closed you can run it here // if you need any logic when the notification is closed you can run it here
@@ -88,7 +88,7 @@ The default layout will cover most cases, but there might be situations where we
import { html, LitElement } from 'lit'; import { html, LitElement } from 'lit';
import { property } from 'lit/decorators.js'; import { property } from 'lit/decorators.js';
import { UUITextStyles } from '@umbraco-ui/uui-css'; import { UUITextStyles } from '@umbraco-ui/uui-css';
import type { UmbNotificationHandler } from './core/services/notification'; import type { UmbNotificationHandler } from '@umbraco-cms/notification';
export interface UmbNotificationCustomData { export interface UmbNotificationCustomData {
headline: string; headline: string;
@@ -127,21 +127,21 @@ export class UmbNotificationLayoutCustom extends LitElement {
import { html, LitElement } from 'lit'; import { html, LitElement } from 'lit';
import { UmbContextInjectMixin } from '@umbraco-cms/context-api'; import { UmbContextInjectMixin } from '@umbraco-cms/context-api';
import type { import type {
UmbNotificationService, UmbNotificationContext,
UmbNotificationOptions, UmbNotificationOptions,
UMB_NOTIFICATION_SERVICE_CONTEXT_ALIAS, UMB_NOTIFICATION_CONTEXT_ALIAS,
} from './core/services/notification'; } from '@umbraco-cms/notification';
import type { UmbNotificationCustomData } from './custom-notification-layout'; import type { UmbNotificationCustomData } from './custom-notification-layout';
class MyElement extends LitElement { class MyElement extends LitElement {
private _notificationService?: UmbNotificationService; private _notificationContext?: UmbNotificationContext;
constructor() { constructor() {
super(); super();
this.consumeContext(UMB_NOTIFICATION_SERVICE_CONTEXT_ALIAS, (notificationService) => { this.consumeContext(UMB_NOTIFICATION_CONTEXT_ALIAS, (instance) => {
this._notificationService = notificationService; this._notificationContext = instance;
// notificationService is now ready to be used // notificationContext is now ready to be used
}); });
} }
@@ -154,7 +154,7 @@ class MyElement extends LitElement {
}, },
}; };
const notificationHandler = this._notificationService?.stay('default', options); const notificationHandler = this._notificationContext?.stay('default', options);
notificationHandler.onClose().then((result) => { notificationHandler.onClose().then((result) => {
if (result) { if (result) {
@@ -174,4 +174,4 @@ class MyElement extends LitElement {
- Keep messages in notifications short and friendly. - Keep messages in notifications short and friendly.
- Only use headlines when you need extra attention to the notification - Only use headlines when you need extra attention to the notification
- If a custom notification layout is only used in one module keep the files layout files local to that module. - If a custom notification layout is only used in one module keep the files layout files local to that module.
- If a custom notification will be used across the project. Create it as a layout in the notification folder and add a helper method to the UmbNotificationService. - If a custom notification will be used across the project. Create it as a layout in the notification folder and add a helper method to the UmbNotificationContext.

View File

@@ -3,14 +3,14 @@ import '../layouts/default';
import { Meta, Story } from '@storybook/web-components'; import { Meta, Story } from '@storybook/web-components';
import { html } from 'lit'; import { html } from 'lit';
import { UmbNotificationService } from '..'; import { UmbNotificationContext } from '..';
export default { export default {
title: 'API/Notifications/Overview', title: 'API/Notifications/Overview',
component: 'ucp-notification-layout-default', component: 'ucp-notification-layout-default',
decorators: [ decorators: [
(story) => (story) =>
html`<umb-context-provider key="umbNotificationService" .value=${new UmbNotificationService()}> html`<umb-context-provider key="UmbNotificationContext" .value=${new UmbNotificationContext()}>
${story()} ${story()}
</umb-context-provider>`, </umb-context-provider>`,
], ],
@@ -31,7 +31,7 @@ const options: UmbNotificationOptions<UmbNotificationDefaultData> = {
} }
}; };
this._notificationService?.peek('positive', options); this._notificationContext?.peek('positive', options);
`, `,
}, },
}, },

View File

@@ -4,21 +4,20 @@ import { UmbNotificationDefaultData } from '../layouts/default';
import { import {
UmbNotificationColor, UmbNotificationColor,
UmbNotificationOptions, UmbNotificationOptions,
UmbNotificationService, UmbNotificationContext,
UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN UMB_NOTIFICATION_CONTEXT_TOKEN,
} from '..'; } from '..';
import { UmbLitElement } from '@umbraco-cms/element'; import { UmbLitElement } from '@umbraco-cms/element';
@customElement('story-notification-default-example') @customElement('story-notification-default-example')
export class StoryNotificationDefaultExampleElement extends UmbLitElement { export class StoryNotificationDefaultExampleElement extends UmbLitElement {
private _notificationService?: UmbNotificationService; private _notificationContext?: UmbNotificationContext;
connectedCallback(): void { connectedCallback(): void {
super.connectedCallback(); super.connectedCallback();
this.consumeContext(UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN, (notificationService) => { this.consumeContext(UMB_NOTIFICATION_CONTEXT_TOKEN, (instance) => {
this._notificationService = notificationService; this._notificationContext = instance;
}); });
} }
@@ -29,7 +28,7 @@ export class StoryNotificationDefaultExampleElement extends UmbLitElement {
message: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit', message: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
}, },
}; };
this._notificationService?.peek(color, options); this._notificationContext?.peek(color, options);
}; };
render() { render() {

View File

@@ -1,9 +1,8 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import { import {
UmbNotificationOptions, UmbNotificationOptions,
UmbNotificationService, UmbNotificationContext,
UmbNotificationDefaultData, UMB_NOTIFICATION_CONTEXT_TOKEN,
UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN,
} from '@umbraco-cms/notification'; } from '@umbraco-cms/notification';
import { ApiError, CancelablePromise, ProblemDetailsModel } from '@umbraco-cms/backend-api'; import { ApiError, CancelablePromise, ProblemDetailsModel } from '@umbraco-cms/backend-api';
import { UmbController, UmbControllerHostInterface } from '@umbraco-cms/controller'; import { UmbController, UmbControllerHostInterface } from '@umbraco-cms/controller';
@@ -13,15 +12,15 @@ import type { DataSourceResponse } from '@umbraco-cms/models';
export class UmbResourceController extends UmbController { export class UmbResourceController extends UmbController {
#promise: Promise<any>; #promise: Promise<any>;
#notificationService?: UmbNotificationService; #notificationContext?: UmbNotificationContext;
constructor(host: UmbControllerHostInterface, promise: Promise<any>, alias?: string) { constructor(host: UmbControllerHostInterface, promise: Promise<any>, alias?: string) {
super(host, alias); super(host, alias);
this.#promise = promise; this.#promise = promise;
new UmbContextConsumerController(host, UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN, (_instance) => { new UmbContextConsumerController(host, UMB_NOTIFICATION_CONTEXT_TOKEN, (_instance) => {
this.#notificationService = _instance; this.#notificationContext = _instance;
}); });
} }
@@ -57,9 +56,9 @@ export class UmbResourceController extends UmbController {
*/ */
static async tryExecute<T>(promise: Promise<T>): Promise<DataSourceResponse<T>> { static async tryExecute<T>(promise: Promise<T>): Promise<DataSourceResponse<T>> {
try { try {
return { data: await promise }; return {data: await promise};
} catch (e) { } catch (e) {
return { error: UmbResourceController.toProblemDetailsModel(e) }; return {error: UmbResourceController.toProblemDetailsModel(e)};
} }
} }
@@ -67,17 +66,17 @@ export class UmbResourceController extends UmbController {
* Wrap the {execute} function in a try/catch block and return the result. * Wrap the {execute} function in a try/catch block and return the result.
* If the executor function throws an error, then show the details in a notification. * If the executor function throws an error, then show the details in a notification.
*/ */
async tryExecuteAndNotify<T>(options?: UmbNotificationOptions<any>): Promise<DataSourceResponse<T>> { async tryExecuteAndNotify<T>(options?: UmbNotificationOptions): Promise<DataSourceResponse<T>> {
const { data, error } = await UmbResourceController.tryExecute<T>(this.#promise); const {data, error} = await UmbResourceController.tryExecute<T>(this.#promise);
if (error) { if (error) {
const data: UmbNotificationDefaultData = { if (this.#notificationContext) {
headline: error.title ?? 'Server Error', this.#notificationContext?.peek('danger', {
message: error.detail ?? 'Something went wrong', data: {
}; headline: error.title ?? 'Server Error',
message: error.detail ?? 'Something went wrong'
if (this.#notificationService) { }, ...options
this.#notificationService?.peek('danger', { data, ...options }); });
} else { } else {
console.group('UmbResourceController'); console.group('UmbResourceController');
console.error(error); console.error(error);
@@ -85,7 +84,7 @@ export class UmbResourceController extends UmbController {
} }
} }
return { data, error }; return {data, error};
} }
/** /**

View File

@@ -12,7 +12,7 @@ import { UmbRouterSlotChangeEvent, UmbRouterSlotInitEvent } from '@umbraco-cms/r
*/ */
@customElement('umb-router-slot') @customElement('umb-router-slot')
export class UmbRouterSlotElement extends LitElement { export class UmbRouterSlotElement extends LitElement {
#router: RouterSlot; #router: RouterSlot = new RouterSlot();
#listening = false; #listening = false;
@property() @property()
@@ -37,13 +37,6 @@ export class UmbRouterSlotElement extends LitElement {
return this._routerPath + '/' + this._activeLocalPath; return this._routerPath + '/' + this._activeLocalPath;
} }
constructor() {
super();
this.#router = new RouterSlot();
// Note: I decided not to use the local changestate event, because it is not fired when the route is changed from any router-slot. And for now I wanted to keep it local.
//this.#router.addEventListener('changestate', this._onNavigationChanged);
}
connectedCallback() { connectedCallback() {
super.connectedCallback(); super.connectedCallback();
if (this.#listening === false) { if (this.#listening === false) {

View File

@@ -0,0 +1,4 @@
import config from '../../utils/rollup.config.js';
export default {
...config,
};

File diff suppressed because it is too large Load Diff

View File

@@ -29,8 +29,8 @@
"dev": "vite", "dev": "vite",
"build": "tsc && vite build --mode staging", "build": "tsc && vite build --mode staging",
"build:for:static": "tsc && vite build", "build:for:static": "tsc && vite build",
"build:for:cms": "tsc && vite build -c vite.cms.config.ts", "build:for:cms": "tsc && vite build -c vite.cms.config.ts && node utils/build-libs.js",
"build:for:cms:watch": "npm run build:for:cms -- --watch", "build:for:cms:watch": "vite build -c vite.cms.config.ts --watch",
"preview": "vite preview --open", "preview": "vite preview --open",
"test": "web-test-runner --coverage", "test": "web-test-runner --coverage",
"test:watch": "web-test-runner --watch", "test:watch": "web-test-runner --watch",
@@ -66,7 +66,7 @@
"element-internals-polyfill": "^1.1.19", "element-internals-polyfill": "^1.1.19",
"lit": "^2.6.1", "lit": "^2.6.1",
"lodash-es": "4.17.21", "lodash-es": "4.17.21",
"router-slot": "^1.5.5", "router-slot": "file:router-slot-1.6.1.tgz",
"rxjs": "^7.8.0", "rxjs": "^7.8.0",
"uuid": "^9.0.0" "uuid": "^9.0.0"
}, },
@@ -75,19 +75,20 @@
"@mdx-js/react": "^2.2.1", "@mdx-js/react": "^2.2.1",
"@open-wc/testing": "^3.1.7", "@open-wc/testing": "^3.1.7",
"@playwright/test": "^1.30.0", "@playwright/test": "^1.30.0",
"@storybook/addon-a11y": "^7.0.0-beta.53", "@rollup/plugin-json": "^6.0.0",
"@storybook/addon-actions": "^7.0.0-beta.53", "@storybook/addon-a11y": "^7.0.0-beta.59",
"@storybook/addon-essentials": "^7.0.0-beta.53", "@storybook/addon-actions": "^7.0.0-beta.59",
"@storybook/addon-links": "^7.0.0-beta.53", "@storybook/addon-essentials": "^7.0.0-beta.59",
"@storybook/addon-links": "^7.0.0-beta.59",
"@storybook/mdx2-csf": "^1.0.0-next.5", "@storybook/mdx2-csf": "^1.0.0-next.5",
"@storybook/web-components": "^7.0.0-beta.53", "@storybook/web-components": "^7.0.0-beta.59",
"@storybook/web-components-vite": "^7.0.0-beta.53", "@storybook/web-components-vite": "^7.0.0-beta.59",
"@types/chai": "^4.3.4", "@types/chai": "^4.3.4",
"@types/lodash-es": "^4.17.6", "@types/lodash-es": "^4.17.6",
"@types/mocha": "^10.0.0", "@types/mocha": "^10.0.0",
"@types/uuid": "^9.0.0", "@types/uuid": "^9.0.0",
"@typescript-eslint/eslint-plugin": "^5.50.0", "@typescript-eslint/eslint-plugin": "^5.50.0",
"@typescript-eslint/parser": "^5.50.0", "@typescript-eslint/parser": "^5.54.0",
"@web/dev-server-esbuild": "^0.3.3", "@web/dev-server-esbuild": "^0.3.3",
"@web/dev-server-import-maps": "^0.0.7", "@web/dev-server-import-maps": "^0.0.7",
"@web/dev-server-rollup": "^0.3.21", "@web/dev-server-rollup": "^0.3.21",
@@ -104,18 +105,18 @@
"eslint-plugin-storybook": "^0.6.11", "eslint-plugin-storybook": "^0.6.11",
"eslint-plugin-wc": "^1.4.0", "eslint-plugin-wc": "^1.4.0",
"lit-html": "^2.6.1", "lit-html": "^2.6.1",
"msw": "^0.49.2", "msw": "^1.1.0",
"msw-storybook-addon": "^1.7.0", "msw-storybook-addon": "^1.7.0",
"openapi-typescript-codegen": "^0.23.0", "openapi-typescript-codegen": "^0.23.0",
"playwright-msw": "^2.1.0", "playwright-msw": "^2.1.0",
"plop": "^3.1.1", "plop": "^3.1.1",
"prettier": "2.8.3", "prettier": "2.8.4",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"rollup": "^3.10.0", "rollup": "^3.10.0",
"rollup-plugin-esbuild": "^5.0.0", "rollup-plugin-esbuild": "^5.0.0",
"rollup-plugin-url": "^3.0.1", "rollup-plugin-url": "^3.0.1",
"storybook": "^7.0.0-beta.53", "storybook": "^7.0.0-beta.59",
"tiny-glob": "^0.2.9", "tiny-glob": "^0.2.9",
"typescript": "^4.9.5", "typescript": "^4.9.5",
"vite": "^4.1.4", "vite": "^4.1.4",

View File

@@ -2,7 +2,7 @@
/* tslint:disable */ /* tslint:disable */
/** /**
* Mock Service Worker (0.49.3). * Mock Service Worker (1.1.0).
* @see https://github.com/mswjs/msw * @see https://github.com/mswjs/msw
* - Please do NOT modify this file. * - Please do NOT modify this file.
* - Please do NOT serve this file on production. * - Please do NOT serve this file on production.

Binary file not shown.

View File

@@ -2,9 +2,7 @@ import { defineElement } from '@umbraco-ui/uui-base/lib/registration';
import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
import { css, html } from 'lit'; import { css, html } from 'lit';
import { UmbModalService, UMB_MODAL_SERVICE_CONTEXT_TOKEN } from '../core/modal'; import { UmbModalContext, UMB_MODAL_CONTEXT_TOKEN } from '../core/modal';
import { UmbUserStore } from './users/users/user.store';
import { UmbUserGroupStore } from './users/user-groups/user-group.store';
import { UmbCurrentUserStore, UMB_CURRENT_USER_STORE_CONTEXT_TOKEN } from './users/current-user/current-user.store'; import { UmbCurrentUserStore, UMB_CURRENT_USER_STORE_CONTEXT_TOKEN } from './users/current-user/current-user.store';
import { import {
UmbCurrentUserHistoryStore, UmbCurrentUserHistoryStore,
@@ -15,39 +13,15 @@ import {
UmbBackofficeContext, UmbBackofficeContext,
UMB_BACKOFFICE_CONTEXT_TOKEN, UMB_BACKOFFICE_CONTEXT_TOKEN,
} from './shared/components/backoffice-frame/backoffice.context'; } from './shared/components/backoffice-frame/backoffice.context';
import { UmbDocumentTypeStore } from './documents/document-types/repository/document-type.store';
import { UmbDocumentTypeTreeStore } from './documents/document-types/repository/document-type.tree.store';
import { UmbMediaTypeDetailStore } from './media/media-types/repository/media-type.detail.store';
import { UmbMediaTypeTreeStore } from './media/media-types/repository/media-type.tree.store';
import { UmbDocumentStore } from './documents/documents/repository/document.store';
import { UmbDocumentTreeStore } from './documents/documents/repository/document.tree.store';
import { UmbMediaDetailStore } from './media/media/repository/media.detail.store';
import { UmbMediaTreeStore } from './media/media/repository/media.tree.store';
import { UmbMemberTypeDetailStore } from './members/member-types/repository/member-type.detail.store';
import { UmbMemberTypeTreeStore } from './members/member-types/repository/member-type.tree.store';
import { UmbMemberGroupDetailStore } from './members/member-groups/repository/member-group.detail.store';
import { UmbMemberGroupTreeStore } from './members/member-groups/repository/member-group.tree.store';
import { UmbMemberDetailStore } from './members/members/member.detail.store';
import { UmbMemberTreeStore } from './members/members/repository/member.tree.store';
import { UmbDictionaryDetailStore } from './translation/dictionary/repository/dictionary.detail.store';
import { UmbDictionaryTreeStore } from './translation/dictionary/repository/dictionary.tree.store';
import { UmbDocumentBlueprintDetailStore } from './documents/document-blueprints/document-blueprint.detail.store';
import { UmbDocumentBlueprintTreeStore } from './documents/document-blueprints/document-blueprint.tree.store';
import { UmbDataTypeStore } from './settings/data-types/repository/data-type.store';
import { UmbDataTypeTreeStore } from './settings/data-types/repository/data-type.tree.store';
import { UmbTemplateTreeStore } from './templating/templates/tree/data/template.tree.store';
import { UmbTemplateDetailStore } from './templating/templates/workspace/data/template.detail.store';
import { UmbThemeContext } from './themes/theme.context'; import { UmbThemeContext } from './themes/theme.context';
import { UmbLanguageStore } from './settings/languages/repository/language.store';
import { import {
UMB_APP_LANGUAGE_CONTEXT_TOKEN, UMB_APP_LANGUAGE_CONTEXT_TOKEN,
UmbAppLanguageContext, UmbAppLanguageContext,
} from './settings/languages/app-language-select/app-language.context'; } from './settings/languages/app-language-select/app-language.context';
import { UmbPackageStore } from './packages/repository/package.store';
import { UmbServerExtensionController } from './packages/repository/server-extension.controller'; import { UmbServerExtensionController } from './packages/repository/server-extension.controller';
import { UmbNotificationService, UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN } from '@umbraco-cms/notification'; import { createExtensionClass, umbExtensionsRegistry } from '@umbraco-cms/extensions-api';
import { UmbNotificationContext, UMB_NOTIFICATION_CONTEXT_TOKEN } from '@umbraco-cms/notification';
import { UmbLitElement } from '@umbraco-cms/element'; import { UmbLitElement } from '@umbraco-cms/element';
import { umbExtensionsRegistry } from '@umbraco-cms/extensions-api';
import '@umbraco-cms/router'; import '@umbraco-cms/router';
@@ -83,45 +57,20 @@ export class UmbBackofficeElement extends UmbLitElement {
constructor() { constructor() {
super(); super();
this.provideContext(UMB_MODAL_SERVICE_CONTEXT_TOKEN, new UmbModalService()); // TODO: find a way this is possible outside this element.
this.provideContext(UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN, new UmbNotificationService()); this.provideContext(UMB_MODAL_CONTEXT_TOKEN, new UmbModalContext());
this.provideContext(UMB_NOTIFICATION_CONTEXT_TOKEN, new UmbNotificationContext());
// TODO: find a way this is possible outside this element. It needs to be possible to register stores in extensions
this.provideContext(UMB_CURRENT_USER_STORE_CONTEXT_TOKEN, new UmbCurrentUserStore()); this.provideContext(UMB_CURRENT_USER_STORE_CONTEXT_TOKEN, new UmbCurrentUserStore());
new UmbDocumentStore(this);
new UmbDocumentTreeStore(this);
new UmbMediaDetailStore(this);
new UmbMediaTreeStore(this);
new UmbDataTypeStore(this);
new UmbDataTypeTreeStore(this);
new UmbUserStore(this);
new UmbMediaTypeDetailStore(this);
new UmbMediaTypeTreeStore(this);
new UmbDocumentTypeStore(this);
new UmbDocumentTypeTreeStore(this);
new UmbMemberTypeDetailStore(this);
new UmbMemberTypeTreeStore(this);
new UmbUserGroupStore(this);
new UmbMemberGroupDetailStore(this);
new UmbMemberGroupTreeStore(this);
new UmbMemberDetailStore(this);
new UmbMemberTreeStore(this);
new UmbDictionaryDetailStore(this);
new UmbDictionaryTreeStore(this);
new UmbDocumentBlueprintDetailStore(this);
new UmbDocumentBlueprintTreeStore(this);
new UmbTemplateTreeStore(this);
new UmbTemplateDetailStore(this);
new UmbLanguageStore(this);
this.provideContext(UMB_APP_LANGUAGE_CONTEXT_TOKEN, new UmbAppLanguageContext(this)); this.provideContext(UMB_APP_LANGUAGE_CONTEXT_TOKEN, new UmbAppLanguageContext(this));
this.provideContext(UMB_BACKOFFICE_CONTEXT_TOKEN, new UmbBackofficeContext()); this.provideContext(UMB_BACKOFFICE_CONTEXT_TOKEN, new UmbBackofficeContext());
this.provideContext(UMB_CURRENT_USER_HISTORY_STORE_CONTEXT_TOKEN, new UmbCurrentUserHistoryStore());
new UmbThemeContext(this); new UmbThemeContext(this);
new UmbPackageStore(this);
new UmbServerExtensionController(this, umbExtensionsRegistry); new UmbServerExtensionController(this, umbExtensionsRegistry);
this.provideContext(UMB_CURRENT_USER_HISTORY_STORE_CONTEXT_TOKEN, new UmbCurrentUserHistoryStore());
// Register All Stores
this.observe(umbExtensionsRegistry.extensionsOfTypes(['store', 'treeStore']), (stores) => {
stores.forEach((store) => createExtensionClass(store, [this]));
});
} }
render() { render() {

View File

@@ -2,7 +2,7 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
import { css, html, nothing } from 'lit'; import { css, html, nothing } from 'lit';
import { customElement, state, query, property } from 'lit/decorators.js'; import { customElement, state, query, property } from 'lit/decorators.js';
import { UUIButtonState, UUIPaginationElement, UUIPaginationEvent } from '@umbraco-ui/uui'; import { UUIButtonState, UUIPaginationElement, UUIPaginationEvent } from '@umbraco-ui/uui';
import { UmbModalService, UMB_MODAL_SERVICE_CONTEXT_TOKEN } from '../../../../core/modal'; import { UmbModalContext, UMB_MODAL_CONTEXT_TOKEN } from '../../../../core/modal';
import { UmbLitElement } from '@umbraco-cms/element'; import { UmbLitElement } from '@umbraco-cms/element';
import { RedirectManagementResource, RedirectStatusModel, RedirectUrlModel } from '@umbraco-cms/backend-api'; import { RedirectManagementResource, RedirectStatusModel, RedirectUrlModel } from '@umbraco-cms/backend-api';
import { tryExecuteAndNotify } from '@umbraco-cms/resources'; import { tryExecuteAndNotify } from '@umbraco-cms/resources';
@@ -105,12 +105,12 @@ export class UmbDashboardRedirectManagementElement extends UmbLitElement {
@query('uui-pagination') @query('uui-pagination')
private _pagination?: UUIPaginationElement; private _pagination?: UUIPaginationElement;
private _modalService?: UmbModalService; private _modalContext?: UmbModalContext;
constructor() { constructor() {
super(); super();
this.consumeContext(UMB_MODAL_SERVICE_CONTEXT_TOKEN, (_instance) => { this.consumeContext(UMB_MODAL_CONTEXT_TOKEN, (_instance) => {
this._modalService = _instance; this._modalContext = _instance;
}); });
} }
@@ -126,7 +126,7 @@ export class UmbDashboardRedirectManagementElement extends UmbLitElement {
} }
private _removeRedirectHandler(data: RedirectUrlModel) { private _removeRedirectHandler(data: RedirectUrlModel) {
const modalHandler = this._modalService?.confirm({ const modalHandler = this._modalContext?.confirm({
headline: 'Delete', headline: 'Delete',
content: html` content: html`
<div style="width:300px"> <div style="width:300px">
@@ -157,7 +157,7 @@ export class UmbDashboardRedirectManagementElement extends UmbLitElement {
} }
private _disableRedirectHandler() { private _disableRedirectHandler() {
const modalHandler = this._modalService?.confirm({ const modalHandler = this._modalContext?.confirm({
headline: 'Disable URL tracker', headline: 'Disable URL tracker',
content: html`Are you sure you want to disable the URL tracker?`, content: html`Are you sure you want to disable the URL tracker?`,
color: 'danger', color: 'danger',

View File

@@ -4,32 +4,25 @@ import { ArrayState } from '@umbraco-cms/observable-api';
import { UmbStoreBase } from '@umbraco-cms/store'; import { UmbStoreBase } from '@umbraco-cms/store';
import { UmbControllerHostInterface } from '@umbraco-cms/controller'; import { UmbControllerHostInterface } from '@umbraco-cms/controller';
export const UMB_DocumentBlueprint_DETAIL_STORE_CONTEXT_TOKEN = new UmbContextToken<UmbDocumentBlueprintDetailStore>('UmbDocumentBlueprintDetailStore');
/** /**
* @export * @export
* @class UmbDocumentBlueprintDetailStore * @class UmbDocumentBlueprintStore
* @extends {UmbStoreBase} * @extends {UmbStoreBase}
* @description - Details Data Store for Document Blueprints * @description - Data Store for Document Blueprints
*/ */
export class UmbDocumentBlueprintDetailStore extends UmbStoreBase { export class UmbDocumentBlueprintStore extends UmbStoreBase {
// TODO: use the right type: // TODO: use the right type:
#data = new ArrayState<DocumentBlueprintDetails>([], (x) => x.key); #data = new ArrayState<DocumentBlueprintDetails>([], (x) => x.key);
constructor(host: UmbControllerHostInterface) { constructor(host: UmbControllerHostInterface) {
super(host, UMB_DocumentBlueprint_DETAIL_STORE_CONTEXT_TOKEN.toString()); super(host, UMB_DOCUMENT_BLUEPRINT_STORE_CONTEXT_TOKEN.toString());
} }
/** /**
* @description - Request a Data Type by key. The Data Type is added to the store and is returned as an Observable. * @description - Request a Data Type by key. The Data Type is added to the store and is returned as an Observable.
* @param {string} key * @param {string} key
* @return {*} {(Observable<DocumentBlueprintDetails | undefined>)} * @return {*} {(Observable<DocumentBlueprintDetails | undefined>)}
* @memberof UmbDocumentBlueprintDetailStore * @memberof UmbDocumentBlueprintStore
*/ */
getByKey(key: string) { getByKey(key: string) {
// TODO: use backend cli when available. // TODO: use backend cli when available.
@@ -39,21 +32,18 @@ export class UmbDocumentBlueprintDetailStore extends UmbStoreBase {
this.#data.append(data); this.#data.append(data);
}); });
return this.#data.getObservablePart((documents) => return this.#data.getObservablePart((documents) => documents.find((document) => document.key === key));
documents.find((document) => document.key === key)
);
} }
getScaffold(entityType: string, parentKey: string | null) { getScaffold(entityType: string, parentKey: string | null) {
return { return {} as DocumentBlueprintDetails;
} as DocumentBlueprintDetails;
} }
// TODO: make sure UI somehow can follow the status of this action. // TODO: make sure UI somehow can follow the status of this action.
/** /**
* @description - Save a DocumentBlueprint. * @description - Save a DocumentBlueprint.
* @param {Array<DocumentBlueprintDetails>} Dictionaries * @param {Array<DocumentBlueprintDetails>} Dictionaries
* @memberof UmbDocumentBlueprintDetailStore * @memberof UmbDocumentBlueprintStore
* @return {*} {Promise<void>} * @return {*} {Promise<void>}
*/ */
save(data: DocumentBlueprintDetails[]) { save(data: DocumentBlueprintDetails[]) {
@@ -86,7 +76,7 @@ export class UmbDocumentBlueprintDetailStore extends UmbStoreBase {
/** /**
* @description - Delete a Data Type. * @description - Delete a Data Type.
* @param {string[]} keys * @param {string[]} keys
* @memberof UmbDocumentBlueprintDetailStore * @memberof UmbDocumentBlueprintStore
* @return {*} {Promise<void>} * @return {*} {Promise<void>}
*/ */
async delete(keys: string[]) { async delete(keys: string[]) {
@@ -102,3 +92,7 @@ export class UmbDocumentBlueprintDetailStore extends UmbStoreBase {
this.#data.remove(keys); this.#data.remove(keys);
} }
} }
export const UMB_DOCUMENT_BLUEPRINT_STORE_CONTEXT_TOKEN = new UmbContextToken<UmbDocumentBlueprintStore>(
'UmbDocumentBlueprintStore'
);

View File

@@ -1,11 +1,8 @@
import { DocumentBlueprintResource, DocumentTreeItemModel } from '@umbraco-cms/backend-api';
import { tryExecuteAndNotify } from '@umbraco-cms/resources';
import { UmbContextToken } from '@umbraco-cms/context-api'; import { UmbContextToken } from '@umbraco-cms/context-api';
import { ArrayState } from '@umbraco-cms/observable-api'; import { UmbTreeStoreBase } from '@umbraco-cms/store';
import { UmbStoreBase } from '@umbraco-cms/store';
import type { UmbControllerHostInterface } from '@umbraco-cms/controller'; import type { UmbControllerHostInterface } from '@umbraco-cms/controller';
export const UMB_DocumentBlueprint_TREE_STORE_CONTEXT_TOKEN = new UmbContextToken<UmbDocumentBlueprintTreeStore>( export const UMB_DOCUMENT_BLUEPRINT_TREE_STORE_CONTEXT_TOKEN = new UmbContextToken<UmbDocumentBlueprintTreeStore>(
'UmbDocumentBlueprintTreeStore' 'UmbDocumentBlueprintTreeStore'
); );
@@ -15,81 +12,8 @@ export const UMB_DocumentBlueprint_TREE_STORE_CONTEXT_TOKEN = new UmbContextToke
* @extends {UmbStoreBase} * @extends {UmbStoreBase}
* @description - Tree Data Store for Document Blueprints * @description - Tree Data Store for Document Blueprints
*/ */
export class UmbDocumentBlueprintTreeStore extends UmbStoreBase { export class UmbDocumentBlueprintTreeStore extends UmbTreeStoreBase {
#data = new ArrayState<DocumentTreeItemModel>([], (x) => x.key);
constructor(host: UmbControllerHostInterface) { constructor(host: UmbControllerHostInterface) {
super(host, UMB_DocumentBlueprint_TREE_STORE_CONTEXT_TOKEN.toString()); super(host, UMB_DOCUMENT_BLUEPRINT_TREE_STORE_CONTEXT_TOKEN.toString());
}
// TODO: How can we avoid having this in both stores?
/**
* @description - Delete a Document Blueprint Type.
* @param {string[]} keys
* @memberof UmbDocumentBlueprintsStore
* @return {*} {Promise<void>}
*/
async delete(keys: string[]) {
// TODO: use backend cli when available.
await fetch('/umbraco/backoffice/data-type/delete', {
method: 'POST',
body: JSON.stringify(keys),
headers: {
'Content-Type': 'application/json',
},
});
this.#data.remove(keys);
}
getTreeRoot() {
tryExecuteAndNotify(this._host, DocumentBlueprintResource.getTreeDocumentBlueprintRoot({})).then(({ data }) => {
if (data) {
// TODO: how do we handle if an item has been removed during this session(like in another tab or by another user)?
this.#data.append(data.items);
}
});
// TODO: how do we handle trashed items?
// TODO: remove ignore when we know how to handle trashed items.
return this.#data.getObservablePart((items) => items.filter((item) => item.parentKey === null && !item.isTrashed));
}
getTreeItemChildren(key: string) {
/*
tryExecuteAndNotify(
this._host,
DocumentBlueprintResource.getTreeDocumentBlueprintChildren({
parentKey: key,
})
).then(({ data }) => {
if (data) {
// TODO: how do we handle if an item has been removed during this session(like in another tab or by another user)?
this.#data.append(data.items);
}
});
*/
// TODO: how do we handle trashed items?
// TODO: remove ignore when we know how to handle trashed items.
return this.#data.getObservablePart((items) => items.filter((item) => item.parentKey === key && !item.isTrashed));
}
getTreeItems(keys: Array<string>) {
if (keys?.length > 0) {
tryExecuteAndNotify(
this._host,
DocumentBlueprintResource.getTreeDocumentBlueprintItem({
key: keys,
})
).then(({ data }) => {
if (data) {
// TODO: how do we handle if an item has been removed during this session(like in another tab or by another user)?
this.#data.append(data);
}
});
}
return this.#data.getObservablePart((items) => items.filter((item) => keys.includes(item.key ?? '')));
} }
} }

View File

@@ -1,4 +1,24 @@
import { UmbDocumentBlueprintStore } from './document-blueprint.detail.store';
import { UmbDocumentBlueprintTreeStore } from './document-blueprint.tree.store';
import { manifests as menuItemManifests } from './menu-item/manifests'; import { manifests as menuItemManifests } from './menu-item/manifests';
import { manifests as workspaceManifests } from './workspace/manifests'; import { manifests as workspaceManifests } from './workspace/manifests';
import type { ManifestStore, ManifestTreeStore } from '@umbraco-cms/extensions-registry';
export const manifests = [...menuItemManifests, ...workspaceManifests]; export const DOCUMENT_BLUEPRINT_STORE_ALIAS = 'Umb.Store.DocumentBlueprint';
export const DOCUMENT_BLUEPRINT_TREE_STORE_ALIAS = 'Umb.Store.DocumentBlueprintTree';
const store: ManifestStore = {
type: 'store',
alias: DOCUMENT_BLUEPRINT_STORE_ALIAS,
name: 'Document Blueprint Store',
class: UmbDocumentBlueprintStore,
};
const treeStore: ManifestTreeStore = {
type: 'treeStore',
alias: DOCUMENT_BLUEPRINT_TREE_STORE_ALIAS,
name: 'Document Blueprint Tree Store',
class: UmbDocumentBlueprintTreeStore,
};
export const manifests = [store, treeStore, ...menuItemManifests, ...workspaceManifests];

View File

@@ -8,7 +8,7 @@ import { UmbContextConsumerController } from '@umbraco-cms/context-api';
import { ProblemDetailsModel, DocumentTypeModel } from '@umbraco-cms/backend-api'; import { ProblemDetailsModel, DocumentTypeModel } from '@umbraco-cms/backend-api';
import type { UmbTreeRepository } from 'libs/repository/tree-repository.interface'; import type { UmbTreeRepository } from 'libs/repository/tree-repository.interface';
import { UmbDetailRepository } from '@umbraco-cms/repository'; import { UmbDetailRepository } from '@umbraco-cms/repository';
import { UmbNotificationService, UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN } from '@umbraco-cms/notification'; import { UmbNotificationContext, UMB_NOTIFICATION_CONTEXT_TOKEN } from '@umbraco-cms/notification';
type ItemType = DocumentTypeModel; type ItemType = DocumentTypeModel;
@@ -27,7 +27,7 @@ export class UmbDocumentTypeRepository implements UmbTreeRepository, UmbDetailRe
#detailDataSource: UmbDocumentTypeServerDataSource; #detailDataSource: UmbDocumentTypeServerDataSource;
#detailStore?: UmbDocumentTypeStore; #detailStore?: UmbDocumentTypeStore;
#notificationService?: UmbNotificationService; #notificationContext?: UmbNotificationContext;
constructor(host: UmbControllerHostInterface) { constructor(host: UmbControllerHostInterface) {
this.#host = host; this.#host = host;
@@ -45,8 +45,8 @@ export class UmbDocumentTypeRepository implements UmbTreeRepository, UmbDetailRe
this.#detailStore = instance; this.#detailStore = instance;
}), }),
new UmbContextConsumerController(this.#host, UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN, (instance) => { new UmbContextConsumerController(this.#host, UMB_NOTIFICATION_CONTEXT_TOKEN, (instance) => {
this.#notificationService = instance; this.#notificationContext = instance;
}), }),
]); ]);
} }
@@ -159,7 +159,7 @@ export class UmbDocumentTypeRepository implements UmbTreeRepository, UmbDetailRe
if (!error) { if (!error) {
const notification = { data: { message: `Document created` } }; const notification = { data: { message: `Document created` } };
this.#notificationService?.peek('positive', notification); this.#notificationContext?.peek('positive', notification);
} }
// TODO: we currently don't use the detail store for anything. // TODO: we currently don't use the detail store for anything.
@@ -181,7 +181,7 @@ export class UmbDocumentTypeRepository implements UmbTreeRepository, UmbDetailRe
if (!error) { if (!error) {
const notification = { data: { message: `Document saved` } }; const notification = { data: { message: `Document saved` } };
this.#notificationService?.peek('positive', notification); this.#notificationContext?.peek('positive', notification);
} }
// TODO: we currently don't use the detail store for anything. // TODO: we currently don't use the detail store for anything.
@@ -207,7 +207,7 @@ export class UmbDocumentTypeRepository implements UmbTreeRepository, UmbDetailRe
if (!error) { if (!error) {
const notification = { data: { message: `Document deleted` } }; const notification = { data: { message: `Document deleted` } };
this.#notificationService?.peek('positive', notification); this.#notificationContext?.peek('positive', notification);
} }
// TODO: we currently don't use the detail store for anything. // TODO: we currently don't use the detail store for anything.

View File

@@ -19,7 +19,7 @@ export class UmbDocumentTypeStore extends UmbStoreBase {
* @memberof UmbDocumentTypeStore * @memberof UmbDocumentTypeStore
*/ */
constructor(host: UmbControllerHostInterface) { constructor(host: UmbControllerHostInterface) {
super(host, UmbDocumentTypeStore.name); super(host, UMB_DOCUMENT_TYPE_STORE_CONTEXT_TOKEN.toString());
} }
/** /**
@@ -51,5 +51,5 @@ export class UmbDocumentTypeStore extends UmbStoreBase {
} }
export const UMB_DOCUMENT_TYPE_STORE_CONTEXT_TOKEN = new UmbContextToken<UmbDocumentTypeStore>( export const UMB_DOCUMENT_TYPE_STORE_CONTEXT_TOKEN = new UmbContextToken<UmbDocumentTypeStore>(
UmbDocumentTypeStore.name 'UmbDocumentTypeStore'
); );

View File

@@ -21,5 +21,5 @@ export class UmbDocumentTypeTreeStore extends UmbTreeStoreBase {
} }
export const UMB_DOCUMENT_TYPE_TREE_STORE_CONTEXT_TOKEN = new UmbContextToken<UmbDocumentTypeTreeStore>( export const UMB_DOCUMENT_TYPE_TREE_STORE_CONTEXT_TOKEN = new UmbContextToken<UmbDocumentTypeTreeStore>(
UmbDocumentTypeTreeStore.name 'UmbDocumentTypeTreeStore'
); );

View File

@@ -1,7 +1,9 @@
import { UmbDocumentTypeRepository } from './document-type.repository'; import { UmbDocumentTypeRepository } from './document-type.repository';
import { ManifestRepository } from 'libs/extensions-registry/repository.models'; import { UmbDocumentTypeStore } from './document-type.store';
import { UmbDocumentTypeTreeStore } from './document-type.tree.store';
import { ManifestRepository, ManifestStore, ManifestTreeStore } from '@umbraco-cms/extensions-registry';
export const DOCUMENT_TYPE_REPOSITORY_ALIAS = 'Umb.Repository.DocumentTypes'; export const DOCUMENT_TYPE_REPOSITORY_ALIAS = 'Umb.Repository.DocumentType';
const repository: ManifestRepository = { const repository: ManifestRepository = {
type: 'repository', type: 'repository',
@@ -10,4 +12,21 @@ const repository: ManifestRepository = {
class: UmbDocumentTypeRepository, class: UmbDocumentTypeRepository,
}; };
export const manifests = [repository]; export const DOCUMENT_TYPE_STORE_ALIAS = 'Umb.Store.DocumentType';
export const DOCUMENT_TYPE_TREE_STORE_ALIAS = 'Umb.Store.DocumentTypeTree';
const store: ManifestStore = {
type: 'store',
alias: DOCUMENT_TYPE_STORE_ALIAS,
name: 'Document Type Store',
class: UmbDocumentTypeStore,
};
const treeStore: ManifestTreeStore = {
type: 'treeStore',
alias: DOCUMENT_TYPE_TREE_STORE_ALIAS,
name: 'Document Type Tree Store',
class: UmbDocumentTypeTreeStore,
};
export const manifests = [repository, store, treeStore];

View File

@@ -1,5 +1,5 @@
import { UmbDocumentTypeRepository } from '../repository/document-type.repository'; import { UmbDocumentTypeRepository } from '../repository/document-type.repository';
import type { ManifestTree, ManifestTreeItemAction } from '@umbraco-cms/models'; import type { ManifestTree } from '@umbraco-cms/models';
const tree: ManifestTree = { const tree: ManifestTree = {
type: 'tree', type: 'tree',
@@ -10,6 +10,4 @@ const tree: ManifestTree = {
}, },
}; };
const treeItemActions: Array<ManifestTreeItemAction> = []; export const manifests = [tree];
export const manifests = [tree, ...treeItemActions];

View File

@@ -6,7 +6,7 @@ import type { UmbWorkspaceEntityElement } from '../../../shared/components/works
import { UmbWorkspaceDocumentTypeContext } from './document-type-workspace.context'; import { UmbWorkspaceDocumentTypeContext } from './document-type-workspace.context';
import type { DocumentTypeModel } from '@umbraco-cms/backend-api'; import type { DocumentTypeModel } from '@umbraco-cms/backend-api';
import { UmbLitElement } from '@umbraco-cms/element'; import { UmbLitElement } from '@umbraco-cms/element';
import { UmbModalService, UMB_MODAL_SERVICE_CONTEXT_TOKEN } from '@umbraco-cms/modal'; import { UmbModalContext, UMB_MODAL_CONTEXT_TOKEN } from '@umbraco-cms/modal';
@customElement('umb-document-type-workspace') @customElement('umb-document-type-workspace')
export class UmbDocumentTypeWorkspaceElement extends UmbLitElement implements UmbWorkspaceEntityElement { export class UmbDocumentTypeWorkspaceElement extends UmbLitElement implements UmbWorkspaceEntityElement {
@@ -51,13 +51,13 @@ export class UmbDocumentTypeWorkspaceElement extends UmbLitElement implements Um
@state() @state()
private _documentType?: DocumentTypeModel; private _documentType?: DocumentTypeModel;
private _modalService?: UmbModalService; private _modalContext?: UmbModalContext;
constructor() { constructor() {
super(); super();
this.consumeContext(UMB_MODAL_SERVICE_CONTEXT_TOKEN, (instance) => { this.consumeContext(UMB_MODAL_CONTEXT_TOKEN, (instance) => {
this._modalService = instance; this._modalContext = instance;
}); });
this.observe(this._workspaceContext.data, (data) => { this.observe(this._workspaceContext.data, (data) => {
@@ -86,7 +86,7 @@ export class UmbDocumentTypeWorkspaceElement extends UmbLitElement implements Um
} }
private async _handleIconClick() { private async _handleIconClick() {
const modalHandler = this._modalService?.iconPicker(); const modalHandler = this._modalContext?.iconPicker();
modalHandler?.onClose().then((saved) => { modalHandler?.onClose().then((saved) => {
if (saved) this._workspaceContext?.setIcon(saved.icon); if (saved) this._workspaceContext?.setIcon(saved.icon);

View File

@@ -1,3 +1,4 @@
import { DOCUMENT_REPOSITORY_ALIAS } from '../repository/manifests';
import { UmbCreateDocumentEntityAction } from './create.action'; import { UmbCreateDocumentEntityAction } from './create.action';
import { UmbPublishDocumentEntityAction } from './publish.action'; import { UmbPublishDocumentEntityAction } from './publish.action';
import { UmbDocumentCultureAndHostnamesEntityAction } from './culture-and-hostnames.action'; import { UmbDocumentCultureAndHostnamesEntityAction } from './culture-and-hostnames.action';
@@ -15,7 +16,6 @@ import {
import { ManifestEntityAction } from '@umbraco-cms/extensions-registry'; import { ManifestEntityAction } from '@umbraco-cms/extensions-registry';
const entityType = 'document'; const entityType = 'document';
const repositoryAlias = 'Umb.Repository.Documents';
const entityActions: Array<ManifestEntityAction> = [ const entityActions: Array<ManifestEntityAction> = [
{ {
@@ -27,7 +27,7 @@ const entityActions: Array<ManifestEntityAction> = [
entityType, entityType,
icon: 'umb:add', icon: 'umb:add',
label: 'Create', label: 'Create',
repositoryAlias, repositoryAlias: DOCUMENT_REPOSITORY_ALIAS,
api: UmbCreateDocumentEntityAction, api: UmbCreateDocumentEntityAction,
}, },
}, },
@@ -40,7 +40,7 @@ const entityActions: Array<ManifestEntityAction> = [
entityType, entityType,
icon: 'umb:trash', icon: 'umb:trash',
label: 'Trash', label: 'Trash',
repositoryAlias, repositoryAlias: DOCUMENT_REPOSITORY_ALIAS,
api: UmbTrashEntityAction, api: UmbTrashEntityAction,
}, },
}, },
@@ -53,7 +53,7 @@ const entityActions: Array<ManifestEntityAction> = [
entityType, entityType,
icon: 'umb:blueprint', icon: 'umb:blueprint',
label: 'Create Content Template', label: 'Create Content Template',
repositoryAlias, repositoryAlias: DOCUMENT_REPOSITORY_ALIAS,
api: UmbCreateDocumentBlueprintEntityAction, api: UmbCreateDocumentBlueprintEntityAction,
}, },
}, },
@@ -66,7 +66,7 @@ const entityActions: Array<ManifestEntityAction> = [
entityType, entityType,
icon: 'umb:enter', icon: 'umb:enter',
label: 'Move', label: 'Move',
repositoryAlias, repositoryAlias: DOCUMENT_REPOSITORY_ALIAS,
api: UmbMoveEntityAction, api: UmbMoveEntityAction,
}, },
}, },
@@ -79,7 +79,7 @@ const entityActions: Array<ManifestEntityAction> = [
entityType, entityType,
icon: 'umb:documents', icon: 'umb:documents',
label: 'Copy', label: 'Copy',
repositoryAlias, repositoryAlias: DOCUMENT_REPOSITORY_ALIAS,
api: UmbCopyEntityAction, api: UmbCopyEntityAction,
}, },
}, },
@@ -92,7 +92,7 @@ const entityActions: Array<ManifestEntityAction> = [
entityType, entityType,
icon: 'umb:navigation-vertical', icon: 'umb:navigation-vertical',
label: 'Sort', label: 'Sort',
repositoryAlias, repositoryAlias: DOCUMENT_REPOSITORY_ALIAS,
api: UmbSortChildrenOfEntityAction, api: UmbSortChildrenOfEntityAction,
}, },
}, },
@@ -105,7 +105,7 @@ const entityActions: Array<ManifestEntityAction> = [
entityType, entityType,
icon: 'umb:home', icon: 'umb:home',
label: 'Culture And Hostnames', label: 'Culture And Hostnames',
repositoryAlias, repositoryAlias: DOCUMENT_REPOSITORY_ALIAS,
api: UmbDocumentCultureAndHostnamesEntityAction, api: UmbDocumentCultureAndHostnamesEntityAction,
}, },
}, },
@@ -117,7 +117,7 @@ const entityActions: Array<ManifestEntityAction> = [
entityType, entityType,
icon: 'umb:vcard', icon: 'umb:vcard',
label: 'Permissions', label: 'Permissions',
repositoryAlias, repositoryAlias: DOCUMENT_REPOSITORY_ALIAS,
api: UmbDocumentPermissionsEntityAction, api: UmbDocumentPermissionsEntityAction,
}, },
}, },
@@ -129,7 +129,7 @@ const entityActions: Array<ManifestEntityAction> = [
entityType, entityType,
icon: 'umb:lock', icon: 'umb:lock',
label: 'Public Access', label: 'Public Access',
repositoryAlias, repositoryAlias: DOCUMENT_REPOSITORY_ALIAS,
api: UmbDocumentPublicAccessEntityAction, api: UmbDocumentPublicAccessEntityAction,
}, },
}, },
@@ -141,19 +141,7 @@ const entityActions: Array<ManifestEntityAction> = [
entityType, entityType,
icon: 'umb:globe', icon: 'umb:globe',
label: 'Publish', label: 'Publish',
repositoryAlias, repositoryAlias: DOCUMENT_REPOSITORY_ALIAS,
api: UmbPublishDocumentEntityAction,
},
},
{
type: 'entityAction',
alias: 'Umb.EntityAction.Document.Publish',
name: 'Publish Document Entity Action',
meta: {
entityType,
icon: 'umb:globe',
label: 'Publish',
repositoryAlias,
api: UmbPublishDocumentEntityAction, api: UmbPublishDocumentEntityAction,
}, },
}, },
@@ -165,7 +153,7 @@ const entityActions: Array<ManifestEntityAction> = [
entityType, entityType,
icon: 'umb:globe', icon: 'umb:globe',
label: 'Unpublish', label: 'Unpublish',
repositoryAlias, repositoryAlias: DOCUMENT_REPOSITORY_ALIAS,
api: UmbUnpublishDocumentEntityAction, api: UmbUnpublishDocumentEntityAction,
}, },
}, },
@@ -177,7 +165,7 @@ const entityActions: Array<ManifestEntityAction> = [
entityType, entityType,
icon: 'umb:undo', icon: 'umb:undo',
label: 'Rollback', label: 'Rollback',
repositoryAlias, repositoryAlias: DOCUMENT_REPOSITORY_ALIAS,
api: UmbRollbackDocumentEntityAction, api: UmbRollbackDocumentEntityAction,
}, },
}, },

View File

@@ -1,9 +1,9 @@
import { DOCUMENT_REPOSITORY_ALIAS } from '../repository/manifests';
import { UmbDocumentMoveEntityBulkAction } from './move/move.action'; import { UmbDocumentMoveEntityBulkAction } from './move/move.action';
import { UmbDocumentCopyEntityBulkAction } from './copy/copy.action'; import { UmbDocumentCopyEntityBulkAction } from './copy/copy.action';
import { ManifestEntityBulkAction } from '@umbraco-cms/extensions-registry'; import { ManifestEntityBulkAction } from '@umbraco-cms/extensions-registry';
const entityType = 'document'; const entityType = 'document';
const repositoryAlias = 'Umb.Repository.Documents';
const entityActions: Array<ManifestEntityBulkAction> = [ const entityActions: Array<ManifestEntityBulkAction> = [
{ {
@@ -14,7 +14,7 @@ const entityActions: Array<ManifestEntityBulkAction> = [
meta: { meta: {
entityType, entityType,
label: 'Move', label: 'Move',
repositoryAlias, repositoryAlias: DOCUMENT_REPOSITORY_ALIAS,
api: UmbDocumentMoveEntityBulkAction, api: UmbDocumentMoveEntityBulkAction,
}, },
}, },
@@ -26,7 +26,7 @@ const entityActions: Array<ManifestEntityBulkAction> = [
meta: { meta: {
entityType, entityType,
label: 'Copy', label: 'Copy',
repositoryAlias, repositoryAlias: DOCUMENT_REPOSITORY_ALIAS,
api: UmbDocumentCopyEntityBulkAction, api: UmbDocumentCopyEntityBulkAction,
}, },
}, },

View File

@@ -1,14 +1,14 @@
import type { RepositoryTreeDataSource } from '../../../../../libs/repository/repository-tree-data-source.interface'; import type { RepositoryTreeDataSource } from '../../../../../libs/repository/repository-tree-data-source.interface';
import { DocumentTreeServerDataSource } from './sources/document.tree.server.data'; import { DocumentTreeServerDataSource } from './sources/document.tree.server.data';
import { UmbDocumentTreeStore, UMB_DOCUMENT_TREE_STORE_CONTEXT_TOKEN } from './document.tree.store'; import { UmbDocumentTreeStore, UMB_DOCUMENT_TREE_STORE_CONTEXT_TOKEN } from './document.tree.store';
import { UmbDocumentStore, UMB_DOCUMENT_DETAIL_STORE_CONTEXT_TOKEN } from './document.store'; import { UmbDocumentStore, UMB_DOCUMENT_STORE_CONTEXT_TOKEN } from './document.store';
import { UmbDocumentServerDataSource } from './sources/document.server.data'; import { UmbDocumentServerDataSource } from './sources/document.server.data';
import { UmbControllerHostInterface } from '@umbraco-cms/controller'; import { UmbControllerHostInterface } from '@umbraco-cms/controller';
import { UmbContextConsumerController } from '@umbraco-cms/context-api'; import { UmbContextConsumerController } from '@umbraco-cms/context-api';
import { ProblemDetailsModel, DocumentModel } from '@umbraco-cms/backend-api'; import { ProblemDetailsModel, DocumentModel } from '@umbraco-cms/backend-api';
import type { UmbTreeRepository } from 'libs/repository/tree-repository.interface'; import type { UmbTreeRepository } from 'libs/repository/tree-repository.interface';
import { UmbDetailRepository } from '@umbraco-cms/repository'; import { UmbDetailRepository } from '@umbraco-cms/repository';
import { UmbNotificationService, UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN } from '@umbraco-cms/notification'; import { UmbNotificationContext, UMB_NOTIFICATION_CONTEXT_TOKEN } from '@umbraco-cms/notification';
type ItemType = DocumentModel; type ItemType = DocumentModel;
@@ -25,9 +25,9 @@ export class UmbDocumentRepository implements UmbTreeRepository, UmbDetailReposi
#treeStore?: UmbDocumentTreeStore; #treeStore?: UmbDocumentTreeStore;
#detailDataSource: UmbDocumentServerDataSource; #detailDataSource: UmbDocumentServerDataSource;
#detailStore?: UmbDocumentStore; #store?: UmbDocumentStore;
#notificationService?: UmbNotificationService; #notificationContext?: UmbNotificationContext;
constructor(host: UmbControllerHostInterface) { constructor(host: UmbControllerHostInterface) {
this.#host = host; this.#host = host;
@@ -41,12 +41,12 @@ export class UmbDocumentRepository implements UmbTreeRepository, UmbDetailReposi
this.#treeStore = instance; this.#treeStore = instance;
}), }),
new UmbContextConsumerController(this.#host, UMB_DOCUMENT_DETAIL_STORE_CONTEXT_TOKEN, (instance) => { new UmbContextConsumerController(this.#host, UMB_DOCUMENT_STORE_CONTEXT_TOKEN, (instance) => {
this.#detailStore = instance; this.#store = instance;
}), }),
new UmbContextConsumerController(this.#host, UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN, (instance) => { new UmbContextConsumerController(this.#host, UMB_NOTIFICATION_CONTEXT_TOKEN, (instance) => {
this.#notificationService = instance; this.#notificationContext = instance;
}), }),
]); ]);
} }
@@ -135,7 +135,7 @@ export class UmbDocumentRepository implements UmbTreeRepository, UmbDetailReposi
const { data, error } = await this.#detailDataSource.get(key); const { data, error } = await this.#detailDataSource.get(key);
if (data) { if (data) {
this.#detailStore?.append(data); this.#store?.append(data);
} }
return { data, error }; return { data, error };
@@ -154,12 +154,12 @@ export class UmbDocumentRepository implements UmbTreeRepository, UmbDetailReposi
if (!error) { if (!error) {
const notification = { data: { message: `Document created` } }; const notification = { data: { message: `Document created` } };
this.#notificationService?.peek('positive', notification); this.#notificationContext?.peek('positive', notification);
} }
// TODO: we currently don't use the detail store for anything. // TODO: we currently don't use the detail store for anything.
// Consider to look up the data before fetching from the server // Consider to look up the data before fetching from the server
this.#detailStore?.append(item); this.#store?.append(item);
// TODO: Update tree store with the new item? or ask tree to request the new item? // TODO: Update tree store with the new item? or ask tree to request the new item?
return { error }; return { error };
@@ -176,13 +176,13 @@ export class UmbDocumentRepository implements UmbTreeRepository, UmbDetailReposi
if (!error) { if (!error) {
const notification = { data: { message: `Document saved` } }; const notification = { data: { message: `Document saved` } };
this.#notificationService?.peek('positive', notification); this.#notificationContext?.peek('positive', notification);
} }
// TODO: we currently don't use the detail store for anything. // TODO: we currently don't use the detail store for anything.
// Consider to look up the data before fetching from the server // Consider to look up the data before fetching from the server
// Consider notify a workspace if a document is updated in the store while someone is editing it. // Consider notify a workspace if a document is updated in the store while someone is editing it.
this.#detailStore?.append(item); this.#store?.append(item);
//this.#treeStore?.updateItem(item.key, { name: item.name });// Port data to tree store. //this.#treeStore?.updateItem(item.key, { name: item.name });// Port data to tree store.
// TODO: would be nice to align the stores on methods/methodNames. // TODO: would be nice to align the stores on methods/methodNames.
@@ -202,13 +202,13 @@ export class UmbDocumentRepository implements UmbTreeRepository, UmbDetailReposi
if (!error) { if (!error) {
const notification = { data: { message: `Document deleted` } }; const notification = { data: { message: `Document deleted` } };
this.#notificationService?.peek('positive', notification); this.#notificationContext?.peek('positive', notification);
} }
// TODO: we currently don't use the detail store for anything. // TODO: we currently don't use the detail store for anything.
// Consider to look up the data before fetching from the server. // Consider to look up the data before fetching from the server.
// Consider notify a workspace if a document is deleted from the store while someone is editing it. // Consider notify a workspace if a document is deleted from the store while someone is editing it.
this.#detailStore?.remove([key]); this.#store?.remove([key]);
this.#treeStore?.removeItem(key); this.#treeStore?.removeItem(key);
// TODO: would be nice to align the stores on methods/methodNames. // TODO: would be nice to align the stores on methods/methodNames.

View File

@@ -19,7 +19,7 @@ export class UmbDocumentStore extends UmbStoreBase {
* @memberof UmbDocumentDetailStore * @memberof UmbDocumentDetailStore
*/ */
constructor(host: UmbControllerHostInterface) { constructor(host: UmbControllerHostInterface) {
super(host, UmbDocumentStore.name); super(host, UMB_DOCUMENT_STORE_CONTEXT_TOKEN.toString());
} }
/** /**
@@ -50,4 +50,4 @@ export class UmbDocumentStore extends UmbStoreBase {
} }
} }
export const UMB_DOCUMENT_DETAIL_STORE_CONTEXT_TOKEN = new UmbContextToken<UmbDocumentStore>(UmbDocumentStore.name); export const UMB_DOCUMENT_STORE_CONTEXT_TOKEN = new UmbContextToken<UmbDocumentStore>('UmbDocumentStore');

View File

@@ -20,5 +20,5 @@ export class UmbDocumentTreeStore extends UmbTreeStoreBase {
} }
export const UMB_DOCUMENT_TREE_STORE_CONTEXT_TOKEN = new UmbContextToken<UmbDocumentTreeStore>( export const UMB_DOCUMENT_TREE_STORE_CONTEXT_TOKEN = new UmbContextToken<UmbDocumentTreeStore>(
UmbDocumentTreeStore.name 'UmbDocumentTreeStore'
); );

View File

@@ -1,7 +1,10 @@
import { UmbDocumentRepository } from '../repository/document.repository'; import { UmbDocumentRepository } from '../repository/document.repository';
import { UmbDocumentStore } from './document.store';
import { UmbDocumentTreeStore } from './document.tree.store';
import { ManifestRepository } from 'libs/extensions-registry/repository.models'; import { ManifestRepository } from 'libs/extensions-registry/repository.models';
import { ManifestStore, ManifestTreeStore } from '@umbraco-cms/extensions-registry';
export const DOCUMENT_REPOSITORY_ALIAS = 'Umb.Repository.Documents'; export const DOCUMENT_REPOSITORY_ALIAS = 'Umb.Repository.Document';
const repository: ManifestRepository = { const repository: ManifestRepository = {
type: 'repository', type: 'repository',
@@ -10,4 +13,21 @@ const repository: ManifestRepository = {
class: UmbDocumentRepository, class: UmbDocumentRepository,
}; };
export const manifests = [repository]; export const DOCUMENT_STORE_ALIAS = 'Umb.Store.Document';
export const DOCUMENT_TREE_STORE_ALIAS = 'Umb.Store.DocumentTree';
const store: ManifestStore = {
type: 'store',
alias: DOCUMENT_STORE_ALIAS,
name: 'Document Store',
class: UmbDocumentStore,
};
const treeStore: ManifestTreeStore = {
type: 'treeStore',
alias: DOCUMENT_TREE_STORE_ALIAS,
name: 'Document Tree Store',
class: UmbDocumentTreeStore,
};
export const manifests = [repository, store, treeStore];

View File

@@ -1,34 +0,0 @@
import { UUITextStyles } from '@umbraco-ui/uui-css';
import { css, html } from 'lit';
import { customElement } from 'lit/decorators.js';
import UmbTreeItemActionElement from '../../../../shared/components/tree/action/tree-item-action.element';
@customElement('umb-tree-action-document-create')
export default class UmbTreeActionDocumentCreateElement extends UmbTreeItemActionElement {
static styles = [UUITextStyles, css``];
// TODO: how do we handle the href?
private _constructUrl() {
return `/section/content/${this._activeTreeItem?.type}/${this._activeTreeItem?.key}/view/content?create=true`;
}
// TODO: change to href. This is a temporary solution to get the link to work. For some reason query params gets removed when using href.
private _handleLabelClick() {
if (!this._treeContextMenuService) return;
const href = this._constructUrl();
history.pushState(null, '', href);
this._treeContextMenuService.close();
}
render() {
return html`<uui-menu-item label=${this.treeAction?.meta.label ?? ''} @click-label="${this._handleLabelClick}">
<uui-icon slot="icon" name=${this.treeAction?.meta.icon ?? ''}></uui-icon>
</uui-menu-item>`;
}
}
declare global {
interface HTMLElementTagNameMap {
'umb-tree-action-document-create': UmbTreeActionDocumentCreateElement;
}
}

View File

@@ -1,30 +0,0 @@
import { UUITextStyles } from '@umbraco-ui/uui-css';
import { css, html } from 'lit';
import { customElement } from 'lit/decorators.js';
import UmbTreeItemActionElement from '../../../../shared/components/tree/action/tree-item-action.element';
@customElement('umb-tree-action-create-page-2')
export class UmbTreeActionCreatePage2Element extends UmbTreeItemActionElement {
static styles = [UUITextStyles, css``];
private _save() {
this._treeContextMenuService?.close();
}
private _back() {
this._actionPageService?.closeTopPage();
}
render() {
return html`<h2>Create page 2 for entity: ${this._entity.name}</h2>
<p>This is the last create page, here you can go back og save (it just closes the modal for now)</p>
<uui-button label="Back" look="secondary" @click=${this._back}></uui-button>
<uui-button label="Save" look="primary" color="positive" @click=${this._save}></uui-button>`;
}
}
declare global {
interface HTMLElementTagNameMap {
'umb-tree-action-create-page-2': UmbTreeActionCreatePage2Element;
}
}

View File

@@ -1,30 +0,0 @@
import { UUITextStyles } from '@umbraco-ui/uui-css';
import { css, html } from 'lit';
import { customElement } from 'lit/decorators.js';
import UmbTreeItemActionElement from '../../../../shared/components/tree/action/tree-item-action.element';
@customElement('umb-tree-action-create-page')
export class UmbTreeActionCreatePageElement extends UmbTreeItemActionElement {
static styles = [UUITextStyles, css``];
private _next() {
this._actionPageService?.openPage('umb-tree-action-create-page-2');
}
private _back() {
this._actionPageService?.closeTopPage();
}
render() {
return html`<h2>Create page 1 for entity: ${this._entity.name}</h2>
<p>This is the first create page, here you can go next or back (it just closes the modal for now)</p>
<uui-button label="Back" look="secondary" @click=${this._back}></uui-button>
<uui-button label="Next" look="primary" @click=${this._next}></uui-button>`;
}
}
declare global {
interface HTMLElementTagNameMap {
'umb-tree-action-create-page': UmbTreeActionCreatePageElement;
}
}

View File

@@ -1,30 +0,0 @@
import { UUITextStyles } from '@umbraco-ui/uui-css';
import { css, html } from 'lit';
import { customElement } from 'lit/decorators.js';
import UmbTreeItemActionElement from '../../../../shared/components/tree/action/tree-item-action.element';
import './action-document-paged-page.element';
import './action-document-paged-page-2.element';
@customElement('umb-tree-action-create')
export class UmbTreeActionCreateElement extends UmbTreeItemActionElement {
static styles = [UUITextStyles, css``];
private _handleLabelClick() {
this._actionPageService?.openPage('umb-tree-action-create-page');
}
render() {
return html`<uui-menu-item label=${this.treeAction?.meta.label ?? ''} @click-label="${this._handleLabelClick}">
<uui-icon slot="icon" name=${this.treeAction?.meta.icon ?? ''}></uui-icon>
</uui-menu-item>`;
}
}
export default UmbTreeActionCreateElement;
declare global {
interface HTMLElementTagNameMap {
'umb-tree-action-create': UmbTreeActionCreateElement;
}
}

View File

@@ -100,6 +100,10 @@ export class UmbDocumentWorkspaceContext
this.#activeVariantsInfo.next(activeVariants); this.#activeVariantsInfo.next(activeVariants);
} }
openSplitView(culture: string | null, segment: string | null) {
this.setActiveVariant(1, culture, segment);
}
getVariant(variantId: UmbVariantId) { getVariant(variantId: UmbVariantId) {
return this.#draft.getValue()?.variants?.find((x) => variantId.compare(x)); return this.#draft.getValue()?.variants?.find((x) => variantId.compare(x));
} }

View File

@@ -14,7 +14,7 @@ export class UmbDocumentWorkspaceElement extends UmbLitElement implements UmbWor
UUITextStyles, UUITextStyles,
css` css`
:host { :host {
display: block; display: flex;
width: 100%; width: 100%;
height: 100%; height: 100%;
} }

View File

@@ -15,6 +15,7 @@ export class UmbDocumentWorkspaceViewEditElement extends UmbLitElement {
css` css`
:host { :host {
display: block; display: block;
--uui-tab-background: var(--uui-color-surface);
} }
`, `,
]; ];
@@ -135,7 +136,7 @@ export class UmbDocumentWorkspaceViewEditElement extends UmbLitElement {
? html` ? html`
<uui-tab <uui-tab
label="Content" label="Content"
.active=${this._routerPath === this._activePath} .active=${this._routerPath + '/root' === this._activePath}
href=${this._routerPath || ''} href=${this._routerPath || ''}
>Content</uui-tab >Content</uui-tab
> >
@@ -160,7 +161,7 @@ export class UmbDocumentWorkspaceViewEditElement extends UmbLitElement {
this._routerPath = event.target.absoluteRouterPath; this._routerPath = event.target.absoluteRouterPath;
}} }}
@change=${(event: UmbRouterSlotChangeEvent) => { @change=${(event: UmbRouterSlotChangeEvent) => {
this._activePath = event.target.localActiveViewPath || ''; this._activePath = event.target.absoluteActiveViewPath || '';
}}> }}>
</umb-router-slot> </umb-router-slot>
`; `;

View File

@@ -9,10 +9,7 @@ import { umbExtensionsRegistry } from '@umbraco-cms/extensions-api';
import { ManifestTypes } from '@umbraco-cms/extensions-registry'; import { ManifestTypes } from '@umbraco-cms/extensions-registry';
const registerExtensions = (manifests: Array<ManifestTypes>) => { const registerExtensions = (manifests: Array<ManifestTypes>) => {
manifests.forEach((manifest) => { manifests.forEach((manifest) => umbExtensionsRegistry.register(manifest));
if (umbExtensionsRegistry.isRegistered(manifest.alias)) return;
umbExtensionsRegistry.register(manifest);
});
}; };
registerExtensions([ registerExtensions([

View File

@@ -7,10 +7,7 @@ import { umbExtensionsRegistry } from '@umbraco-cms/extensions-api';
import { ManifestTypes } from '@umbraco-cms/extensions-registry'; import { ManifestTypes } from '@umbraco-cms/extensions-registry';
const registerExtensions = (manifests: Array<ManifestTypes>) => { const registerExtensions = (manifests: Array<ManifestTypes>) => {
manifests.forEach((manifest) => { manifests.forEach((manifest) => umbExtensionsRegistry.register(manifest));
if (umbExtensionsRegistry.isRegistered(manifest.alias)) return;
umbExtensionsRegistry.register(manifest);
});
}; };
registerExtensions([...mediaSectionManifests, ...mediaMenuManifests, ...mediaManifests, ...mediaTypesManifests]); registerExtensions([...mediaSectionManifests, ...mediaMenuManifests, ...mediaManifests, ...mediaTypesManifests]);

View File

@@ -1,13 +1,33 @@
import { UmbMediaTypeRepository } from './media-type.repository'; import { UmbMediaTypeRepository } from './media-type.repository';
import { UmbMediaTypeStore } from './media-type.detail.store';
import { UmbMediaTypeTreeStore } from './media-type.tree.store';
import { ManifestRepository } from 'libs/extensions-registry/repository.models'; import { ManifestRepository } from 'libs/extensions-registry/repository.models';
import { ManifestStore, ManifestTreeStore } from '@umbraco-cms/extensions-registry';
export const MEDIA_TYPE_REPOSITORY_ALIAS = 'Umb.Repository.MediaTypes'; export const MEDIA_TYPE_REPOSITORY_ALIAS = 'Umb.Repository.MediaType';
const repository: ManifestRepository = { const repository: ManifestRepository = {
type: 'repository', type: 'repository',
alias: MEDIA_TYPE_REPOSITORY_ALIAS, alias: MEDIA_TYPE_REPOSITORY_ALIAS,
name: 'Media Types Repository', name: 'Media Type Repository',
class: UmbMediaTypeRepository, class: UmbMediaTypeRepository,
}; };
export const manifests = [repository]; export const MEDIA_TYPE_STORE_ALIAS = 'Umb.Store.MediaType';
export const MEDIA_TYPE_TREE_STORE_ALIAS = 'Umb.Store.MediaTypeTree';
const store: ManifestStore = {
type: 'store',
alias: MEDIA_TYPE_STORE_ALIAS,
name: 'Media Type Store',
class: UmbMediaTypeStore,
};
const treeStore: ManifestTreeStore = {
type: 'treeStore',
alias: MEDIA_TYPE_TREE_STORE_ALIAS,
name: 'Media Type Tree Store',
class: UmbMediaTypeTreeStore,
};
export const manifests = [store, treeStore, repository];

View File

@@ -10,13 +10,11 @@ import type { MediaTypeDetails } from '@umbraco-cms/models';
* @extends {UmbStoreBase} * @extends {UmbStoreBase}
* @description - Details Data Store for Media Types * @description - Details Data Store for Media Types
*/ */
export class UmbMediaTypeDetailStore export class UmbMediaTypeStore extends UmbStoreBase {
extends UmbStoreBase
{
#data = new ArrayState<MediaTypeDetails>([], (x) => x.key); #data = new ArrayState<MediaTypeDetails>([], (x) => x.key);
constructor(host: UmbControllerHostInterface) { constructor(host: UmbControllerHostInterface) {
super(host, UmbMediaTypeDetailStore.name); super(host, UMB_MEDIA_TYPE_STORE_CONTEXT_TOKEN.toString());
} }
append(mediaType: MediaTypeDetails) { append(mediaType: MediaTypeDetails) {
@@ -25,9 +23,7 @@ export class UmbMediaTypeDetailStore
remove(uniques: string[]) { remove(uniques: string[]) {
this.#data.remove(uniques); this.#data.remove(uniques);
} }
} }
export const UMB_MEDIA_TYPE_DETAIL_STORE_CONTEXT_TOKEN = new UmbContextToken<UmbMediaTypeDetailStore>( export const UMB_MEDIA_TYPE_STORE_CONTEXT_TOKEN = new UmbContextToken<UmbMediaTypeStore>('UmbMediaTypeStore');
UmbMediaTypeDetailStore.name
);

View File

@@ -1,12 +1,12 @@
import { UmbMediaTypeTreeStore, UMB_MEDIA_TYPE_TREE_STORE_CONTEXT_TOKEN } from './media-type.tree.store'; import { UmbMediaTypeTreeStore, UMB_MEDIA_TYPE_TREE_STORE_CONTEXT_TOKEN } from './media-type.tree.store';
import { UmbMediaTypeDetailServerDataSource } from './sources/media-type.detail.server.data'; import { UmbMediaTypeDetailServerDataSource } from './sources/media-type.detail.server.data';
import { UmbMediaTypeDetailStore, UMB_MEDIA_TYPE_DETAIL_STORE_CONTEXT_TOKEN } from './media-type.detail.store'; import { UmbMediaTypeStore, UMB_MEDIA_TYPE_STORE_CONTEXT_TOKEN } from './media-type.detail.store';
import { MediaTypeTreeServerDataSource } from './sources/media-type.tree.server.data'; import { MediaTypeTreeServerDataSource } from './sources/media-type.tree.server.data';
import { ProblemDetailsModel } from '@umbraco-cms/backend-api'; import { ProblemDetailsModel } from '@umbraco-cms/backend-api';
import { UmbContextConsumerController } from '@umbraco-cms/context-api'; import { UmbContextConsumerController } from '@umbraco-cms/context-api';
import { UmbControllerHostInterface } from '@umbraco-cms/controller'; import { UmbControllerHostInterface } from '@umbraco-cms/controller';
import type { MediaTypeDetails } from '@umbraco-cms/models'; import type { MediaTypeDetails } from '@umbraco-cms/models';
import { UmbNotificationService, UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN } from '@umbraco-cms/notification'; import { UmbNotificationContext, UMB_NOTIFICATION_CONTEXT_TOKEN } from '@umbraco-cms/notification';
import { UmbTreeRepository, RepositoryTreeDataSource } from '@umbraco-cms/repository'; import { UmbTreeRepository, RepositoryTreeDataSource } from '@umbraco-cms/repository';
export class UmbMediaTypeRepository implements UmbTreeRepository { export class UmbMediaTypeRepository implements UmbTreeRepository {
@@ -18,9 +18,9 @@ export class UmbMediaTypeRepository implements UmbTreeRepository {
#treeStore?: UmbMediaTypeTreeStore; #treeStore?: UmbMediaTypeTreeStore;
#detailSource: UmbMediaTypeDetailServerDataSource; #detailSource: UmbMediaTypeDetailServerDataSource;
#detailStore?: UmbMediaTypeDetailStore; #store?: UmbMediaTypeStore;
#notificationService?: UmbNotificationService; #notificationContext?: UmbNotificationContext;
constructor(host: UmbControllerHostInterface) { constructor(host: UmbControllerHostInterface) {
this.#host = host; this.#host = host;
@@ -30,16 +30,16 @@ export class UmbMediaTypeRepository implements UmbTreeRepository {
this.#detailSource = new UmbMediaTypeDetailServerDataSource(this.#host); this.#detailSource = new UmbMediaTypeDetailServerDataSource(this.#host);
this.#init = Promise.all([ this.#init = Promise.all([
new UmbContextConsumerController(this.#host, UMB_MEDIA_TYPE_DETAIL_STORE_CONTEXT_TOKEN, (instance) => { new UmbContextConsumerController(this.#host, UMB_MEDIA_TYPE_STORE_CONTEXT_TOKEN, (instance) => {
this.#detailStore = instance; this.#store = instance;
}), }),
new UmbContextConsumerController(this.#host, UMB_MEDIA_TYPE_TREE_STORE_CONTEXT_TOKEN, (instance) => { new UmbContextConsumerController(this.#host, UMB_MEDIA_TYPE_TREE_STORE_CONTEXT_TOKEN, (instance) => {
this.#treeStore = instance; this.#treeStore = instance;
}), }),
new UmbContextConsumerController(this.#host, UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN, (instance) => { new UmbContextConsumerController(this.#host, UMB_NOTIFICATION_CONTEXT_TOKEN, (instance) => {
this.#notificationService = instance; this.#notificationContext = instance;
}), }),
]); ]);
} }
@@ -120,7 +120,7 @@ export class UmbMediaTypeRepository implements UmbTreeRepository {
const { data, error } = await this.#detailSource.get(key); const { data, error } = await this.#detailSource.get(key);
if (data) { if (data) {
this.#detailStore?.append(data); this.#store?.append(data);
} }
return { data, error }; return { data, error };
} }
@@ -144,13 +144,13 @@ export class UmbMediaTypeRepository implements UmbTreeRepository {
if (!error) { if (!error) {
const notification = { data: { message: `Media type '${mediaType.name}' saved` } }; const notification = { data: { message: `Media type '${mediaType.name}' saved` } };
this.#notificationService?.peek('positive', notification); this.#notificationContext?.peek('positive', notification);
} }
// TODO: we currently don't use the detail store for anything. // TODO: we currently don't use the detail store for anything.
// Consider to look up the data before fetching from the server // Consider to look up the data before fetching from the server
// Consider notify a workspace if a media type is updated in the store while someone is editing it. // Consider notify a workspace if a media type is updated in the store while someone is editing it.
this.#detailStore?.append(mediaType); this.#store?.append(mediaType);
this.#treeStore?.updateItem(mediaType.key, { name: mediaType.name }); this.#treeStore?.updateItem(mediaType.key, { name: mediaType.name });
// TODO: would be nice to align the stores on methods/methodNames. // TODO: would be nice to align the stores on methods/methodNames.
@@ -169,7 +169,7 @@ export class UmbMediaTypeRepository implements UmbTreeRepository {
if (!error) { if (!error) {
const notification = { data: { message: `Media type '${mediaType.name}' created` } }; const notification = { data: { message: `Media type '${mediaType.name}' created` } };
this.#notificationService?.peek('positive', notification); this.#notificationContext?.peek('positive', notification);
} }
return { data, error }; return { data, error };

View File

@@ -9,7 +9,6 @@ import type { UmbControllerHostInterface } from '@umbraco-cms/controller';
* @description - Tree Data Store for Media Types * @description - Tree Data Store for Media Types
*/ */
export class UmbMediaTypeTreeStore extends UmbTreeStoreBase { export class UmbMediaTypeTreeStore extends UmbTreeStoreBase {
/** /**
* Creates an instance of UmbMediaTypeTreeStore. * Creates an instance of UmbMediaTypeTreeStore.
* @param {UmbControllerHostInterface} host * @param {UmbControllerHostInterface} host
@@ -21,5 +20,5 @@ export class UmbMediaTypeTreeStore extends UmbTreeStoreBase {
} }
export const UMB_MEDIA_TYPE_TREE_STORE_CONTEXT_TOKEN = new UmbContextToken<UmbMediaTypeTreeStore>( export const UMB_MEDIA_TYPE_TREE_STORE_CONTEXT_TOKEN = new UmbContextToken<UmbMediaTypeTreeStore>(
UmbMediaTypeTreeStore.name 'UmbMediaTypeTreeStore'
); );

View File

@@ -1,5 +1,5 @@
import { UmbMediaTypeRepository } from '../repository/media-type.repository'; import { UmbMediaTypeRepository } from '../repository/media-type.repository';
import type { ManifestTree, ManifestTreeItemAction } from '@umbraco-cms/models'; import type { ManifestTree } from '@umbraco-cms/models';
const tree: ManifestTree = { const tree: ManifestTree = {
type: 'tree', type: 'tree',
@@ -10,6 +10,4 @@ const tree: ManifestTree = {
}, },
}; };
const treeItemActions: Array<ManifestTreeItemAction> = []; export const manifests = [tree];
export const manifests = [tree, ...treeItemActions];

View File

@@ -1,3 +1,4 @@
import { MEDIA_REPOSITORY_ALIAS } from '../repository/manifests';
import { UmbTrashEntityAction } from '@umbraco-cms/entity-action'; import { UmbTrashEntityAction } from '@umbraco-cms/entity-action';
import { ManifestEntityAction } from 'libs/extensions-registry/entity-action.models'; import { ManifestEntityAction } from 'libs/extensions-registry/entity-action.models';
@@ -11,7 +12,7 @@ const entityActions: Array<ManifestEntityAction> = [
icon: 'umb:trash', icon: 'umb:trash',
label: 'Trash', label: 'Trash',
api: UmbTrashEntityAction, api: UmbTrashEntityAction,
repositoryAlias: 'Umb.Repository.Media', repositoryAlias: MEDIA_REPOSITORY_ALIAS,
}, },
}, },
]; ];

View File

@@ -1,10 +1,10 @@
import { MEDIA_REPOSITORY_ALIAS } from '../repository/manifests';
import { UmbMediaMoveEntityBulkAction } from './move/move.action'; import { UmbMediaMoveEntityBulkAction } from './move/move.action';
import { UmbMediaCopyEntityBulkAction } from './copy/copy.action'; import { UmbMediaCopyEntityBulkAction } from './copy/copy.action';
import { UmbMediaTrashEntityBulkAction } from './trash/trash.action'; import { UmbMediaTrashEntityBulkAction } from './trash/trash.action';
import { ManifestEntityBulkAction } from '@umbraco-cms/extensions-registry'; import { ManifestEntityBulkAction } from '@umbraco-cms/extensions-registry';
const entityType = 'media'; const entityType = 'media';
const repositoryAlias = 'Umb.Repository.Media';
const entityActions: Array<ManifestEntityBulkAction> = [ const entityActions: Array<ManifestEntityBulkAction> = [
{ {
@@ -15,7 +15,7 @@ const entityActions: Array<ManifestEntityBulkAction> = [
meta: { meta: {
entityType, entityType,
label: 'Move', label: 'Move',
repositoryAlias, repositoryAlias: MEDIA_REPOSITORY_ALIAS,
api: UmbMediaMoveEntityBulkAction, api: UmbMediaMoveEntityBulkAction,
}, },
}, },
@@ -27,7 +27,7 @@ const entityActions: Array<ManifestEntityBulkAction> = [
meta: { meta: {
entityType, entityType,
label: 'Copy', label: 'Copy',
repositoryAlias, repositoryAlias: MEDIA_REPOSITORY_ALIAS,
api: UmbMediaCopyEntityBulkAction, api: UmbMediaCopyEntityBulkAction,
}, },
}, },
@@ -39,7 +39,7 @@ const entityActions: Array<ManifestEntityBulkAction> = [
meta: { meta: {
entityType, entityType,
label: 'Trash', label: 'Trash',
repositoryAlias, repositoryAlias: MEDIA_REPOSITORY_ALIAS,
api: UmbMediaTrashEntityBulkAction, api: UmbMediaTrashEntityBulkAction,
}, },
}, },

View File

@@ -2,22 +2,22 @@ import type { UmbMediaRepository } from '../../repository/media.repository';
import { UmbEntityBulkActionBase } from '@umbraco-cms/entity-action'; import { UmbEntityBulkActionBase } from '@umbraco-cms/entity-action';
import { UmbControllerHostInterface } from '@umbraco-cms/controller'; import { UmbControllerHostInterface } from '@umbraco-cms/controller';
import { UmbContextConsumerController } from '@umbraco-cms/context-api'; import { UmbContextConsumerController } from '@umbraco-cms/context-api';
import { UmbModalService, UMB_MODAL_SERVICE_CONTEXT_TOKEN } from '@umbraco-cms/modal'; import { UmbModalContext, UMB_MODAL_CONTEXT_TOKEN } from '@umbraco-cms/modal';
export class UmbMediaMoveEntityBulkAction extends UmbEntityBulkActionBase<UmbMediaRepository> { export class UmbMediaMoveEntityBulkAction extends UmbEntityBulkActionBase<UmbMediaRepository> {
#modalService?: UmbModalService; #modalContext?: UmbModalContext;
constructor(host: UmbControllerHostInterface, repositoryAlias: string, selection: Array<string>) { constructor(host: UmbControllerHostInterface, repositoryAlias: string, selection: Array<string>) {
super(host, repositoryAlias, selection); super(host, repositoryAlias, selection);
new UmbContextConsumerController(host, UMB_MODAL_SERVICE_CONTEXT_TOKEN, (instance) => { new UmbContextConsumerController(host, UMB_MODAL_CONTEXT_TOKEN, (instance) => {
this.#modalService = instance; this.#modalContext = instance;
}); });
} }
async execute() { async execute() {
// TODO: the picker should be single picker by default // TODO: the picker should be single picker by default
const modalHandler = this.#modalService?.mediaPicker({ selection: [], multiple: false }); const modalHandler = this.#modalContext?.mediaPicker({ selection: [], multiple: false });
const selection = await modalHandler?.onClose(); const selection = await modalHandler?.onClose();
const destination = selection[0]; const destination = selection[0];
await this.repository?.move(this.selection, destination); await this.repository?.move(this.selection, destination);

View File

@@ -3,29 +3,29 @@ import type { UmbMediaRepository } from '../../repository/media.repository';
import { UmbEntityBulkActionBase } from '@umbraco-cms/entity-action'; import { UmbEntityBulkActionBase } from '@umbraco-cms/entity-action';
import { UmbControllerHostInterface } from '@umbraco-cms/controller'; import { UmbControllerHostInterface } from '@umbraco-cms/controller';
import { UmbContextConsumerController } from '@umbraco-cms/context-api'; import { UmbContextConsumerController } from '@umbraco-cms/context-api';
import { UmbModalService, UMB_MODAL_SERVICE_CONTEXT_TOKEN } from '@umbraco-cms/modal'; import { UmbModalContext, UMB_MODAL_CONTEXT_TOKEN } from '@umbraco-cms/modal';
export class UmbMediaTrashEntityBulkAction extends UmbEntityBulkActionBase<UmbMediaRepository> { export class UmbMediaTrashEntityBulkAction extends UmbEntityBulkActionBase<UmbMediaRepository> {
#modalService?: UmbModalService; #modalContext?: UmbModalContext;
constructor(host: UmbControllerHostInterface, repositoryAlias: string, selection: Array<string>) { constructor(host: UmbControllerHostInterface, repositoryAlias: string, selection: Array<string>) {
super(host, repositoryAlias, selection); super(host, repositoryAlias, selection);
new UmbContextConsumerController(host, UMB_MODAL_SERVICE_CONTEXT_TOKEN, (instance) => { new UmbContextConsumerController(host, UMB_MODAL_CONTEXT_TOKEN, (instance) => {
this.#modalService = instance; this.#modalContext = instance;
}); });
} }
async execute() { async execute() {
// TODO: show error // TODO: show error
if (!this.#modalService || !this.repository) return; if (!this.#modalContext || !this.repository) return;
// TODO: should we subscribe in cases like this? // TODO: should we subscribe in cases like this?
const { data } = await this.repository.requestTreeItems(this.selection); const { data } = await this.repository.requestTreeItems(this.selection);
if (data) { if (data) {
// TODO: use correct markup // TODO: use correct markup
const modalHandler = this.#modalService?.confirm({ const modalHandler = this.#modalContext?.confirm({
headline: `Deleting ${this.selection.length} items`, headline: `Deleting ${this.selection.length} items`,
content: html` content: html`
This will delete the following files: This will delete the following files:

View File

@@ -1,13 +1,33 @@
import { UmbMediaRepository } from './media.repository'; import { UmbMediaRepository } from './media.repository';
import { UmbMediaStore } from './media.store';
import { UmbMediaTreeStore } from './media.tree.store';
import { ManifestRepository } from 'libs/extensions-registry/repository.models'; import { ManifestRepository } from 'libs/extensions-registry/repository.models';
import { ManifestStore, ManifestTreeStore } from '@umbraco-cms/extensions-registry';
export const DOCUMENT_REPOSITORY_ALIAS = 'Umb.Repository.Media'; export const MEDIA_REPOSITORY_ALIAS = 'Umb.Repository.Media';
const repository: ManifestRepository = { const repository: ManifestRepository = {
type: 'repository', type: 'repository',
alias: DOCUMENT_REPOSITORY_ALIAS, alias: MEDIA_REPOSITORY_ALIAS,
name: 'Media Repository', name: 'Media Repository',
class: UmbMediaRepository, class: UmbMediaRepository,
}; };
export const manifests = [repository]; export const MEDIA_STORE_ALIAS = 'Umb.Store.Media';
export const MEDIA_TREE_STORE_ALIAS = 'Umb.Store.MediaTree';
const store: ManifestStore = {
type: 'store',
alias: MEDIA_STORE_ALIAS,
name: 'Media Store',
class: UmbMediaStore,
};
const treeStore: ManifestTreeStore = {
type: 'treeStore',
alias: MEDIA_TREE_STORE_ALIAS,
name: 'Media Tree Store',
class: UmbMediaTreeStore,
};
export const manifests = [store, treeStore, repository];

View File

@@ -1,7 +1,7 @@
import type { RepositoryTreeDataSource } from '../../../../../libs/repository/repository-tree-data-source.interface'; import type { RepositoryTreeDataSource } from '../../../../../libs/repository/repository-tree-data-source.interface';
import { MediaTreeServerDataSource } from './sources/media.tree.server.data'; import { MediaTreeServerDataSource } from './sources/media.tree.server.data';
import { UmbMediaTreeStore, UMB_MEDIA_TREE_STORE_CONTEXT_TOKEN } from './media.tree.store'; import { UmbMediaTreeStore, UMB_MEDIA_TREE_STORE_CONTEXT_TOKEN } from './media.tree.store';
import { UmbMediaDetailStore, UMB_MEDIA_DETAIL_STORE_CONTEXT_TOKEN } from './media.detail.store'; import { UmbMediaStore, UMB_MEDIA_STORE_CONTEXT_TOKEN } from './media.store';
import { UmbMediaDetailServerDataSource } from './sources/media.detail.server.data'; import { UmbMediaDetailServerDataSource } from './sources/media.detail.server.data';
import { UmbControllerHostInterface } from '@umbraco-cms/controller'; import { UmbControllerHostInterface } from '@umbraco-cms/controller';
import { UmbContextConsumerController } from '@umbraco-cms/context-api'; import { UmbContextConsumerController } from '@umbraco-cms/context-api';
@@ -9,7 +9,7 @@ import { ProblemDetailsModel } from '@umbraco-cms/backend-api';
import type { UmbTreeRepository } from 'libs/repository/tree-repository.interface'; import type { UmbTreeRepository } from 'libs/repository/tree-repository.interface';
import { UmbDetailRepository } from '@umbraco-cms/repository'; import { UmbDetailRepository } from '@umbraco-cms/repository';
import type { MediaDetails } from '@umbraco-cms/models'; import type { MediaDetails } from '@umbraco-cms/models';
import { UmbNotificationService, UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN } from '@umbraco-cms/notification'; import { UmbNotificationContext, UMB_NOTIFICATION_CONTEXT_TOKEN } from '@umbraco-cms/notification';
type ItemDetailType = MediaDetails; type ItemDetailType = MediaDetails;
@@ -26,9 +26,9 @@ export class UmbMediaRepository implements UmbTreeRepository, UmbDetailRepositor
#treeStore?: UmbMediaTreeStore; #treeStore?: UmbMediaTreeStore;
#detailDataSource: UmbMediaDetailServerDataSource; #detailDataSource: UmbMediaDetailServerDataSource;
#detailStore?: UmbMediaDetailStore; #store?: UmbMediaStore;
#notificationService?: UmbNotificationService; #notificationContext?: UmbNotificationContext;
constructor(host: UmbControllerHostInterface) { constructor(host: UmbControllerHostInterface) {
this.#host = host; this.#host = host;
@@ -42,12 +42,12 @@ export class UmbMediaRepository implements UmbTreeRepository, UmbDetailRepositor
this.#treeStore = instance; this.#treeStore = instance;
}), }),
new UmbContextConsumerController(this.#host, UMB_MEDIA_DETAIL_STORE_CONTEXT_TOKEN, (instance) => { new UmbContextConsumerController(this.#host, UMB_MEDIA_STORE_CONTEXT_TOKEN, (instance) => {
this.#detailStore = instance; this.#store = instance;
}), }),
new UmbContextConsumerController(this.#host, UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN, (instance) => { new UmbContextConsumerController(this.#host, UMB_NOTIFICATION_CONTEXT_TOKEN, (instance) => {
this.#notificationService = instance; this.#notificationContext = instance;
}), }),
]); ]);
} }
@@ -133,7 +133,7 @@ export class UmbMediaRepository implements UmbTreeRepository, UmbDetailRepositor
const { data, error } = await this.#detailDataSource.get(key); const { data, error } = await this.#detailDataSource.get(key);
if (data) { if (data) {
this.#detailStore?.append(data); this.#store?.append(data);
} }
return { data, error }; return { data, error };
@@ -152,12 +152,12 @@ export class UmbMediaRepository implements UmbTreeRepository, UmbDetailRepositor
if (!error) { if (!error) {
const notification = { data: { message: `Media created` } }; const notification = { data: { message: `Media created` } };
this.#notificationService?.peek('positive', notification); this.#notificationContext?.peek('positive', notification);
} }
// TODO: we currently don't use the detail store for anything. // TODO: we currently don't use the detail store for anything.
// Consider to look up the data before fetching from the server // Consider to look up the data before fetching from the server
this.#detailStore?.append(template); this.#store?.append(template);
// TODO: Update tree store with the new item? or ask tree to request the new item? // TODO: Update tree store with the new item? or ask tree to request the new item?
return { error }; return { error };
@@ -174,13 +174,13 @@ export class UmbMediaRepository implements UmbTreeRepository, UmbDetailRepositor
if (!error) { if (!error) {
const notification = { data: { message: `Document saved` } }; const notification = { data: { message: `Document saved` } };
this.#notificationService?.peek('positive', notification); this.#notificationContext?.peek('positive', notification);
} }
// TODO: we currently don't use the detail store for anything. // TODO: we currently don't use the detail store for anything.
// Consider to look up the data before fetching from the server // Consider to look up the data before fetching from the server
// Consider notify a workspace if a template is updated in the store while someone is editing it. // Consider notify a workspace if a template is updated in the store while someone is editing it.
this.#detailStore?.append(document); this.#store?.append(document);
this.#treeStore?.updateItem(document.key, { name: document.name }); this.#treeStore?.updateItem(document.key, { name: document.name });
// TODO: would be nice to align the stores on methods/methodNames. // TODO: would be nice to align the stores on methods/methodNames.
@@ -200,13 +200,13 @@ export class UmbMediaRepository implements UmbTreeRepository, UmbDetailRepositor
if (!error) { if (!error) {
const notification = { data: { message: `Document deleted` } }; const notification = { data: { message: `Document deleted` } };
this.#notificationService?.peek('positive', notification); this.#notificationContext?.peek('positive', notification);
} }
// TODO: we currently don't use the detail store for anything. // TODO: we currently don't use the detail store for anything.
// Consider to look up the data before fetching from the server. // Consider to look up the data before fetching from the server.
// Consider notify a workspace if a template is deleted from the store while someone is editing it. // Consider notify a workspace if a template is deleted from the store while someone is editing it.
this.#detailStore?.remove([key]); this.#store?.remove([key]);
this.#treeStore?.removeItem(key); this.#treeStore?.removeItem(key);
// TODO: would be nice to align the stores on methods/methodNames. // TODO: would be nice to align the stores on methods/methodNames.

View File

@@ -6,26 +6,26 @@ import { UmbControllerHostInterface } from '@umbraco-cms/controller';
/** /**
* @export * @export
* @class UmbMediaDetailStore * @class UmbMediaStore
* @extends {UmbStoreBase} * @extends {UmbStoreBase}
* @description - Data Store for Template Details * @description - Data Store for Template Details
*/ */
export class UmbMediaDetailStore extends UmbStoreBase { export class UmbMediaStore extends UmbStoreBase {
#data = new ArrayState<MediaDetails>([], (x) => x.key); #data = new ArrayState<MediaDetails>([], (x) => x.key);
/** /**
* Creates an instance of UmbMediaDetailStore. * Creates an instance of UmbMediaStore.
* @param {UmbControllerHostInterface} host * @param {UmbControllerHostInterface} host
* @memberof UmbMediaDetailStore * @memberof UmbMediaStore
*/ */
constructor(host: UmbControllerHostInterface) { constructor(host: UmbControllerHostInterface) {
super(host, UmbMediaDetailStore.name); super(host, UMB_MEDIA_STORE_CONTEXT_TOKEN.toString());
} }
/** /**
* Append a media to the store * Append a media to the store
* @param {MediaDetails} media * @param {MediaDetails} media
* @memberof UmbMediaDetailStore * @memberof UmbMediaStore
*/ */
append(media: MediaDetails) { append(media: MediaDetails) {
this.#data.append([media]); this.#data.append([media]);
@@ -34,11 +34,11 @@ export class UmbMediaDetailStore extends UmbStoreBase {
/** /**
* Removes media in the store with the given uniques * Removes media in the store with the given uniques
* @param {string[]} uniques * @param {string[]} uniques
* @memberof UmbMediaDetailStore * @memberof UmbMediaStore
*/ */
remove(uniques: string[]) { remove(uniques: string[]) {
this.#data.remove(uniques); this.#data.remove(uniques);
} }
} }
export const UMB_MEDIA_DETAIL_STORE_CONTEXT_TOKEN = new UmbContextToken<UmbMediaDetailStore>(UmbMediaDetailStore.name); export const UMB_MEDIA_STORE_CONTEXT_TOKEN = new UmbContextToken<UmbMediaStore>('UmbMediaStore');

View File

@@ -1,17 +1,18 @@
import { EntityTreeItemModel } from '@umbraco-cms/backend-api'; import { EntityTreeItemModel } from '@umbraco-cms/backend-api';
import { UmbContextToken } from '@umbraco-cms/context-api'; import { UmbContextToken } from '@umbraco-cms/context-api';
import { ArrayState } from '@umbraco-cms/observable-api'; import { ArrayState } from '@umbraco-cms/observable-api';
import { UmbStoreBase } from '@umbraco-cms/store'; import { UmbTreeStoreBase } from '@umbraco-cms/store';
import { UmbControllerHostInterface } from '@umbraco-cms/controller'; import { UmbControllerHostInterface } from '@umbraco-cms/controller';
export const UMB_MEDIA_TREE_STORE_CONTEXT_TOKEN = new UmbContextToken<UmbMediaTreeStore>('UmbMediaTreeStore');
/** /**
* @export * @export
* @class UmbMediaTreeStore * @class UmbMediaTreeStore
* @extends {UmbStoreBase} * @extends {UmbTreeStoreBase}
* @description - Tree Data Store for Templates * @description - Tree Data Store for Media
*/ */
// TODO: consider if tree store could be turned into a general EntityTreeStore class? export class UmbMediaTreeStore extends UmbTreeStoreBase {
export class UmbMediaTreeStore extends UmbStoreBase {
#data = new ArrayState<EntityTreeItemModel>([], (x) => x.key); #data = new ArrayState<EntityTreeItemModel>([], (x) => x.key);
/** /**
@@ -22,70 +23,4 @@ export class UmbMediaTreeStore extends UmbStoreBase {
constructor(host: UmbControllerHostInterface) { constructor(host: UmbControllerHostInterface) {
super(host, UMB_MEDIA_TREE_STORE_CONTEXT_TOKEN.toString()); super(host, UMB_MEDIA_TREE_STORE_CONTEXT_TOKEN.toString());
} }
/**
* Appends items to the store
* @param {Array<EntityTreeItemModel>} items
* @memberof UmbMediaTreeStore
*/
appendItems(items: Array<EntityTreeItemModel>) {
this.#data.append(items);
}
/**
* Updates an item in the store
* @param {string} key
* @param {Partial<EntityTreeItemModel>} data
* @memberof UmbMediaTreeStore
*/
updateItem(key: string, data: Partial<EntityTreeItemModel>) {
const entries = this.#data.getValue();
const entry = entries.find((entry) => entry.key === key);
if (entry) {
this.#data.appendOne({ ...entry, ...data });
}
}
/**
* Removes an item from the store
* @param {string} key
* @memberof UmbMediaTreeStore
*/
removeItem(key: string) {
const entries = this.#data.getValue();
const entry = entries.find((entry) => entry.key === key);
if (entry) {
this.#data.remove([key]);
}
}
/**
* An observable to observe the root items
* @memberof UmbMediaTreeStore
*/
rootItems = this.#data.getObservablePart((items) => items.filter((item) => item.parentKey === null));
/**
* Returns an observable to observe the children of a given parent
* @param {(string | null)} parentKey
* @return {*}
* @memberof UmbMediaTreeStore
*/
childrenOf(parentKey: string | null) {
return this.#data.getObservablePart((items) => items.filter((item) => item.parentKey === parentKey));
}
/**
* Returns an observable to observe the items with the given keys
* @param {Array<string>} keys
* @return {*}
* @memberof UmbMediaTreeStore
*/
items(keys: Array<string>) {
return this.#data.getObservablePart((items) => items.filter((item) => keys.includes(item.key ?? '')));
}
} }
export const UMB_MEDIA_TREE_STORE_CONTEXT_TOKEN = new UmbContextToken<UmbMediaTreeStore>(UmbMediaTreeStore.name);

View File

@@ -1,5 +1,5 @@
import { UmbMediaRepository } from '../repository/media.repository'; import { UmbMediaRepository } from '../repository/media.repository';
import type { ManifestTree, ManifestTreeItemAction } from '@umbraco-cms/models'; import type { ManifestTree } from '@umbraco-cms/models';
const treeAlias = 'Umb.Tree.Media'; const treeAlias = 'Umb.Tree.Media';
@@ -12,6 +12,4 @@ const tree: ManifestTree = {
}, },
}; };
const treeItemActions: Array<ManifestTreeItemAction> = []; export const manifests = [tree];
export const manifests = [tree, ...treeItemActions];

View File

@@ -1,3 +1,4 @@
import { MEDIA_REPOSITORY_ALIAS } from '../repository/manifests';
import { UmbSaveWorkspaceAction } from '@umbraco-cms/workspace'; import { UmbSaveWorkspaceAction } from '@umbraco-cms/workspace';
import type { import type {
ManifestWorkspace, ManifestWorkspace,
@@ -59,7 +60,7 @@ const workspaceViewCollections: Array<ManifestWorkspaceViewCollection> = [
pathname: 'collection', pathname: 'collection',
icon: 'umb:grid', icon: 'umb:grid',
entityType: 'media', entityType: 'media',
repositoryAlias: 'Umb.Repository.Media', repositoryAlias: MEDIA_REPOSITORY_ALIAS,
}, },
}, },
]; ];

View File

@@ -1,3 +1,4 @@
import { MEDIA_REPOSITORY_ALIAS } from './media/repository/manifests';
import type { ManifestDashboardCollection, ManifestSection, ManifestMenuSectionSidebarApp } from '@umbraco-cms/models'; import type { ManifestDashboardCollection, ManifestSection, ManifestMenuSectionSidebarApp } from '@umbraco-cms/models';
const sectionAlias = 'Umb.Section.Media'; const sectionAlias = 'Umb.Section.Media';
@@ -24,7 +25,7 @@ const dashboards: Array<ManifestDashboardCollection> = [
sections: [sectionAlias], sections: [sectionAlias],
pathname: 'media-management', pathname: 'media-management',
entityType: 'media', entityType: 'media',
repositoryAlias: 'Umb.Repository.Media', repositoryAlias: MEDIA_REPOSITORY_ALIAS,
}, },
}, },
]; ];

View File

@@ -8,10 +8,7 @@ import { umbExtensionsRegistry } from '@umbraco-cms/extensions-api';
import { ManifestTypes } from '@umbraco-cms/extensions-registry'; import { ManifestTypes } from '@umbraco-cms/extensions-registry';
const registerExtensions = (manifests: Array<ManifestTypes>) => { const registerExtensions = (manifests: Array<ManifestTypes>) => {
manifests.forEach((manifest) => { manifests.forEach((manifest) => umbExtensionsRegistry.register(manifest));
if (umbExtensionsRegistry.isRegistered(manifest.alias)) return;
umbExtensionsRegistry.register(manifest);
});
}; };
registerExtensions([ registerExtensions([

View File

@@ -1,3 +1,4 @@
import { MEMBER_GROUP_REPOSITORY_ALIAS } from '../repository/manifests';
import { UmbDeleteEntityAction } from '@umbraco-cms/entity-action'; import { UmbDeleteEntityAction } from '@umbraco-cms/entity-action';
import { ManifestEntityAction } from 'libs/extensions-registry/entity-action.models'; import { ManifestEntityAction } from 'libs/extensions-registry/entity-action.models';
@@ -11,7 +12,7 @@ const entityActions: Array<ManifestEntityAction> = [
icon: 'umb:trash', icon: 'umb:trash',
label: 'Delete', label: 'Delete',
api: UmbDeleteEntityAction, api: UmbDeleteEntityAction,
repositoryAlias: 'Umb.Repository.MemberGroup', repositoryAlias: MEMBER_GROUP_REPOSITORY_ALIAS,
}, },
}, },
]; ];

View File

@@ -1,5 +1,8 @@
import { UmbMemberGroupRepository } from './member-group.repository'; import { UmbMemberGroupRepository } from './member-group.repository';
import { UmbMemberGroupStore } from './member-group.store';
import { UmbMemberGroupTreeStore } from './member-group.tree.store';
import { ManifestRepository } from 'libs/extensions-registry/repository.models'; import { ManifestRepository } from 'libs/extensions-registry/repository.models';
import { ManifestStore, ManifestTreeStore } from '@umbraco-cms/extensions-registry';
export const MEMBER_GROUP_REPOSITORY_ALIAS = 'Umb.Repository.MemberGroup'; export const MEMBER_GROUP_REPOSITORY_ALIAS = 'Umb.Repository.MemberGroup';
@@ -10,4 +13,21 @@ const repository: ManifestRepository = {
class: UmbMemberGroupRepository, class: UmbMemberGroupRepository,
}; };
export const manifests = [repository]; export const MEMBER_GROUP_STORE_ALIAS = 'Umb.Store.MemberGroup';
export const MEMBER_GROUP_TREE_STORE_ALIAS = 'Umb.Store.MemberGroupTree';
const store: ManifestStore = {
type: 'store',
alias: MEMBER_GROUP_STORE_ALIAS,
name: 'Member Group Store',
class: UmbMemberGroupStore,
};
const treeStore: ManifestTreeStore = {
type: 'treeStore',
alias: MEMBER_GROUP_TREE_STORE_ALIAS,
name: 'Member Group Tree Store',
class: UmbMemberGroupTreeStore,
};
export const manifests = [store, treeStore, repository];

View File

@@ -1,9 +1,9 @@
import { UmbMemberGroupTreeStore, UMB_MEMBER_GROUP_TREE_STORE_CONTEXT_TOKEN } from './member-group.tree.store'; import { UmbMemberGroupTreeStore, UMB_MEMBER_GROUP_TREE_STORE_CONTEXT_TOKEN } from './member-group.tree.store';
import { UmbMemberGroupDetailServerDataSource } from './sources/member-group.detail.server.data'; import { UmbMemberGroupDetailServerDataSource } from './sources/member-group.detail.server.data';
import { UmbMemberGroupDetailStore, UMB_MEMBER_GROUP_DETAIL_STORE_CONTEXT_TOKEN } from './member-group.detail.store'; import { UmbMemberGroupStore, UMB_MEMBER_GROUP_STORE_CONTEXT_TOKEN } from './member-group.store';
import { MemberGroupTreeServerDataSource } from './sources/member-group.tree.server.data'; import { MemberGroupTreeServerDataSource } from './sources/member-group.tree.server.data';
import { UmbControllerHostInterface } from '@umbraco-cms/controller'; import { UmbControllerHostInterface } from '@umbraco-cms/controller';
import { UmbNotificationService, UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN } from '@umbraco-cms/notification'; import { UmbNotificationContext, UMB_NOTIFICATION_CONTEXT_TOKEN } from '@umbraco-cms/notification';
import { UmbContextConsumerController } from '@umbraco-cms/context-api'; import { UmbContextConsumerController } from '@umbraco-cms/context-api';
import type { MemberGroupDetails } from '@umbraco-cms/models'; import type { MemberGroupDetails } from '@umbraco-cms/models';
import { ProblemDetailsModel } from '@umbraco-cms/backend-api'; import { ProblemDetailsModel } from '@umbraco-cms/backend-api';
@@ -19,9 +19,9 @@ export class UmbMemberGroupRepository implements UmbTreeRepository, UmbDetailRep
#treeStore?: UmbMemberGroupTreeStore; #treeStore?: UmbMemberGroupTreeStore;
#detailSource: UmbMemberGroupDetailServerDataSource; #detailSource: UmbMemberGroupDetailServerDataSource;
#detailStore?: UmbMemberGroupDetailStore; #store?: UmbMemberGroupStore;
#notificationService?: UmbNotificationService; #notificationContext?: UmbNotificationContext;
constructor(host: UmbControllerHostInterface) { constructor(host: UmbControllerHostInterface) {
this.#host = host; this.#host = host;
@@ -33,12 +33,12 @@ export class UmbMemberGroupRepository implements UmbTreeRepository, UmbDetailRep
this.#treeStore = instance; this.#treeStore = instance;
}); });
new UmbContextConsumerController(this.#host, UMB_MEMBER_GROUP_DETAIL_STORE_CONTEXT_TOKEN, (instance) => { new UmbContextConsumerController(this.#host, UMB_MEMBER_GROUP_STORE_CONTEXT_TOKEN, (instance) => {
this.#detailStore = instance; this.#store = instance;
}); });
new UmbContextConsumerController(this.#host, UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN, (instance) => { new UmbContextConsumerController(this.#host, UMB_NOTIFICATION_CONTEXT_TOKEN, (instance) => {
this.#notificationService = instance; this.#notificationContext = instance;
}); });
} }
@@ -74,7 +74,7 @@ export class UmbMemberGroupRepository implements UmbTreeRepository, UmbDetailRep
async rootTreeItems() { async rootTreeItems() {
await this.#init; await this.#init;
return this.#treeStore!.rootItems(); return this.#treeStore!.rootItems;
} }
async treeItemsOf(parentKey: string | null) { async treeItemsOf(parentKey: string | null) {
@@ -106,7 +106,7 @@ export class UmbMemberGroupRepository implements UmbTreeRepository, UmbDetailRep
const { data, error } = await this.#detailSource.get(key); const { data, error } = await this.#detailSource.get(key);
if (data) { if (data) {
this.#detailStore?.append(data); this.#store?.append(data);
} }
return { data, error }; return { data, error };
} }
@@ -123,7 +123,7 @@ export class UmbMemberGroupRepository implements UmbTreeRepository, UmbDetailRep
if (!error) { if (!error) {
const notification = { data: { message: `Member group '${detail.name}' created` } }; const notification = { data: { message: `Member group '${detail.name}' created` } };
this.#notificationService?.peek('positive', notification); this.#notificationContext?.peek('positive', notification);
} }
return { data, error }; return { data, error };
@@ -141,10 +141,10 @@ export class UmbMemberGroupRepository implements UmbTreeRepository, UmbDetailRep
if (!error) { if (!error) {
const notification = { data: { message: `Member group '${memberGroup.name} saved` } }; const notification = { data: { message: `Member group '${memberGroup.name} saved` } };
this.#notificationService?.peek('positive', notification); this.#notificationContext?.peek('positive', notification);
} }
this.#detailStore?.append(memberGroup); this.#store?.append(memberGroup);
this.#treeStore?.updateItem(memberGroup.key, { name: memberGroup.name }); this.#treeStore?.updateItem(memberGroup.key, { name: memberGroup.name });
return { error }; return { error };
@@ -162,13 +162,13 @@ export class UmbMemberGroupRepository implements UmbTreeRepository, UmbDetailRep
if (!error) { if (!error) {
const notification = { data: { message: `Document deleted` } }; const notification = { data: { message: `Document deleted` } };
this.#notificationService?.peek('positive', notification); this.#notificationContext?.peek('positive', notification);
} }
// TODO: we currently don't use the detail store for anything. // TODO: we currently don't use the detail store for anything.
// Consider to look up the data before fetching from the server. // Consider to look up the data before fetching from the server.
// Consider notify a workspace if a template is deleted from the store while someone is editing it. // Consider notify a workspace if a template is deleted from the store while someone is editing it.
this.#detailStore?.remove([key]); this.#store?.remove([key]);
this.#treeStore?.removeItem(key); this.#treeStore?.removeItem(key);
// TODO: would be nice to align the stores on methods/methodNames. // TODO: would be nice to align the stores on methods/methodNames.

View File

@@ -6,17 +6,15 @@ import { UmbStoreBase } from '@umbraco-cms/store';
/** /**
* @export * @export
* @class UmbMemberGroupDetailStore * @class UmbMemberGroupStore
* @extends {UmbStoreBase} * @extends {UmbStoreBase}
* @description - Details Data Store for Member Groups * @description - Data Store for Member Groups
*/ */
export class UmbMemberGroupDetailStore export class UmbMemberGroupStore extends UmbStoreBase {
extends UmbStoreBase
{
#data = new ArrayState<MemberGroupDetails>([], (x) => x.key); #data = new ArrayState<MemberGroupDetails>([], (x) => x.key);
constructor(host: UmbControllerHostInterface) { constructor(host: UmbControllerHostInterface) {
super(host, UmbMemberGroupDetailStore.name); super(host, UMB_MEMBER_GROUP_STORE_CONTEXT_TOKEN.toString());
} }
append(memberGroup: MemberGroupDetails) { append(memberGroup: MemberGroupDetails) {
@@ -25,9 +23,7 @@ export class UmbMemberGroupDetailStore
remove(uniques: string[]) { remove(uniques: string[]) {
this.#data.remove(uniques); this.#data.remove(uniques);
} }
} }
export const UMB_MEMBER_GROUP_DETAIL_STORE_CONTEXT_TOKEN = new UmbContextToken<UmbMemberGroupDetailStore>( export const UMB_MEMBER_GROUP_STORE_CONTEXT_TOKEN = new UmbContextToken<UmbMemberGroupStore>('UmbMemberGroupStore');
UmbMemberGroupDetailStore.name
);

View File

@@ -1,95 +1,24 @@
import type { EntityTreeItemModel } from '@umbraco-cms/backend-api';
import { UmbContextToken } from '@umbraco-cms/context-api'; import { UmbContextToken } from '@umbraco-cms/context-api';
import { ArrayState } from '@umbraco-cms/observable-api'; import { UmbTreeStoreBase } from '@umbraco-cms/store';
import { UmbStoreBase } from '@umbraco-cms/store';
import type { UmbControllerHostInterface } from '@umbraco-cms/controller'; import type { UmbControllerHostInterface } from '@umbraco-cms/controller';
/** /**
* @export * @export
* @class UmbMemberGroupTreeStore * @class UmbMemberGroupTreeStore
* @extends {UmbStoreBase} * @extends {UmbTreeStoreBase}
* @description - Tree Data Store for Member Groups * @description - Tree Data Store for Member Groups
*/ */
export class UmbMemberGroupTreeStore extends UmbStoreBase { export class UmbMemberGroupTreeStore extends UmbTreeStoreBase {
#data = new ArrayState<EntityTreeItemModel>([], (x) => x.key);
/** /**
* Creates an instance of UmbTemplateTreeStore. * Creates an instance of UmbMemberGroupTreeStore.
* @param {UmbControllerHostInterface} host * @param {UmbControllerHostInterface} host
* @memberof UmbMemberGroupTreeStore * @memberof UmbMemberGroupTreeStore
*/ */
constructor(host: UmbControllerHostInterface) { constructor(host: UmbControllerHostInterface) {
super(host, UMB_MEMBER_GROUP_TREE_STORE_CONTEXT_TOKEN.toString()); super(host, UMB_MEMBER_GROUP_TREE_STORE_CONTEXT_TOKEN.toString());
} }
/**
* Appends items to the store
* @param {Array<EntityTreeItem>} items
* @memberof UmbTemplateTreeStore
*/
appendItems(items: Array<EntityTreeItemModel>) {
this.#data.append(items);
}
/**
* Updates an item in the store
* @param {string} key
* @param {Partial<EntityTreeItem>} data
* @memberof UmbMemberGroupTreeStore
*/
updateItem(key: string, data: Partial<EntityTreeItemModel>) {
const entries = this.#data.getValue();
const entry = entries.find((entry) => entry.key === key);
if (entry) {
this.#data.appendOne({ ...entry, ...data });
}
}
/**
* Removes an item from the store
* @param {string} key
* @memberof UmbMemberGroupTreeStore
*/
removeItem(key: string) {
const entries = this.#data.getValue();
const entry = entries.find((entry) => entry.key === key);
if (entry) {
this.#data.remove([key]);
}
}
/**
* Returns an observable to observe the root items
* @return {*}
* @memberof UmbMemberGroupTreeStore
*/
rootItems() {
return this.#data.getObservablePart((items) => items.filter((item) => item.parentKey === null));
}
/**
* Returns an observable to observe the children of a given parent
* @param {(string | null)} parentKey
* @return {*}
* @memberof UmbMemberGroupTreeStore
*/
childrenOf(parentKey: string | null) {
return this.#data.getObservablePart((items) => items.filter((item) => item.parentKey === parentKey));
}
/**
* Returns an observable to observe the items with the given keys
* @param {Array<string>} keys
* @return {*}
* @memberof UmbMemberGroupTreeStore
*/
items(keys: Array<string>) {
return this.#data.getObservablePart((items) => items.filter((item) => keys.includes(item.key ?? '')));
}
} }
export const UMB_MEMBER_GROUP_TREE_STORE_CONTEXT_TOKEN = new UmbContextToken<UmbMemberGroupTreeStore>( export const UMB_MEMBER_GROUP_TREE_STORE_CONTEXT_TOKEN = new UmbContextToken<UmbMemberGroupTreeStore>(
UmbMemberGroupTreeStore.name 'UmbMemberGroupTreeStore'
); );

View File

@@ -1,7 +1,9 @@
import { UmbMemberTypeRepository } from './member-type.repository'; import { UmbMemberTypeRepository } from './member-type.repository';
import { ManifestRepository } from 'libs/extensions-registry/repository.models'; import { UmbMemberTypeStore } from './member-type.store';
import { UmbMemberTypeTreeStore } from './member-type.tree.store';
import type { ManifestRepository, ManifestStore, ManifestTreeStore } from '@umbraco-cms/extensions-registry';
export const MEMBER_TYPES_REPOSITORY_ALIAS = 'Umb.Repository.MemberTypes'; export const MEMBER_TYPES_REPOSITORY_ALIAS = 'Umb.Repository.MemberType';
const repository: ManifestRepository = { const repository: ManifestRepository = {
type: 'repository', type: 'repository',
@@ -10,4 +12,21 @@ const repository: ManifestRepository = {
class: UmbMemberTypeRepository, class: UmbMemberTypeRepository,
}; };
export const manifests = [repository]; export const MEMBER_TYPE_STORE_ALIAS = 'Umb.Store.MemberType';
export const MEMBER_TYPE_TREE_STORE_ALIAS = 'Umb.Store.MemberTypeTree';
const store: ManifestStore = {
type: 'store',
alias: MEMBER_TYPE_STORE_ALIAS,
name: 'Member Type Store',
class: UmbMemberTypeStore,
};
const treeStore: ManifestTreeStore = {
type: 'treeStore',
alias: MEMBER_TYPE_TREE_STORE_ALIAS,
name: 'Member Type Tree Store',
class: UmbMemberTypeTreeStore,
};
export const manifests = [store, treeStore, repository];

View File

@@ -1,12 +1,12 @@
import { MemberTypeTreeServerDataSource } from './sources/member-type.tree.server.data'; import { MemberTypeTreeServerDataSource } from './sources/member-type.tree.server.data';
import { UmbMemberTypeTreeStore, UMB_MEMBER_TYPE_TREE_STORE_CONTEXT_TOKEN } from './member-type.tree.store'; import { UmbMemberTypeTreeStore, UMB_MEMBER_TYPE_TREE_STORE_CONTEXT_TOKEN } from './member-type.tree.store';
import { UmbMemberTypeDetailStore, UMB_MEMBER_TYPE_DETAIL_STORE_CONTEXT_TOKEN } from './member-type.detail.store'; import { UmbMemberTypeStore, UMB_MEMBER_TYPE_STORE_CONTEXT_TOKEN } from './member-type.store';
import { UmbMemberTypeDetailServerDataSource } from './sources/member-type.detail.server.data'; import { UmbMemberTypeDetailServerDataSource } from './sources/member-type.detail.server.data';
import { UmbControllerHostInterface } from '@umbraco-cms/controller'; import { UmbControllerHostInterface } from '@umbraco-cms/controller';
import { UmbContextConsumerController } from '@umbraco-cms/context-api'; import { UmbContextConsumerController } from '@umbraco-cms/context-api';
import { RepositoryTreeDataSource, UmbDetailRepository, UmbTreeRepository } from '@umbraco-cms/repository'; import { RepositoryTreeDataSource, UmbDetailRepository, UmbTreeRepository } from '@umbraco-cms/repository';
import { ProblemDetailsModel } from '@umbraco-cms/backend-api'; import { ProblemDetailsModel } from '@umbraco-cms/backend-api';
import { UmbNotificationService, UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN } from '@umbraco-cms/notification'; import { UmbNotificationContext, UMB_NOTIFICATION_CONTEXT_TOKEN } from '@umbraco-cms/notification';
import type { MemberTypeDetails } from '@umbraco-cms/models'; import type { MemberTypeDetails } from '@umbraco-cms/models';
// TODO => use correct type when available // TODO => use correct type when available
@@ -21,9 +21,9 @@ export class UmbMemberTypeRepository implements UmbTreeRepository, UmbDetailRepo
#treeStore?: UmbMemberTypeTreeStore; #treeStore?: UmbMemberTypeTreeStore;
#detailSource: UmbMemberTypeDetailServerDataSource; #detailSource: UmbMemberTypeDetailServerDataSource;
#detailStore?: UmbMemberTypeDetailStore; #store?: UmbMemberTypeStore;
#notificationService?: UmbNotificationService; #notificationContext?: UmbNotificationContext;
constructor(host: UmbControllerHostInterface) { constructor(host: UmbControllerHostInterface) {
this.#host = host; this.#host = host;
@@ -33,16 +33,16 @@ export class UmbMemberTypeRepository implements UmbTreeRepository, UmbDetailRepo
this.#detailSource = new UmbMemberTypeDetailServerDataSource(this.#host); this.#detailSource = new UmbMemberTypeDetailServerDataSource(this.#host);
this.#init = Promise.all([ this.#init = Promise.all([
new UmbContextConsumerController(this.#host, UMB_MEMBER_TYPE_DETAIL_STORE_CONTEXT_TOKEN, (instance) => { new UmbContextConsumerController(this.#host, UMB_MEMBER_TYPE_STORE_CONTEXT_TOKEN, (instance) => {
this.#detailStore = instance; this.#store = instance;
}), }),
new UmbContextConsumerController(this.#host, UMB_MEMBER_TYPE_TREE_STORE_CONTEXT_TOKEN, (instance) => { new UmbContextConsumerController(this.#host, UMB_MEMBER_TYPE_TREE_STORE_CONTEXT_TOKEN, (instance) => {
this.#treeStore = instance; this.#treeStore = instance;
}), }),
new UmbContextConsumerController(this.#host, UMB_NOTIFICATION_SERVICE_CONTEXT_TOKEN, (instance) => { new UmbContextConsumerController(this.#host, UMB_NOTIFICATION_CONTEXT_TOKEN, (instance) => {
this.#notificationService = instance; this.#notificationContext = instance;
}), }),
]); ]);
} }
@@ -123,7 +123,7 @@ export class UmbMemberTypeRepository implements UmbTreeRepository, UmbDetailRepo
const { data, error } = await this.#detailSource.requestByKey(key); const { data, error } = await this.#detailSource.requestByKey(key);
if (data) { if (data) {
this.#detailStore?.append(data); this.#store?.append(data);
} }
return { data, error }; return { data, error };
} }
@@ -140,13 +140,13 @@ export class UmbMemberTypeRepository implements UmbTreeRepository, UmbDetailRepo
if (!error) { if (!error) {
const notification = { data: { message: `Member type deleted` } }; const notification = { data: { message: `Member type deleted` } };
this.#notificationService?.peek('positive', notification); this.#notificationContext?.peek('positive', notification);
} }
// TODO: we currently don't use the detail store for anything. // TODO: we currently don't use the detail store for anything.
// Consider to look up the data before fetching from the server. // Consider to look up the data before fetching from the server.
// Consider notify a workspace if a member type is deleted from the store while someone is editing it. // Consider notify a workspace if a member type is deleted from the store while someone is editing it.
this.#detailStore?.remove([key]); this.#store?.remove([key]);
this.#treeStore?.removeItem(key); this.#treeStore?.removeItem(key);
// TODO: would be nice to align the stores on methods/methodNames. // TODO: would be nice to align the stores on methods/methodNames.
@@ -167,13 +167,13 @@ export class UmbMemberTypeRepository implements UmbTreeRepository, UmbDetailRepo
if (!error) { if (!error) {
const notification = { data: { message: `Member type '${detail.name}' saved` } }; const notification = { data: { message: `Member type '${detail.name}' saved` } };
this.#notificationService?.peek('positive', notification); this.#notificationContext?.peek('positive', notification);
} }
// TODO: we currently don't use the detail store for anything. // TODO: we currently don't use the detail store for anything.
// Consider to look up the data before fetching from the server // Consider to look up the data before fetching from the server
// Consider notify a workspace if a member type is updated in the store while someone is editing it. // Consider notify a workspace if a member type is updated in the store while someone is editing it.
this.#detailStore?.append(detail); this.#store?.append(detail);
this.#treeStore?.updateItem(detail.key, { name: detail.name }); this.#treeStore?.updateItem(detail.key, { name: detail.name });
// TODO: would be nice to align the stores on methods/methodNames. // TODO: would be nice to align the stores on methods/methodNames.
@@ -192,7 +192,7 @@ export class UmbMemberTypeRepository implements UmbTreeRepository, UmbDetailRepo
if (!error) { if (!error) {
const notification = { data: { message: `Member type '${detail.name}' created` } }; const notification = { data: { message: `Member type '${detail.name}' created` } };
this.#notificationService?.peek('positive', notification); this.#notificationContext?.peek('positive', notification);
} }
return { data, error }; return { data, error };

View File

@@ -6,17 +6,15 @@ import type { MemberTypeDetails } from '@umbraco-cms/models';
/** /**
* @export * @export
* @class UmbMemberTypeDetailStore * @class UmbMemberTypeStore
* @extends {UmbStoreBase} * @extends {UmbStoreBase}
* @description - Details Data Store for Member Types * @description - Data Store for Member Types
*/ */
export class UmbMemberTypeDetailStore export class UmbMemberTypeStore extends UmbStoreBase {
extends UmbStoreBase
{
#data = new ArrayState<MemberTypeDetails>([], (x) => x.key); #data = new ArrayState<MemberTypeDetails>([], (x) => x.key);
constructor(host: UmbControllerHostInterface) { constructor(host: UmbControllerHostInterface) {
super(host, UmbMemberTypeDetailStore.name); super(host, UMB_MEMBER_TYPE_STORE_CONTEXT_TOKEN.toString());
} }
append(MemberType: MemberTypeDetails) { append(MemberType: MemberTypeDetails) {
@@ -25,9 +23,7 @@ export class UmbMemberTypeDetailStore
remove(uniques: string[]) { remove(uniques: string[]) {
this.#data.remove(uniques); this.#data.remove(uniques);
} }
} }
export const UMB_MEMBER_TYPE_DETAIL_STORE_CONTEXT_TOKEN = new UmbContextToken<UmbMemberTypeDetailStore>( export const UMB_MEMBER_TYPE_STORE_CONTEXT_TOKEN = new UmbContextToken<UmbMemberTypeStore>('UmbMemberTypeStore');
UmbMemberTypeDetailStore.name
);

Some files were not shown because too many files have changed in this diff Show More