Skip to content

Workspace view navigation context #19255

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 18 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Workspace View Badge Example

This example demonstrates the essence of the Workspace View Navigation Context. And how to append a Status that will be displayed as a badge on the Workspace Views Tab Navigation.
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { css, html, customElement, LitElement } from '@umbraco-cms/backoffice/external/lit';
import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api';
import { UMB_WORKSPACE_VIEW_NAVIGATION_CONTEXT } from '@umbraco-cms/backoffice/workspace';
import { UmbVariantId } from '@umbraco-cms/backoffice/variant';

@customElement('example-hint-workspace-view')
export class ExampleHintWorkspaceView extends UmbElementMixin(LitElement) {
//

async onClick() {
const context = await this.getContext(UMB_WORKSPACE_VIEW_NAVIGATION_CONTEXT);
if (!context) {
throw new Error('Could not find the context');
}
const view = await context.getViewContext('example.workspaceView.hint');
if (!view) {
throw new Error('Could not find the view');
}

if (view.hints.has('exampleHintFromToggleAction')) {
view.hints.removeOne('exampleHintFromToggleAction');
} else {
view.hints.addOne({
unique: 'exampleHintFromToggleAction',
text: 'Hi',
color: 'invalid',
weight: 100,
variantId: new UmbVariantId('en-US'),
});
}
}

override render() {
return html`
<uui-box class="uui-text">
<h1 class="uui-h2" style="margin-top: var(--uui-size-layout-1);">See the hint on this views tab</h1>
<p>This is toggle on/off via this button:</p>
<uui-button type="button" @click=${this.onClick} look="primary" color="positive">Toggle hint</uui-button>
</uui-box>
`;
}

static override styles = [
UmbTextStyles,
css`
:host {
display: block;
padding: var(--uui-size-layout-1);
}
`,
];
}

export { ExampleHintWorkspaceView as element };

declare global {
interface HTMLElementTagNameMap {
'example-hint-workspace-view': ExampleHintWorkspaceView;
}
}
22 changes: 22 additions & 0 deletions src/Umbraco.Web.UI.Client/examples/workspace-view-hint/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace';

export const manifests: Array<UmbExtensionManifest> = [
{
type: 'workspaceView',
name: 'Example Badge Workspace View',
alias: 'example.workspaceView.hint',
element: () => import('./hint-workspace-view.js'),
weight: 900,
meta: {
label: 'View with badge',
pathname: 'badge',
icon: 'icon-lab',
},
conditions: [
{
alias: UMB_WORKSPACE_CONDITION_ALIAS,
match: 'Umb.Workspace.Document',
},
],
},
];
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ describe('ArrayState', () => {
});
});

it('getHasOne method, return true when key exists', () => {
expect(subject.getHasOne('2')).to.be.true;
});
it('getHasOne method, return false when key does not exists', () => {
expect(subject.getHasOne('1337')).to.be.false;
});

