Shortcuts: Implement context and a few shortcuts of interest (#20204)
* setup files * allow Unproviding as a valid word * setup context * declare new module * clean up on destroy * implement keydown listener * rename to all * Revert "rename to all" This reverts commit 5384408d5f70111b63a5e07b9b20d6536c530c00. * revert shortcuts revert * move view initialization to submittable workspace base * comment on destroy thingy * submit workspace shortcut * rename to action * observe parent activation to make sure children follows along. * fix comment to make AI happy * implement modal view and titles * fix getting title from token * rename context alias * use controller not context here * provide modal view at modal element * implement view context at app level * Refactor view inheritance logic * reverse children to be activated loop * note on global shortcuts * additional note
This commit is contained in:
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@@ -1,5 +1,8 @@
|
||||
{
|
||||
"cSpell.words": ["unprovide"],
|
||||
"cSpell.words": [
|
||||
"unprovide",
|
||||
"Unproviding"
|
||||
],
|
||||
"eslint.useFlatConfig": true,
|
||||
"eslint.workingDirectories": [
|
||||
"./src/Umbraco.Web.UI.Client/",
|
||||
|
||||
@@ -31,9 +31,9 @@
|
||||
"./collection": "./dist-cms/packages/core/collection/index.js",
|
||||
"./components": "./dist-cms/packages/core/components/index.js",
|
||||
"./const": "./dist-cms/packages/core/const/index.js",
|
||||
"./content-picker": "./dist-cms/packages/property-editors/content-picker/index.js",
|
||||
"./content-type": "./dist-cms/packages/content/content-type/index.js",
|
||||
"./content": "./dist-cms/packages/content/content/index.js",
|
||||
"./content-picker": "./dist-cms/packages/property-editors/content-picker/index.js",
|
||||
"./culture": "./dist-cms/packages/core/culture/index.js",
|
||||
"./current-user": "./dist-cms/packages/user/current-user/index.js",
|
||||
"./dashboard": "./dist-cms/packages/core/dashboard/index.js",
|
||||
@@ -47,8 +47,8 @@
|
||||
"./entity-action": "./dist-cms/packages/core/entity-action/index.js",
|
||||
"./entity-bulk-action": "./dist-cms/packages/core/entity-bulk-action/index.js",
|
||||
"./entity-create-option-action": "./dist-cms/packages/core/entity-create-option-action/index.js",
|
||||
"./entity": "./dist-cms/packages/core/entity/index.js",
|
||||
"./entity-item": "./dist-cms/packages/core/entity-item/index.js",
|
||||
"./entity": "./dist-cms/packages/core/entity/index.js",
|
||||
"./event": "./dist-cms/packages/core/event/index.js",
|
||||
"./extension-registry": "./dist-cms/packages/core/extension-registry/index.js",
|
||||
"./health-check": "./dist-cms/packages/health-check/index.js",
|
||||
@@ -68,9 +68,9 @@
|
||||
"./media-type": "./dist-cms/packages/media/media-types/index.js",
|
||||
"./media": "./dist-cms/packages/media/media/index.js",
|
||||
"./member-group": "./dist-cms/packages/members/member-group/index.js",
|
||||
"./member-public-access": "./dist-cms/packages/members/member-public-access/index.js",
|
||||
"./member-type": "./dist-cms/packages/members/member-type/index.js",
|
||||
"./member": "./dist-cms/packages/members/member/index.js",
|
||||
"./member-public-access": "./dist-cms/packages/members/member-public-access/index.js",
|
||||
"./menu": "./dist-cms/packages/core/menu/index.js",
|
||||
"./modal": "./dist-cms/packages/core/modal/index.js",
|
||||
"./models": "./dist-cms/packages/core/models/index.js",
|
||||
@@ -96,9 +96,10 @@
|
||||
"./search": "./dist-cms/packages/search/index.js",
|
||||
"./section": "./dist-cms/packages/core/section/index.js",
|
||||
"./segment": "./dist-cms/packages/segment/index.js",
|
||||
"./server": "./dist-cms/packages/core/server/index.js",
|
||||
"./server-file-system": "./dist-cms/packages/core/server-file-system/index.js",
|
||||
"./server": "./dist-cms/packages/core/server/index.js",
|
||||
"./settings": "./dist-cms/packages/settings/index.js",
|
||||
"./shortcut": "./dist-cms/packages/core/shortcut/index.js",
|
||||
"./sorter": "./dist-cms/packages/core/sorter/index.js",
|
||||
"./static-file": "./dist-cms/packages/static-file/index.js",
|
||||
"./store": "./dist-cms/packages/core/store/index.js",
|
||||
|
||||
@@ -22,6 +22,7 @@ import { filter, first, firstValueFrom } from '@umbraco-cms/backoffice/external/
|
||||
import { hasOwnOpener, redirectToStoredPath } from '@umbraco-cms/backoffice/utils';
|
||||
import { UmbApiInterceptorController } from '@umbraco-cms/backoffice/resources';
|
||||
import { umbHttpClient } from '@umbraco-cms/backoffice/http-client';
|
||||
import { UmbViewContext } from '@umbraco-cms/backoffice/view';
|
||||
|
||||
import './app-logo.element.js';
|
||||
import './app-oauth.element.js';
|
||||
@@ -159,6 +160,8 @@ export class UmbAppElement extends UmbLitElement {
|
||||
new UmbContextDebugController(this);
|
||||
|
||||
new UmbNetworkConnectionStatusManager(this);
|
||||
|
||||
new UmbViewContext(this, null);
|
||||
}
|
||||
|
||||
override connectedCallback(): void {
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
UmbRequestReloadChildrenOfEntityEvent,
|
||||
UmbRequestReloadStructureForEntityEvent,
|
||||
} from '@umbraco-cms/backoffice/entity-action';
|
||||
import { UmbViewContext } from '@umbraco-cms/backoffice/view';
|
||||
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
|
||||
import type { Observable } from '@umbraco-cms/backoffice/observable-api';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
@@ -53,8 +52,6 @@ export abstract class UmbContentTypeWorkspaceContextBase<
|
||||
|
||||
public readonly structure: UmbContentTypeStructureManager<DetailModelType>;
|
||||
|
||||
public readonly view = new UmbViewContext(this, null);
|
||||
|
||||
constructor(host: UmbControllerHost, args: UmbContentTypeWorkspaceContextArgs) {
|
||||
super(host, args);
|
||||
|
||||
|
||||
@@ -33,7 +33,6 @@ import {
|
||||
} from '@umbraco-cms/backoffice/property';
|
||||
import { UmbSegmentCollectionRepository } from '@umbraco-cms/backoffice/segment';
|
||||
import { UmbVariantId } from '@umbraco-cms/backoffice/variant';
|
||||
import { UmbViewContext } from '@umbraco-cms/backoffice/view';
|
||||
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
|
||||
import {
|
||||
UMB_VALIDATION_CONTEXT,
|
||||
@@ -145,9 +144,6 @@ export abstract class UmbContentDetailWorkspaceContextBase<
|
||||
|
||||
readonly collection: UmbContentCollectionManager;
|
||||
|
||||
/* View */
|
||||
readonly view = new UmbViewContext(this, null);
|
||||
|
||||
/* Variant Options */
|
||||
// TODO: Optimize this so it uses either a App Language Context? [NL]
|
||||
#languageRepository = new UmbLanguageCollectionRepository(this);
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} from '@umbraco-cms/backoffice/content-type';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import { UMB_VIEW_CONTEXT, UmbViewContext } from '@umbraco-cms/backoffice/view';
|
||||
import { UMB_VIEW_CONTEXT, UmbViewController } from '@umbraco-cms/backoffice/view';
|
||||
import type {
|
||||
PageComponent,
|
||||
UmbRoute,
|
||||
@@ -31,7 +31,7 @@ export class UmbContentWorkspaceViewEditElement extends UmbLitElement implements
|
||||
@state()
|
||||
private _hasRootProperties = false;
|
||||
*/
|
||||
#viewContext?: UmbViewContext;
|
||||
#viewContext?: typeof UMB_VIEW_CONTEXT.TYPE;
|
||||
|
||||
@state()
|
||||
private _hasRootGroups = false;
|
||||
@@ -51,7 +51,7 @@ export class UmbContentWorkspaceViewEditElement extends UmbLitElement implements
|
||||
@state()
|
||||
private _hintMap: Map<string | null, UmbVariantHint> = new Map();
|
||||
|
||||
#tabViewContexts: Array<UmbViewContext> = [];
|
||||
#tabViewContexts: Array<UmbViewController> = [];
|
||||
|
||||
#structureManager?: UmbContentTypeStructureManager<UmbContentTypeModel>;
|
||||
|
||||
@@ -150,7 +150,7 @@ export class UmbContentWorkspaceViewEditElement extends UmbLitElement implements
|
||||
|
||||
#createViewContext(viewAlias: string | null, tabName: string) {
|
||||
if (!this.#tabViewContexts.find((context) => context.viewAlias === viewAlias)) {
|
||||
const view = new UmbViewContext(this, viewAlias);
|
||||
const view = new UmbViewController(this, viewAlias);
|
||||
this.#tabViewContexts.push(view);
|
||||
|
||||
if (viewAlias === null) {
|
||||
@@ -176,7 +176,7 @@ export class UmbContentWorkspaceViewEditElement extends UmbLitElement implements
|
||||
}
|
||||
}
|
||||
|
||||
#currentProvidedView?: UmbViewContext;
|
||||
#currentProvidedView?: UmbViewController;
|
||||
|
||||
#provideViewContext(viewAlias: string | null, component: PageComponent) {
|
||||
const view = this.#tabViewContexts.find((context) => context.viewAlias === viewAlias);
|
||||
@@ -188,6 +188,11 @@ export class UmbContentWorkspaceViewEditElement extends UmbLitElement implements
|
||||
throw new Error(`View context with alias ${viewAlias} not found`);
|
||||
}
|
||||
this.#currentProvidedView = view;
|
||||
// ViewAlias null is only for the root tab, therefor we can implement this hack.
|
||||
if (viewAlias === null) {
|
||||
// Specific hack for the Generic tab to only show its name if there are other tabs.
|
||||
view.setBrowserTitle(this._tabs && this._tabs?.length > 0 ? '#general_generic' : undefined);
|
||||
}
|
||||
view.provideAt(component as any);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import type {
|
||||
UmbInvariantDatasetWorkspaceContext,
|
||||
UmbRoutableWorkspaceContext,
|
||||
ManifestWorkspace,
|
||||
UmbNamableWorkspaceContext,
|
||||
} from '@umbraco-cms/backoffice/workspace';
|
||||
import {
|
||||
UmbSubmittableWorkspaceContextBase,
|
||||
@@ -26,7 +27,7 @@ type PropertyTypeDataModel = UmbPropertyTypeScaffoldModel;
|
||||
|
||||
export class UmbPropertyTypeWorkspaceContext
|
||||
extends UmbSubmittableWorkspaceContextBase<PropertyTypeDataModel>
|
||||
implements UmbInvariantDatasetWorkspaceContext, UmbRoutableWorkspaceContext
|
||||
implements UmbInvariantDatasetWorkspaceContext, UmbRoutableWorkspaceContext, UmbNamableWorkspaceContext
|
||||
{
|
||||
// Just for context token safety:
|
||||
public readonly IS_PROPERTY_TYPE_WORKSPACE_CONTEXT = true;
|
||||
@@ -62,11 +63,22 @@ export class UmbPropertyTypeWorkspaceContext
|
||||
this.validationContext = new UmbValidationContext(this);
|
||||
this.addValidationContext(this.validationContext);
|
||||
|
||||
this.observe(this.unique, (unique) => {
|
||||
if (unique) {
|
||||
this.validationContext.setDataPath(UmbDataPathPropertyTypeQuery({ id: unique }));
|
||||
}
|
||||
});
|
||||
this.observe(
|
||||
this.unique,
|
||||
(unique) => {
|
||||
if (unique) {
|
||||
this.validationContext.setDataPath(UmbDataPathPropertyTypeQuery({ id: unique }));
|
||||
}
|
||||
},
|
||||
null,
|
||||
);
|
||||
this.observe(
|
||||
this.name,
|
||||
(name) => {
|
||||
this.view.setBrowserTitle(name);
|
||||
},
|
||||
null,
|
||||
);
|
||||
|
||||
this.#init = this.consumeContext(UMB_CONTENT_TYPE_WORKSPACE_CONTEXT, (context) => {
|
||||
this.#contentTypeContext = context;
|
||||
|
||||
@@ -55,6 +55,7 @@ export class UmbModalElement extends UmbLitElement {
|
||||
}
|
||||
|
||||
this.#modalContext.addEventListener('umb:destroy', this.#onContextDestroy);
|
||||
this.#modalContext.view.provideAt(this);
|
||||
this.element = await this.#createContainerElement();
|
||||
|
||||
// Makes sure that the modal triggers the reject of the context promise when it is closed by pressing escape.
|
||||
|
||||
@@ -2,10 +2,11 @@ import { UmbModalToken } from '../token/modal-token.js';
|
||||
import type { UmbModalConfig, UmbModalType } from '../types.js';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import type { UUIModalElement, UUIModalSidebarSize } from '@umbraco-cms/backoffice/external/uui';
|
||||
import { umbDeepMerge } from '@umbraco-cms/backoffice/utils';
|
||||
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
|
||||
import { umbDeepMerge } from '@umbraco-cms/backoffice/utils';
|
||||
import { UmbId } from '@umbraco-cms/backoffice/id';
|
||||
import { UmbObjectState, UmbStringState } from '@umbraco-cms/backoffice/observable-api';
|
||||
import { UmbViewController } from '@umbraco-cms/backoffice/view';
|
||||
import { UMB_ROUTE_CONTEXT } from '@umbraco-cms/backoffice/router';
|
||||
import type { ElementLoaderProperty } from '@umbraco-cms/backoffice/extension-api';
|
||||
import type { IRouterSlot } from '@umbraco-cms/backoffice/router';
|
||||
@@ -61,6 +62,8 @@ export class UmbModalContext<
|
||||
#size = new UmbStringState<UUIModalSidebarSize>('small');
|
||||
public readonly size = this.#size.asObservable();
|
||||
|
||||
public readonly view;
|
||||
|
||||
constructor(
|
||||
host: UmbControllerHost,
|
||||
modalAlias: string | UmbModalToken<ModalData, ModalValue>,
|
||||
@@ -71,6 +74,9 @@ export class UmbModalContext<
|
||||
this.router = args.router ?? null;
|
||||
this.alias = modalAlias;
|
||||
|
||||
this.view = new UmbViewController(this, modalAlias.toString());
|
||||
|
||||
let title: string | undefined = undefined;
|
||||
let size = 'small';
|
||||
|
||||
if (this.alias instanceof UmbModalToken) {
|
||||
@@ -78,8 +84,11 @@ export class UmbModalContext<
|
||||
size = this.alias.getDefaultModal()?.size ?? size;
|
||||
this.element = this.alias.getDefaultModal()?.element || this.element;
|
||||
this.backdropBackground = this.alias.getDefaultModal()?.backdropBackground || this.backdropBackground;
|
||||
title = this.alias.getDefaultModal()?.title ?? undefined;
|
||||
}
|
||||
|
||||
this.view.setBrowserTitle(title);
|
||||
|
||||
this.type = args.modal?.type || this.type;
|
||||
size = args.modal?.size ?? size;
|
||||
this.element = args.modal?.element || this.element;
|
||||
|
||||
@@ -35,4 +35,9 @@ export interface UmbModalConfig {
|
||||
* Set the background property of the modal backdrop
|
||||
*/
|
||||
backdropBackground?: string;
|
||||
|
||||
/**
|
||||
* Set the title of the modal, this is used as Browser Title
|
||||
*/
|
||||
title?: string;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { UmbItemRepository } from './item/index.js';
|
||||
import type { UmbRepositoryItemsStatus } from './types.js';
|
||||
import { UmbDeprecation } from '@umbraco-cms/backoffice/utils';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api';
|
||||
@@ -7,7 +8,6 @@ import { UmbExtensionApiInitializer } from '@umbraco-cms/backoffice/extension-ap
|
||||
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
|
||||
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
|
||||
import { UmbEntityUpdatedEvent } from '@umbraco-cms/backoffice/entity-action';
|
||||
import type { UmbRepositoryItemsStatus } from './types.js';
|
||||
|
||||
const ObserveRepositoryAlias = Symbol();
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './shortcut.context-token.js';
|
||||
export * from './shortcut.context.js';
|
||||
export * from './shortcut.controller.js';
|
||||
@@ -0,0 +1,4 @@
|
||||
import type { UmbShortcutController } from './shortcut.controller.js';
|
||||
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
|
||||
|
||||
export const UMB_SHORTCUT_CONTEXT = new UmbContextToken<UmbShortcutController>('UmbShortcutContext');
|
||||
@@ -0,0 +1,10 @@
|
||||
import { UMB_SHORTCUT_CONTEXT } from './shortcut.context-token.js';
|
||||
import { UmbShortcutController } from './shortcut.controller.js';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
|
||||
export class UmbShortcutContext extends UmbShortcutController {
|
||||
constructor(host: UmbControllerHost) {
|
||||
super(host);
|
||||
this.provideContext(UMB_SHORTCUT_CONTEXT, this as unknown as UmbShortcutContext);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
import type { UmbShortcut } from '../types.js';
|
||||
import { UMB_SHORTCUT_CONTEXT } from './shortcut.context-token.js';
|
||||
import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api';
|
||||
import { UmbControllerBase, type UmbClassInterface } from '@umbraco-cms/backoffice/class-api';
|
||||
import type { UmbContextProviderController } from '@umbraco-cms/backoffice/context-api';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import type { UmbPartialSome } from '@umbraco-cms/backoffice/utils';
|
||||
|
||||
type IncomingShortcutType = UmbPartialSome<UmbShortcut, 'unique' | 'weight' | 'modifier' | 'shift' | 'alt'>;
|
||||
|
||||
const IsMac = navigator.userAgent ? /Mac/i.test(navigator.userAgent) : navigator.platform.toUpperCase().includes('MAC');
|
||||
|
||||
export class UmbShortcutController extends UmbControllerBase {
|
||||
//
|
||||
#inUnprovidingState = false;
|
||||
|
||||
#parent?: UmbShortcutController;
|
||||
|
||||
readonly #shortcuts = new UmbArrayState<UmbShortcut>([], (x) => x.unique);
|
||||
public readonly all = this.#shortcuts.asObservable();
|
||||
|
||||
constructor(host: UmbControllerHost) {
|
||||
super(host);
|
||||
|
||||
this.#shortcuts.sortBy((a, b) => (b.weight || 0) - (a.weight || 0));
|
||||
}
|
||||
|
||||
#providerCtrl?: UmbContextProviderController;
|
||||
#currentProvideHost?: UmbClassInterface;
|
||||
/**
|
||||
* Provide this validation context to a specific controller host.
|
||||
* This can be used to Host a validation context in a Workspace, but provide it on a certain scope, like a specific Workspace View.
|
||||
* @param {UmbClassInterface} controllerHost - The controller host to provide this validation context to.
|
||||
*/
|
||||
provideAt(controllerHost: UmbClassInterface): void {
|
||||
if (this.#currentProvideHost === controllerHost) return;
|
||||
|
||||
this.unprovide();
|
||||
|
||||
this.#currentProvideHost = controllerHost;
|
||||
this.#providerCtrl = controllerHost.provideContext(UMB_SHORTCUT_CONTEXT, this as any);
|
||||
}
|
||||
|
||||
unprovide(): void {
|
||||
if (this.#providerCtrl) {
|
||||
// We need to set this in Unprovide state, so this context can be provided again later.
|
||||
this.#inUnprovidingState = true;
|
||||
this.#providerCtrl.destroy();
|
||||
this.#providerCtrl = undefined;
|
||||
this.#inUnprovidingState = false;
|
||||
this.#currentProvideHost = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
inherit(): void {
|
||||
this.consumeContext(UMB_SHORTCUT_CONTEXT, (parent) => {
|
||||
this.inheritFrom(parent);
|
||||
}).skipHost();
|
||||
// Notice skipHost ^^, this is because we do not want it to consume it self, as this would be a match for this consumption, instead we will look at the parent and above. [NL]
|
||||
}
|
||||
|
||||
inheritFrom(parent: UmbShortcutController | undefined): void {
|
||||
if (this.#parent === parent) return;
|
||||
this.#parent = parent;
|
||||
}
|
||||
|
||||
initiateChange() {
|
||||
this.#shortcuts.mute();
|
||||
}
|
||||
finishChange() {
|
||||
this.#shortcuts.unmute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new hint
|
||||
* @param {IncomingShortcutType} shortcut - The hint to add
|
||||
* @returns {UmbShortcut['unique']} Unique value of the hint
|
||||
*/
|
||||
addOne(shortcut: IncomingShortcutType): string | symbol {
|
||||
const newShortcut = { ...shortcut } as unknown as UmbShortcut;
|
||||
newShortcut.unique ??= Symbol();
|
||||
newShortcut.weight ??= 0;
|
||||
newShortcut.modifier ??= false;
|
||||
newShortcut.shift ??= false;
|
||||
newShortcut.alt ??= false;
|
||||
this.#shortcuts.appendOne(newShortcut);
|
||||
return shortcut.unique!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add multiple rules
|
||||
* @param {IncomingShortcutType[]} shortcuts - Array of hints to add
|
||||
*/
|
||||
add(shortcuts: IncomingShortcutType[]) {
|
||||
this.#shortcuts.mute();
|
||||
shortcuts.forEach((hint) => this.addOne(hint));
|
||||
this.#shortcuts.unmute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a hint
|
||||
* @param {UmbShortcut['unique']} unique Unique value of the hint to remove
|
||||
*/
|
||||
removeOne(unique: UmbShortcut['unique']) {
|
||||
this.#shortcuts.removeOne(unique);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove multiple hints
|
||||
* @param {UmbShortcut['unique'][]} uniques Array of unique values to remove
|
||||
*/
|
||||
remove(uniques: UmbShortcut['unique'][]) {
|
||||
this.#shortcuts.remove(uniques);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a hint exists
|
||||
* @param {UmbShortcut['unique']} unique Unique value of the hint to check
|
||||
* @returns {boolean} True if the hint exists, false otherwise
|
||||
*/
|
||||
has(unique: UmbShortcut['unique']): boolean {
|
||||
return this.#shortcuts.getHasOne(unique);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all hints
|
||||
* @returns {UmbShortcut[]} Array of hints
|
||||
*/
|
||||
getAll(): UmbShortcut[] {
|
||||
return this.#shortcuts.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all hints
|
||||
* @param key
|
||||
* @param modifier
|
||||
* @param shift
|
||||
* @param alt
|
||||
* @returns {UmbShortcut[]} Array of hints
|
||||
*/
|
||||
findShortcut(key: string, modifier: boolean, shift: boolean = false, alt: boolean = false): UmbShortcut | undefined {
|
||||
const shortcuts = this.#shortcuts.getValue();
|
||||
for (const s of shortcuts) {
|
||||
if (s.key.toLowerCase() === key.toLowerCase() && s.modifier === modifier && s.shift === shift && s.alt === alt) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all hints
|
||||
*/
|
||||
clear(): void {
|
||||
this.#shortcuts.setValue([]);
|
||||
}
|
||||
|
||||
activate() {
|
||||
window.addEventListener('keydown', this.#onKeyDown);
|
||||
}
|
||||
|
||||
deactivate() {
|
||||
window.removeEventListener('keydown', this.#onKeyDown);
|
||||
}
|
||||
|
||||
#onKeyDown = (e: KeyboardEvent) => {
|
||||
const keyDown = e.key.toLowerCase();
|
||||
const modifierDown = IsMac ? e.metaKey : e.ctrlKey;
|
||||
|
||||
const shortcut = this.findShortcut(keyDown, modifierDown, e.shiftKey, e.altKey);
|
||||
if (shortcut) {
|
||||
e.preventDefault();
|
||||
shortcut.action();
|
||||
}
|
||||
};
|
||||
|
||||
override destroy(): void {
|
||||
super.destroy();
|
||||
if (this.#inUnprovidingState === true) {
|
||||
// TODO: What is it i'm doing here, check if it actually makes sense, if so add a comment on why [NL]
|
||||
return;
|
||||
}
|
||||
this.unprovide();
|
||||
this.#parent = undefined;
|
||||
|
||||
this.#shortcuts.destroy();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './context/index.js';
|
||||
export type * from './types.js';
|
||||
@@ -0,0 +1,12 @@
|
||||
export interface UmbShortcut {
|
||||
unique: string | symbol;
|
||||
key: string;
|
||||
modifier: boolean;
|
||||
shift: boolean;
|
||||
alt: boolean;
|
||||
label?: string;
|
||||
weight?: number;
|
||||
action: () => void | Promise<void>;
|
||||
// TODO: Consider implementing a global option, to make a shortcut be available despite children setting up their own inheritance scopes. [NL]
|
||||
// TODO: Addition thought, also a bit dangerous cause how do you know the interest of the children. [NL]
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './view.controller.js';
|
||||
export * from './view.context.js';
|
||||
export * from './view.context-token.js';
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import { UmbShortcutController } from '../../shortcut/context/shortcut.controller.js';
|
||||
import { UMB_VIEW_CONTEXT } from './view.context-token.js';
|
||||
import {
|
||||
UmbBooleanState,
|
||||
UmbClassState,
|
||||
UmbStringState,
|
||||
mergeObservables,
|
||||
} from '@umbraco-cms/backoffice/observable-api';
|
||||
import { UmbClassState, UmbStringState, mergeObservables } from '@umbraco-cms/backoffice/observable-api';
|
||||
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
|
||||
import { UmbHintController } from '@umbraco-cms/backoffice/hint';
|
||||
import { UmbLocalizationController } from '@umbraco-cms/backoffice/localization-api';
|
||||
@@ -14,12 +10,7 @@ import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import type { UmbVariantHint } from '@umbraco-cms/backoffice/hint';
|
||||
import type { UmbVariantId } from '@umbraco-cms/backoffice/variant';
|
||||
|
||||
const ObserveParentActiveCtrlAlias = Symbol();
|
||||
|
||||
/**
|
||||
*
|
||||
* TODO:
|
||||
* Include Shortcuts
|
||||
*
|
||||
* The View Context handles the aspects of three Features:
|
||||
* Browser Titles — Provide a title for this view and it will be set or joint with parent views depending on the inheritance setting.
|
||||
@@ -28,6 +19,8 @@ const ObserveParentActiveCtrlAlias = Symbol();
|
||||
*
|
||||
*/
|
||||
export class UmbViewController extends UmbControllerBase {
|
||||
//
|
||||
static #ActiveView?: UmbViewController;
|
||||
//
|
||||
#attached = false;
|
||||
#providerCtrl?: UmbContextProviderController;
|
||||
@@ -37,13 +30,34 @@ export class UmbViewController extends UmbControllerBase {
|
||||
|
||||
// State used to know if the context can be auto activated when attached.
|
||||
#autoActivate = true;
|
||||
#active = new UmbBooleanState(false);
|
||||
public readonly active = this.#active.asObservable();
|
||||
#active = false;
|
||||
get isActive() {
|
||||
return this.#active.getValue();
|
||||
return this.#active;
|
||||
}
|
||||
#hasActiveChild = false;
|
||||
#inherit?: boolean;
|
||||
#setActive() {
|
||||
this.#active = true;
|
||||
if (this.#inherit) {
|
||||
// Secure the parent in the inheritance chain is active.
|
||||
this.#parentView?._internal_activate();
|
||||
} else {
|
||||
// This is for a single, or top level of the inheritance chain, so we can disable the previous active view.
|
||||
if (UmbViewController.#ActiveView && UmbViewController.#ActiveView !== this) {
|
||||
UmbViewController.#ActiveView._internal_deactivate();
|
||||
UmbViewController.#ActiveView = undefined;
|
||||
}
|
||||
UmbViewController.#ActiveView = this;
|
||||
}
|
||||
}
|
||||
#removeActive() {
|
||||
this.#active = false;
|
||||
if (!this.#inherit) {
|
||||
if (UmbViewController.#ActiveView === this) {
|
||||
UmbViewController.#ActiveView = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#inherit = false;
|
||||
#explicitInheritance?: boolean;
|
||||
#parentView?: UmbViewController;
|
||||
#title?: string;
|
||||
@@ -55,9 +69,11 @@ export class UmbViewController extends UmbControllerBase {
|
||||
#variantId = new UmbClassState<UmbVariantId | undefined>(undefined);
|
||||
protected readonly variantId = this.#variantId.asObservable();
|
||||
|
||||
public hints;
|
||||
public readonly hints;
|
||||
|
||||
readonly firstHintOfVariant;
|
||||
public readonly shortcuts = new UmbShortcutController(this);
|
||||
|
||||
public readonly firstHintOfVariant;
|
||||
|
||||
constructor(host: UmbControllerHost, viewAlias: string | null) {
|
||||
super(host);
|
||||
@@ -79,24 +95,25 @@ export class UmbViewController extends UmbControllerBase {
|
||||
this.#consumeParentCtrl = this.consumeContext(UMB_VIEW_CONTEXT, (parentView) => {
|
||||
// In case of explicit inheritance we do not want to overview the parent view.
|
||||
if (this.#explicitInheritance) return;
|
||||
if (this.isActive && !this.#hasActiveChild) {
|
||||
// If we were active we will react as if we got deactivated and then activated again below if state allows. [NL]
|
||||
this.#propagateActivation();
|
||||
}
|
||||
this.#active.setValue(false);
|
||||
if (parentView) {
|
||||
this.#parentView = parentView;
|
||||
}
|
||||
if (this.#inherit) {
|
||||
this.#inheritFromParent();
|
||||
this.#setParentView(parentView);
|
||||
}
|
||||
// only activate if we had an incoming parentView, cause if not we are in a disassembling state. [NL]
|
||||
if (parentView && this.#attached && this.#autoActivate) {
|
||||
this._internal_activate();
|
||||
this._internal_requestActivate();
|
||||
}
|
||||
}).skipHost();
|
||||
}
|
||||
|
||||
#setParentView(view: UmbViewController | undefined) {
|
||||
if (this.#parentView === view) return;
|
||||
this.#parentView = view;
|
||||
|
||||
if (this.#inherit) {
|
||||
this.#inheritFromParent();
|
||||
}
|
||||
}
|
||||
|
||||
public setVariantId(variantId: UmbVariantId | undefined): void {
|
||||
this.#variantId.setValue(variantId);
|
||||
this.hints.updateScaffold({ variantId: variantId });
|
||||
@@ -105,7 +122,6 @@ export class UmbViewController extends UmbControllerBase {
|
||||
public setBrowserTitle(title: string | undefined): void {
|
||||
if (this.#title === title) return;
|
||||
this.#title = title;
|
||||
// TODO: This check should be if its the most child being active, but again think about how the parents in the active chain should work.
|
||||
this.#computeTitle();
|
||||
this.#updateTitle();
|
||||
}
|
||||
@@ -119,9 +135,10 @@ export class UmbViewController extends UmbControllerBase {
|
||||
this.#currentProvideHost = controllerHost;
|
||||
this.#providerCtrl = controllerHost.provideContext(UMB_VIEW_CONTEXT, this);
|
||||
this.hints.provideAt(controllerHost);
|
||||
this.shortcuts.provideAt(controllerHost);
|
||||
|
||||
if (this.#attached && this.#autoActivate) {
|
||||
this._internal_activate();
|
||||
if (this.#attached) {
|
||||
this._internal_requestActivate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,30 +148,41 @@ export class UmbViewController extends UmbControllerBase {
|
||||
this.#providerCtrl = undefined;
|
||||
}
|
||||
this.hints.unprovide();
|
||||
this.shortcuts.unprovide();
|
||||
|
||||
this._internal_deactivate();
|
||||
this.#requestActivateParent();
|
||||
}
|
||||
|
||||
override hostConnected(): void {
|
||||
const wasActive = this.isActive;
|
||||
const wasAttached = this.#attached;
|
||||
this.#attached = true;
|
||||
super.hostConnected();
|
||||
if (!wasAttached) {
|
||||
this.#parentView?._internal_addChild(this);
|
||||
}
|
||||
// Check that we have a providerController, otherwise this is not provided. [NL]
|
||||
if (this.#autoActivate && !wasActive) {
|
||||
this._internal_activate();
|
||||
this._internal_requestActivate();
|
||||
}
|
||||
}
|
||||
|
||||
override hostDisconnected(): void {
|
||||
const wasAttached = this.#attached;
|
||||
const wasActive = this.isActive;
|
||||
this.#attached = false;
|
||||
this.#active.setValue(false);
|
||||
super.hostDisconnected();
|
||||
if (wasAttached === true && wasActive) {
|
||||
// Check that we have a providerController, otherwise this is not provided. [NL]
|
||||
this.#propagateActivation();
|
||||
if (wasAttached) {
|
||||
this.#parentView?._internal_removeChild(this);
|
||||
}
|
||||
|
||||
this._internal_deactivate();
|
||||
super.hostDisconnected();
|
||||
this.#autoActivate = true;
|
||||
this.#requestActivateParent();
|
||||
}
|
||||
|
||||
public isInheriting() {
|
||||
return this.#inherit;
|
||||
}
|
||||
|
||||
public inherit() {
|
||||
@@ -166,21 +194,7 @@ export class UmbViewController extends UmbControllerBase {
|
||||
this.#explicitInheritance = true;
|
||||
this.#consumeParentCtrl?.destroy();
|
||||
this.#consumeParentCtrl = undefined;
|
||||
this.#parentView = context;
|
||||
// Notice because we cannot break the inheritance, we do not need to stop this observation in any of the logic. [NL]
|
||||
this.observe(
|
||||
this.#parentView?.active,
|
||||
(isActive) => {
|
||||
if (isActive) {
|
||||
this._internal_activate();
|
||||
} else {
|
||||
this._internal_deactivate();
|
||||
}
|
||||
},
|
||||
ObserveParentActiveCtrlAlias,
|
||||
);
|
||||
this.#inheritFromParent();
|
||||
this.#propagateActivation();
|
||||
this.#setParentView(context);
|
||||
}
|
||||
|
||||
#inheritFromParent(): void {
|
||||
@@ -205,19 +219,10 @@ export class UmbViewController extends UmbControllerBase {
|
||||
this.hints.inheritFrom(this.#parentView?.hints);
|
||||
}
|
||||
|
||||
#propagateActivation() {
|
||||
if (!this.#parentView) return;
|
||||
if (this.#inherit) {
|
||||
if (this.isActive) {
|
||||
this.#parentView._internal_childActivated();
|
||||
} else {
|
||||
this.#parentView._internal_childDeactivated();
|
||||
}
|
||||
} else {
|
||||
if (this.isActive) {
|
||||
this.#parentView._internal_deactivate();
|
||||
} else {
|
||||
this.#parentView._internal_activate();
|
||||
#requestActivateParent() {
|
||||
if (!this.#inherit) {
|
||||
if (this.#parentView) {
|
||||
this.#parentView._internal_requestActivate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -227,58 +232,52 @@ export class UmbViewController extends UmbControllerBase {
|
||||
* Notify that a view context has been activated.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
public _internal_activate() {
|
||||
public _internal_requestActivate(): boolean {
|
||||
if (!this.#providerCtrl) {
|
||||
// If we are not provided we should not be activated. [NL]
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
// TODO: Check this one: We do not want a parent to auto activate if a child is having the activation. [NL], well maybe it not that bad because of the asking of the children...
|
||||
this.#autoActivate = true;
|
||||
if (this.isActive) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
// If not attached then propagate the activation to the parent. [NL]
|
||||
if (this.#attached === false) {
|
||||
if (!this.#parentView) {
|
||||
throw new Error('Cannot activate a view that is not attached to the DOM.');
|
||||
}
|
||||
this.#propagateActivation();
|
||||
} else {
|
||||
this.#active.setValue(true);
|
||||
this.#propagateActivation();
|
||||
this.#updateTitle();
|
||||
// TODO: Start shortcuts. [NL]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* Notify that a child has been activated.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
public _internal_childActivated() {
|
||||
if (this.#hasActiveChild) return;
|
||||
this.#hasActiveChild = true;
|
||||
this._internal_activate();
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* Notify that a child is no longer activated.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
public _internal_childDeactivated() {
|
||||
this.#hasActiveChild = false;
|
||||
if (this.#attached === false) {
|
||||
if (this.#parentView) {
|
||||
return;
|
||||
} else {
|
||||
throw new Error('Cannot re-activate(_childDeactivated) a view that is not attached to the DOM.');
|
||||
// Check if any of the children likes to be activated instead:
|
||||
// A reverse loop ensures latest added child gets first chance to activate. This may matter in some future issue-scenario, I will say it could be that it is not the right way to determine if multiple children wants to be active. [NL]
|
||||
let i = this.#children.length;
|
||||
while (i--) {
|
||||
const child = this.#children[i];
|
||||
if (child._internal_requestActivate()) {
|
||||
// If we have an active child we should not update the title.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// if not then check your self:
|
||||
if (this.#autoActivate && this.#attached) {
|
||||
this._internal_activate();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (this.#autoActivate) {
|
||||
this._internal_activate();
|
||||
} else {
|
||||
this.#propagateActivation();
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* Notify that a view context has been activated.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
public _internal_activate() {
|
||||
if (this.#attached) {
|
||||
this.#autoActivate = true;
|
||||
this.#setActive();
|
||||
this.#updateTitle();
|
||||
this.shortcuts.activate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,16 +288,21 @@ export class UmbViewController extends UmbControllerBase {
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
public _internal_deactivate() {
|
||||
this.#autoActivate = false;
|
||||
if (!this.isActive) return;
|
||||
this.#active.setValue(false);
|
||||
// TODO: Stop shortcuts. [NL]
|
||||
// Deactivate parents:
|
||||
this.#propagateActivation();
|
||||
this.#autoActivate = false;
|
||||
|
||||
// Deactive children:
|
||||
this.#children.forEach((child) => {
|
||||
if (child.isInheriting()) {
|
||||
child._internal_deactivate();
|
||||
}
|
||||
});
|
||||
this.shortcuts.deactivate();
|
||||
this.#removeActive();
|
||||
}
|
||||
|
||||
#updateTitle() {
|
||||
if (!this.#active || this.#hasActiveChild) {
|
||||
if (!this.#active || this.#hasActiveChildren()) {
|
||||
return;
|
||||
}
|
||||
const localTitle = this.getComputedTitle();
|
||||
@@ -320,9 +324,32 @@ export class UmbViewController extends UmbControllerBase {
|
||||
return this.#computedTitle.getValue();
|
||||
}
|
||||
|
||||
#children: UmbViewController[] = [];
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
public _internal_addChild(child: UmbViewController) {
|
||||
this.#children.push(child);
|
||||
if (this.isActive) {
|
||||
child._internal_activate();
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
public _internal_removeChild(child: UmbViewController) {
|
||||
const index = this.#children.indexOf(child);
|
||||
if (index !== -1) {
|
||||
this.#children.splice(index, 1);
|
||||
}
|
||||
// update title?
|
||||
if (this.#active && !this.#hasActiveChildren()) {
|
||||
this.#updateTitle();
|
||||
}
|
||||
}
|
||||
#hasActiveChildren() {
|
||||
return this.#children.some((child) => child.isActive);
|
||||
}
|
||||
|
||||
override destroy(): void {
|
||||
this.#inherit = false;
|
||||
this.#active.setValue(false);
|
||||
this.#removeActive();
|
||||
this.#autoActivate = false;
|
||||
(this as any).provideAt = undefined;
|
||||
this.unprovide();
|
||||
|
||||
@@ -26,18 +26,18 @@ export default defineConfig({
|
||||
'entity-action/index': './entity-action/index.ts',
|
||||
'entity-bulk-action/index': './entity-bulk-action/index.ts',
|
||||
'entity-create-option-action/index': './entity-create-option-action/index.ts',
|
||||
'entity/index': './entity/index.ts',
|
||||
'entity-item/index': './entity-item/index.ts',
|
||||
'entity/index': './entity/index.ts',
|
||||
'entry-point': 'entry-point.ts',
|
||||
'event/index': './event/index.ts',
|
||||
'extension-registry/index': './extension-registry/index.ts',
|
||||
'http-client/index': './http-client/index.ts',
|
||||
'hint/index': './hint/index.ts',
|
||||
'http-client/index': './http-client/index.ts',
|
||||
'icon-registry/index': './icon-registry/index.ts',
|
||||
'id/index': './id/index.ts',
|
||||
'interaction-memory/index': './interaction-memory/index.ts',
|
||||
'lit-element/index': './lit-element/index.ts',
|
||||
'localization/index': './localization/index.ts',
|
||||
'interaction-memory/index': './interaction-memory/index.ts',
|
||||
'menu/index': './menu/index.ts',
|
||||
'modal/index': './modal/index.ts',
|
||||
'models/index': './models/index.ts',
|
||||
@@ -53,8 +53,9 @@ export default defineConfig({
|
||||
'resources/index': './resources/index.ts',
|
||||
'router/index': './router/index.ts',
|
||||
'section/index': './section/index.ts',
|
||||
'server/index': './server/index.ts',
|
||||
'server-file-system/index': './server-file-system/index.ts',
|
||||
'server/index': './server/index.ts',
|
||||
'shortcut/index': './shortcut/index.ts',
|
||||
'sorter/index': './sorter/index.ts',
|
||||
'store/index': './store/index.ts',
|
||||
'style/index': './style/index.ts',
|
||||
|
||||
@@ -2,7 +2,6 @@ import type { UmbNamableWorkspaceContext } from '../types.js';
|
||||
import { UmbNameWriteGuardManager } from '../namable/index.js';
|
||||
import { UmbEntityDetailWorkspaceContextBase } from './entity-detail-workspace-base.js';
|
||||
import type { UmbEntityDetailWorkspaceContextArgs, UmbEntityDetailWorkspaceContextCreateArgs } from './types.js';
|
||||
import { UmbViewContext } from '@umbraco-cms/backoffice/view';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import type { UmbDetailRepository } from '@umbraco-cms/backoffice/repository';
|
||||
import type { UmbNamedEntityModel } from '@umbraco-cms/backoffice/entity';
|
||||
@@ -24,8 +23,6 @@ export abstract class UmbEntityNamedDetailWorkspaceContextBase<
|
||||
|
||||
public readonly nameWriteGuard = new UmbNameWriteGuardManager(this);
|
||||
|
||||
public readonly view = new UmbViewContext(this, null);
|
||||
|
||||
constructor(host: UmbControllerHost, args: UmbEntityDetailWorkspaceContextArgs) {
|
||||
super(host, args);
|
||||
this.nameWriteGuard.fallbackToPermitted();
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
import { UMB_WORKSPACE_CONTEXT } from '../../workspace.context-token.js';
|
||||
import type { UmbWorkspaceContext } from '../../workspace-context.interface.js';
|
||||
import type { ManifestWorkspace } from '../../extensions/types.js';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
|
||||
import { UmbEntityContext, type UmbEntityUnique } from '@umbraco-cms/backoffice/entity';
|
||||
import { UmbViewContext } from '@umbraco-cms/backoffice/view';
|
||||
import type { ManifestWorkspaceDefaultKind } from './types.js';
|
||||
|
||||
export class UmbDefaultWorkspaceContext extends UmbContextBase implements UmbWorkspaceContext {
|
||||
public workspaceAlias!: string;
|
||||
|
||||
#entityContext = new UmbEntityContext(this);
|
||||
|
||||
public readonly view = new UmbViewContext(this, null);
|
||||
|
||||
constructor(host: UmbControllerHost) {
|
||||
super(host, UMB_WORKSPACE_CONTEXT.toString());
|
||||
}
|
||||
|
||||
set manifest(manifest: ManifestWorkspace) {
|
||||
set manifest(manifest: ManifestWorkspaceDefaultKind) {
|
||||
this.workspaceAlias = manifest.alias;
|
||||
this.setEntityType(manifest.meta.entityType);
|
||||
this.view.setBrowserTitle(manifest.meta.headline);
|
||||
}
|
||||
|
||||
setUnique(unique: UmbEntityUnique): void {
|
||||
|
||||
@@ -8,6 +8,7 @@ import type { UmbModalContext } from '@umbraco-cms/backoffice/modal';
|
||||
import { UMB_MODAL_CONTEXT } from '@umbraco-cms/backoffice/modal';
|
||||
import type { Observable } from '@umbraco-cms/backoffice/external/rxjs';
|
||||
import type { UmbValidationController } from '@umbraco-cms/backoffice/validation';
|
||||
import { UmbViewContext } from '@umbraco-cms/backoffice/view';
|
||||
|
||||
export abstract class UmbSubmittableWorkspaceContextBase<WorkspaceDataModelType>
|
||||
extends UmbContextBase
|
||||
@@ -20,6 +21,8 @@ export abstract class UmbSubmittableWorkspaceContextBase<WorkspaceDataModelType>
|
||||
|
||||
#validationContexts: Array<UmbValidationController> = [];
|
||||
|
||||
public readonly view = new UmbViewContext(this, null);
|
||||
|
||||
/**
|
||||
* Appends a validation context to the workspace.
|
||||
* @param context
|
||||
@@ -54,6 +57,13 @@ export abstract class UmbSubmittableWorkspaceContextBase<WorkspaceDataModelType>
|
||||
this.consumeContext(UMB_MODAL_CONTEXT, (context) => {
|
||||
(this.modalContext as UmbModalContext | undefined) = context;
|
||||
});
|
||||
|
||||
this.view.shortcuts.addOne({
|
||||
key: 's',
|
||||
modifier: true,
|
||||
action: () => this.requestSubmit(),
|
||||
label: '#general_submit',
|
||||
});
|
||||
}
|
||||
|
||||
protected resetState() {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import type { UmbDocumentVariantOptionModel } from '../../types.js';
|
||||
import type { UmbDocumentSaveModalData, UmbDocumentSaveModalValue } from './document-save-modal.token.js';
|
||||
import { css, customElement, html, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { umbFocus } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import { UmbSelectionManager } from '@umbraco-cms/backoffice/utils';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
|
||||
import '../shared/document-variant-language-picker.element.js';
|
||||
|
||||
@@ -56,21 +57,23 @@ export class UmbDocumentSaveModalElement extends UmbModalBaseElement<
|
||||
}
|
||||
|
||||
override render() {
|
||||
return html`<uui-dialog-layout headline=${this.localize.term('content_saveModalTitle')}>
|
||||
<umb-document-variant-language-picker
|
||||
.selectionManager=${this.#selectionManager}
|
||||
.variantLanguageOptions=${this._options}
|
||||
.pickableFilter=${this.#pickableFilter}></umb-document-variant-language-picker>
|
||||
|
||||
<div slot="actions">
|
||||
<uui-button label=${this.localize.term('general_close')} @click=${this.#close}></uui-button>
|
||||
<uui-button
|
||||
label="${this.localize.term('buttons_save')}"
|
||||
look="primary"
|
||||
color="positive"
|
||||
@click=${this.#submit}></uui-button>
|
||||
</div>
|
||||
</uui-dialog-layout> `;
|
||||
return html`
|
||||
<uui-dialog-layout headline=${this.localize.term('content_saveModalTitle')}>
|
||||
<umb-document-variant-language-picker
|
||||
.selectionManager=${this.#selectionManager}
|
||||
.variantLanguageOptions=${this._options}
|
||||
.pickableFilter=${this.#pickableFilter}></umb-document-variant-language-picker>
|
||||
<div slot="actions">
|
||||
<uui-button label=${this.localize.term('general_close')} @click=${this.#close}></uui-button>
|
||||
<uui-button
|
||||
${umbFocus()}
|
||||
label="${this.localize.term('buttons_save')}"
|
||||
look="primary"
|
||||
color="positive"
|
||||
@click=${this.#submit}></uui-button>
|
||||
</div>
|
||||
</uui-dialog-layout>
|
||||
`;
|
||||
}
|
||||
|
||||
static override styles = [
|
||||
|
||||
@@ -2,9 +2,10 @@ import { UmbDocumentVariantState, type UmbDocumentVariantOptionModel } from '../
|
||||
import { isNotPublishedMandatory } from '../../utils.js';
|
||||
import type { UmbDocumentPublishModalData, UmbDocumentPublishModalValue } from './document-publish-modal.token.js';
|
||||
import { css, customElement, html, state, when } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { umbFocus } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import { UmbSelectionManager } from '@umbraco-cms/backoffice/utils';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
|
||||
import '../../../modals/shared/document-variant-language-picker.element.js';
|
||||
|
||||
@@ -103,33 +104,34 @@ export class UmbDocumentPublishModalElement extends UmbModalBaseElement<
|
||||
override render() {
|
||||
const headline = this.data?.headline ?? this.localize.term('content_publishModalTitle');
|
||||
|
||||
return html`<uui-dialog-layout headline=${headline}>
|
||||
<p>
|
||||
<umb-localize key="prompt_confirmPublish"></umb-localize>
|
||||
</p>
|
||||
return html`
|
||||
<uui-dialog-layout headline=${headline}>
|
||||
<p><umb-localize key="prompt_confirmPublish"></umb-localize></p>
|
||||
|
||||
${when(
|
||||
!this._isInvariant,
|
||||
() =>
|
||||
html` <umb-document-variant-language-picker
|
||||
.selectionManager=${this.#selectionManager}
|
||||
.variantLanguageOptions=${this._options}
|
||||
.requiredFilter=${isNotPublishedMandatory}
|
||||
.pickableFilter=${this.#pickableFilter}></umb-document-variant-language-picker>`,
|
||||
)}
|
||||
${when(
|
||||
!this._isInvariant,
|
||||
() =>
|
||||
html`<umb-document-variant-language-picker
|
||||
.selectionManager=${this.#selectionManager}
|
||||
.variantLanguageOptions=${this._options}
|
||||
.requiredFilter=${isNotPublishedMandatory}
|
||||
.pickableFilter=${this.#pickableFilter}></umb-document-variant-language-picker>`,
|
||||
)}
|
||||
|
||||
<div slot="actions">
|
||||
<uui-button label=${this.localize.term('general_close')} @click=${this.#close}></uui-button>
|
||||
<uui-button
|
||||
label="${this.data?.confirmLabel
|
||||
? this.localize.string(this.data.confirmLabel)
|
||||
: this.localize.term('buttons_saveAndPublish')}"
|
||||
look="primary"
|
||||
color="positive"
|
||||
?disabled=${this._hasNotSelectedMandatory}
|
||||
@click=${this.#submit}></uui-button>
|
||||
</div>
|
||||
</uui-dialog-layout>`;
|
||||
<div slot="actions">
|
||||
<uui-button label=${this.localize.term('general_close')} @click=${this.#close}></uui-button>
|
||||
<uui-button
|
||||
${umbFocus()}
|
||||
label="${this.data?.confirmLabel
|
||||
? this.localize.string(this.data.confirmLabel)
|
||||
: this.localize.term('buttons_saveAndPublish')}"
|
||||
look="primary"
|
||||
color="positive"
|
||||
?disabled=${this._hasNotSelectedMandatory}
|
||||
@click=${this.#submit}></uui-button>
|
||||
</div>
|
||||
</uui-dialog-layout>
|
||||
`;
|
||||
}
|
||||
|
||||
static override styles = [
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
export * from './document-publishing.workspace-context.token.js';
|
||||
|
||||
export const UMB_DOCUMENT_PUBLISHING_SHORTCUT_UNIQUE = 'umb-document-publishing-shortcut';
|
||||
|
||||
@@ -11,21 +11,22 @@ import { UMB_DOCUMENT_PUBLISH_WITH_DESCENDANTS_MODAL } from '../publish-with-des
|
||||
import { UMB_DOCUMENT_PUBLISH_MODAL } from '../publish/constants.js';
|
||||
import { UmbUnpublishDocumentEntityAction } from '../unpublish/index.js';
|
||||
import { UMB_DOCUMENT_PUBLISHING_WORKSPACE_CONTEXT } from './document-publishing.workspace-context.token.js';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
|
||||
import { UMB_DOCUMENT_PUBLISHING_SHORTCUT_UNIQUE } from './constants.js';
|
||||
import { firstValueFrom } from '@umbraco-cms/backoffice/external/rxjs';
|
||||
import { observeMultiple } from '@umbraco-cms/backoffice/observable-api';
|
||||
import { umbOpenModal } from '@umbraco-cms/backoffice/modal';
|
||||
import { DocumentVariantStateModel } from '@umbraco-cms/backoffice/external/backend-api';
|
||||
import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
|
||||
import { UmbLocalizationController } from '@umbraco-cms/backoffice/localization-api';
|
||||
import {
|
||||
UmbRequestReloadChildrenOfEntityEvent,
|
||||
UmbRequestReloadStructureForEntityEvent,
|
||||
} from '@umbraco-cms/backoffice/entity-action';
|
||||
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
|
||||
import { UmbVariantId } from '@umbraco-cms/backoffice/variant';
|
||||
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
|
||||
import { UMB_NOTIFICATION_CONTEXT } from '@umbraco-cms/backoffice/notification';
|
||||
import { firstValueFrom } from '@umbraco-cms/backoffice/external/rxjs';
|
||||
import { observeMultiple } from '@umbraco-cms/backoffice/observable-api';
|
||||
import { DocumentVariantStateModel } from '@umbraco-cms/backoffice/external/backend-api';
|
||||
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
|
||||
import type { UmbEntityUnique } from '@umbraco-cms/backoffice/entity';
|
||||
import { UmbLocalizationController } from '@umbraco-cms/backoffice/localization-api';
|
||||
|
||||
export class UmbDocumentPublishingWorkspaceContext extends UmbContextBase {
|
||||
/**
|
||||
@@ -48,7 +49,18 @@ export class UmbDocumentPublishingWorkspaceContext extends UmbContextBase {
|
||||
|
||||
this.#init = Promise.all([
|
||||
this.consumeContext(UMB_DOCUMENT_WORKSPACE_CONTEXT, async (context) => {
|
||||
if (this.#documentWorkspaceContext) {
|
||||
// remove shortcut:
|
||||
this.#documentWorkspaceContext.view.shortcuts.removeOne(UMB_DOCUMENT_PUBLISHING_SHORTCUT_UNIQUE);
|
||||
}
|
||||
this.#documentWorkspaceContext = context;
|
||||
this.#documentWorkspaceContext?.view.shortcuts.addOne({
|
||||
unique: UMB_DOCUMENT_PUBLISHING_SHORTCUT_UNIQUE,
|
||||
label: this.#localize.term('content_saveAndPublishShortcut'),
|
||||
key: 'p',
|
||||
modifier: true,
|
||||
action: () => this.saveAndPublish(),
|
||||
});
|
||||
this.#initPendingChanges();
|
||||
})
|
||||
.asPromise({ preventTimeout: true })
|
||||
|
||||
@@ -15,6 +15,7 @@ import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
|
||||
import { query } from '@umbraco-cms/backoffice/router';
|
||||
import type { UmbWorkspaceContext } from '@umbraco-cms/backoffice/workspace';
|
||||
import { UMB_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace';
|
||||
import { UmbViewContext } from '@umbraco-cms/backoffice/view';
|
||||
|
||||
export type UmbPoolingInterval = 0 | 2000 | 5000 | 10000 | 20000 | 30000;
|
||||
export interface UmbPoolingConfig {
|
||||
@@ -31,6 +32,8 @@ export class UmbLogViewerWorkspaceContext extends UmbContextBase implements UmbW
|
||||
public readonly workspaceAlias: string = 'Umb.Workspace.LogViewer';
|
||||
#repository: UmbLogViewerRepository;
|
||||
|
||||
public readonly view = new UmbViewContext(this, null);
|
||||
|
||||
getEntityType() {
|
||||
return 'log-viewer';
|
||||
}
|
||||
@@ -108,6 +111,8 @@ export class UmbLogViewerWorkspaceContext extends UmbContextBase implements UmbW
|
||||
// TODO: Revisit usage of workspace for this case... currently no other workspace context provides them self with their own token, we need to update UMB_APP_LOG_VIEWER_CONTEXT to become a workspace context. [NL]
|
||||
this.provideContext(UMB_WORKSPACE_CONTEXT, this);
|
||||
this.#repository = new UmbLogViewerRepository(host);
|
||||
|
||||
this.view.setBrowserTitle('#treeHeaders_logViewer');
|
||||
}
|
||||
|
||||
override hostConnected() {
|
||||
|
||||
@@ -4,5 +4,6 @@ export const UMB_CURRENT_USER_MODAL = new UmbModalToken('Umb.Modal.CurrentUser',
|
||||
modal: {
|
||||
type: 'sidebar',
|
||||
size: 'small',
|
||||
title: '#general_user',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@ export const manifests: Array<UmbExtensionManifest> = [
|
||||
type: 'workspace',
|
||||
kind: 'routable',
|
||||
alias: UMB_WEBHOOK_WORKSPACE_ALIAS,
|
||||
name: 'Webhook Root Workspace',
|
||||
name: 'Webhook Workspace',
|
||||
api: () => import('./webhook-workspace.context.js'),
|
||||
meta: {
|
||||
entityType: UMB_WEBHOOK_ENTITY_TYPE,
|
||||
|
||||
@@ -58,9 +58,9 @@ DON'T EDIT THIS FILE DIRECTLY. It is generated by /devops/tsconfig/index.js
|
||||
"@umbraco-cms/backoffice/collection": ["./src/packages/core/collection/index.ts"],
|
||||
"@umbraco-cms/backoffice/components": ["./src/packages/core/components/index.ts"],
|
||||
"@umbraco-cms/backoffice/const": ["./src/packages/core/const/index.ts"],
|
||||
"@umbraco-cms/backoffice/content-picker": ["./src/packages/property-editors/content-picker/index.ts"],
|
||||
"@umbraco-cms/backoffice/content-type": ["./src/packages/content/content-type/index.ts"],
|
||||
"@umbraco-cms/backoffice/content": ["./src/packages/content/content/index.ts"],
|
||||
"@umbraco-cms/backoffice/content-picker": ["./src/packages/property-editors/content-picker/index.ts"],
|
||||
"@umbraco-cms/backoffice/culture": ["./src/packages/core/culture/index.ts"],
|
||||
"@umbraco-cms/backoffice/current-user": ["./src/packages/user/current-user/index.ts"],
|
||||
"@umbraco-cms/backoffice/dashboard": ["./src/packages/core/dashboard/index.ts"],
|
||||
@@ -76,8 +76,8 @@ DON'T EDIT THIS FILE DIRECTLY. It is generated by /devops/tsconfig/index.js
|
||||
"@umbraco-cms/backoffice/entity-create-option-action": [
|
||||
"./src/packages/core/entity-create-option-action/index.ts"
|
||||
],
|
||||
"@umbraco-cms/backoffice/entity": ["./src/packages/core/entity/index.ts"],
|
||||
"@umbraco-cms/backoffice/entity-item": ["./src/packages/core/entity-item/index.ts"],
|
||||
"@umbraco-cms/backoffice/entity": ["./src/packages/core/entity/index.ts"],
|
||||
"@umbraco-cms/backoffice/event": ["./src/packages/core/event/index.ts"],
|
||||
"@umbraco-cms/backoffice/extension-registry": ["./src/packages/core/extension-registry/index.ts"],
|
||||
"@umbraco-cms/backoffice/health-check": ["./src/packages/health-check/index.ts"],
|
||||
@@ -97,9 +97,9 @@ DON'T EDIT THIS FILE DIRECTLY. It is generated by /devops/tsconfig/index.js
|
||||
"@umbraco-cms/backoffice/media-type": ["./src/packages/media/media-types/index.ts"],
|
||||
"@umbraco-cms/backoffice/media": ["./src/packages/media/media/index.ts"],
|
||||
"@umbraco-cms/backoffice/member-group": ["./src/packages/members/member-group/index.ts"],
|
||||
"@umbraco-cms/backoffice/member-public-access": ["./src/packages/members/member-public-access/index.ts"],
|
||||
"@umbraco-cms/backoffice/member-type": ["./src/packages/members/member-type/index.ts"],
|
||||
"@umbraco-cms/backoffice/member": ["./src/packages/members/member/index.ts"],
|
||||
"@umbraco-cms/backoffice/member-public-access": ["./src/packages/members/member-public-access/index.ts"],
|
||||
"@umbraco-cms/backoffice/menu": ["./src/packages/core/menu/index.ts"],
|
||||
"@umbraco-cms/backoffice/modal": ["./src/packages/core/modal/index.ts"],
|
||||
"@umbraco-cms/backoffice/models": ["./src/packages/core/models/index.ts"],
|
||||
@@ -125,9 +125,10 @@ DON'T EDIT THIS FILE DIRECTLY. It is generated by /devops/tsconfig/index.js
|
||||
"@umbraco-cms/backoffice/search": ["./src/packages/search/index.ts"],
|
||||
"@umbraco-cms/backoffice/section": ["./src/packages/core/section/index.ts"],
|
||||
"@umbraco-cms/backoffice/segment": ["./src/packages/segment/index.ts"],
|
||||
"@umbraco-cms/backoffice/server": ["./src/packages/core/server/index.ts"],
|
||||
"@umbraco-cms/backoffice/server-file-system": ["./src/packages/core/server-file-system/index.ts"],
|
||||
"@umbraco-cms/backoffice/server": ["./src/packages/core/server/index.ts"],
|
||||
"@umbraco-cms/backoffice/settings": ["./src/packages/settings/index.ts"],
|
||||
"@umbraco-cms/backoffice/shortcut": ["./src/packages/core/shortcut/index.ts"],
|
||||
"@umbraco-cms/backoffice/sorter": ["./src/packages/core/sorter/index.ts"],
|
||||
"@umbraco-cms/backoffice/static-file": ["./src/packages/static-file/index.ts"],
|
||||
"@umbraco-cms/backoffice/store": ["./src/packages/core/store/index.ts"],
|
||||
|
||||
Reference in New Issue
Block a user