it('filter method, removes anything that is not true of the given predicate method', (done) => {
const expectedData = [initialData[0], initialData[2]];

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { UmbBlockGridManagerContext } from './block-grid-manager.context.js';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';

// TODO: Make discriminator method for this:
// TODO: Make discriminator method for this: (Aim to do this for v.16) [NL]
export const UMB_BLOCK_GRID_MANAGER_CONTEXT = new UmbContextToken<
UmbBlockGridManagerContext,
UmbBlockGridManagerContext
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { UmbBlockGridEntriesContext } from './block-grid-entries.context.js';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';

// TODO: Make discriminator method for this:
// TODO: Make discriminator method for this: (Aim to do this for v.16) [NL]
export const UMB_BLOCK_GRID_ENTRIES_CONTEXT = new UmbContextToken<UmbBlockGridEntriesContext>('UmbBlockEntriesContext');
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { UmbBlockGridEntryContext } from './block-grid-entry.context.js';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';

// TODO: Make discriminator method for this:
// TODO: Make discriminator method for this: (Aim to do this for v.16) [NL]
export const UMB_BLOCK_GRID_ENTRY_CONTEXT = new UmbContextToken<UmbBlockGridEntryContext>('UmbBlockEntryContext');
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { UmbBlockListEntriesContext } from './block-list-entries.context.js';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';

// TODO: Make discriminator method for this:
// TODO: Make discriminator method for this: (Aim to do this for v.16) [NL]
export const UMB_BLOCK_LIST_ENTRIES_CONTEXT = new UmbContextToken<UmbBlockListEntriesContext>('UmbBlockEntriesContext');
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { UmbBlockListManagerContext } from './block-list-manager.context.js';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';

// TODO: Make discriminator method for this:
// TODO: Make discriminator method for this: (Aim to do this for v.16)
export const UMB_BLOCK_LIST_MANAGER_CONTEXT = new UmbContextToken<
UmbBlockListManagerContext,
UmbBlockListManagerContext
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { UmbBlockRteEntriesContext } from './block-rte-entries.context.js';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';

// TODO: Make discriminator method for this:
// TODO: Make discriminator method for this: (Aim to do this for v.16) [NL]
export const UMB_BLOCK_RTE_ENTRIES_CONTEXT = new UmbContextToken<UmbBlockRteEntriesContext>('UmbBlockEntriesContext');
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { UmbBlockElementPropertyDatasetContext } from './block-element-property-dataset.context.js';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';

// TODO: Use a discriminator (Aim to do this for v.16) [NL]
export const UMB_BLOCK_ELEMENT_PROPERTY_DATASET_CONTEXT = new UmbContextToken<UmbBlockElementPropertyDatasetContext>(
'UmbPropertyDatasetContext',
);
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type { UmbDetailRepository, UmbDetailRepositoryConstructor } from '@umbra
import {
UmbEntityDetailWorkspaceContextBase,
UmbWorkspaceSplitViewManager,
UmbWorkspaceViewHintManager,
type UmbEntityDetailWorkspaceContextArgs,
type UmbEntityDetailWorkspaceContextCreateArgs,
} from '@umbraco-cms/backoffice/workspace';
Expand Down Expand Up @@ -135,6 +136,9 @@ export abstract class UmbContentDetailWorkspaceContextBase<
/* Split View */
readonly splitView = new UmbWorkspaceSplitViewManager();

/* Hints */
readonly hints = new UmbWorkspaceViewHintManager(this);

/* Variant Options */
// TODO: Optimize this so it uses either a App Language Context? [NL]
#languageRepository = new UmbLanguageCollectionRepository(this);
Expand Down Expand Up @@ -818,8 +822,8 @@ export abstract class UmbContentDetailWorkspaceContextBase<

variantIds = result?.selection.map((x) => UmbVariantId.FromString(x)) ?? [];
} else {
/* If there are multiple variants but no modal token is set
we will save the variants that would have been preselected in the modal.
/* If there are multiple variants but no modal token is set
we will save the variants that would have been preselected in the modal.
These are based on the variants that have been edited */
variantIds = selected.map((x) => UmbVariantId.FromString(x));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export abstract class UmbMenuTreeStructureWorkspaceContextBase extends UmbContex

constructor(host: UmbControllerHost, args: UmbMenuTreeStructureWorkspaceContextBaseArgs) {
// TODO: set up context token
// TODO: Use UmbWorkspaceContext as Context Alias and then an additional Api Alias. (aim to do this for v.16) [NL]
super(host, 'UmbMenuStructureWorkspaceContext');
this.#args = args;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import type { UmbPartialSome } from '../type/index.js';
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
import type { UUIInterfaceColor } from '@umbraco-cms/backoffice/external/uui';
import { UmbArrayState, type Observable } from '@umbraco-cms/backoffice/observable-api';

export interface UmbIncomingHintBase {
unique?: string | symbol;
text: string;
weight?: number;
color?: UUIInterfaceColor;
}

export interface UmbHint extends UmbIncomingHintBase {
unique: string | symbol;
weight: number;
}

export class UmbHintManager<
HintType extends UmbHint = UmbHint,
IncomingHintType extends UmbIncomingHintBase = UmbPartialSome<HintType, 'unique' | 'weight'>,
> extends UmbControllerBase {
//
#scaffold?: Partial<HintType>;

protected readonly _hints = new UmbArrayState<HintType>([], (x) => x.unique);
public readonly hints = this._hints.asObservable();
public readonly firstHint = this._hints.asObservablePart((x) => x[0]);
//public readonly hasHints = this._hints.asObservablePart((x) => x.length > 0);

constructor(host: UmbControllerBase, args?: { scaffold?: Partial<HintType> }) {
super(host);

this.#scaffold = args?.scaffold;

this._hints.sortBy((a, b) => (b.weight || 0) - (a.weight || 0));
}

asObservablePart<R>(fn: (hints: HintType[]) => R): Observable<R> {
return this._hints.asObservablePart(fn);
}

#parent?: UmbHintManager;
#parentHints: HintType[] = [];
#localHints: HintType[] = [];
bindWith(parent: UmbHintManager): void {
if (this.#parent) {
this.#parentHints = [];
this.#localHints = [];
}
this.#parent = parent;
this.observe(
parent.hints,
(hints) => {
this._hints.mute();

this.#parentHints = hints as unknown as HintType[];

// Remove the local hints that does not exist in the parent anymore:
const toRemove = this.#parentHints.filter((msg) => !hints.find((m) => m.unique === msg.unique));
this._hints.remove(toRemove.map((msg) => msg.unique));
this._hints.append(this.#parentHints);
this.#localHints = this._hints.getValue();
this._hints.unmute();
},
'observeParentHints',
);
this.observe(
this.hints,
(hints) => {
if (!this.#parent) return;

this.#parent!.initiateChange();

// Remove the parent messages that does not exist locally anymore:
const toRemove = this.#localHints.filter((locals) => !hints.find((m) => m.unique === locals.unique));
this.#parent!.remove(toRemove.map((x) => x.unique));
this.#parent!.add(hints);
this.#parent!.finishChange();
},
'observeLocalHints',
);
}

initiateChange() {
this._hints.mute();
}
finishChange() {
this._hints.unmute();
}

/**
* Add a new hint
* @param {HintType} hint - The hint to add
* @returns {HintType['unique']} Unique value of the hint
*/
addOne(hint: IncomingHintType): string | symbol {
const newHint = { ...this.#scaffold, ...hint } as unknown as HintType;
newHint.unique ??= Symbol();
newHint.weight ??= 0;
newHint.text ??= '!';
this._hints.appendOne(newHint);
return hint.unique!;
}

/**
* Add multiple rules
* @param {HintType[]} hints - Array of hints to add
*/
add(hints: IncomingHintType[]) {
this._hints.mute();
hints.forEach((hint) => this.addOne(hint));
this._hints.unmute();
}

/**
* Remove a hint
* @param {HintType['unique']} unique Unique value of the hint to remove
*/
removeOne(unique: HintType['unique']) {
this._hints.removeOne(unique);
}

/**
* Remove multiple hints
* @param {HintType['unique'][]} uniques Array of unique values to remove
*/
remove(uniques: HintType['unique'][]) {
this._hints.remove(uniques);
}

/**
* Check if a hint exists
* @param {HintType['unique']} unique Unique value of the hint to check
* @returns {boolean} True if the hint exists, false otherwise
*/
has(unique: HintType['unique']): boolean {
return this._hints.getHasOne(unique);
}

/**
* Get all hints
* @returns {HintType[]} Array of hints
*/
getAll(): HintType[] {
return this._hints.getValue();
}

/**
* Clear all hints
*/
clear(): void {
this._hints.setValue([]);
}

override destroy() {
this._hints.destroy();
super.destroy();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './hint-manager.js';
1 change: 1 addition & 0 deletions src/Umbraco.Web.UI.Client/src/packages/core/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export * from './download/blob-download.function.js';
export * from './get-guid-from-udi.function.js';
export * from './get-processed-image-url.function.js';
export * from './guard-manager/index.js';
export * from './hint-manager/index.js';
export * from './math/math.js';
export * from './media/image-size.function.js';
export * from './object/deep-merge.function.js';
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
export * from './workspace-editor.element.js';
export * from './workspace-view-navigation.context-token.js';
export * from './workspace-view-navigation.context.js';
export * from './workspace-view.context-token.js';
Loading
Loading