Folder Workspace: Support menu expansion and breadcrumbs (closes #20675) (#20712)

* add menu context and breadcrumbs for document type folders

* add menu context and breadcrumbs for media type folders

* add menu context and breadcrumbs for media type folders

* add menu context and breadcrumbs for partial view folders

* add menu context and breadcrumbs for partial view folders

* add menu context and breadcrumbs for script folders

* Register menu structure workspace contexts and breadcrumbs for document blueprints

* fix menu alias

* remove from blueprints

* fix wrong path when navigating from an inner folder to an outer

* remove debugger

* fix structure link between variant to invariant

* fix up path generation

---------

Co-authored-by: Niels Lyngsø <niels.lyngso@gmail.com>
This commit is contained in:
Mads Rasmussen
2025-12-01 15:00:00 +01:00
committed by GitHub
parent bbc0f1f894
commit 0210e942ed
13 changed files with 269 additions and 37 deletions

View File

@@ -35,6 +35,7 @@ export abstract class UmbMenuTreeStructureWorkspaceContextBase extends UmbContex
#ancestorContext = new UmbAncestorsEntityContext(this);
#sectionSidebarMenuContext?: typeof UMB_SECTION_SIDEBAR_MENU_SECTION_CONTEXT.TYPE;
#isModalContext: boolean = false;
#isNew: boolean | undefined = undefined;
constructor(host: UmbControllerHost, args: UmbMenuTreeStructureWorkspaceContextBaseArgs) {
super(host, UMB_MENU_STRUCTURE_WORKSPACE_CONTEXT);
@@ -52,15 +53,27 @@ export abstract class UmbMenuTreeStructureWorkspaceContextBase extends UmbContex
this.consumeContext(UMB_SUBMITTABLE_TREE_ENTITY_WORKSPACE_CONTEXT, (instance) => {
this.#workspaceContext = instance;
this.observe(this.#workspaceContext?.unique, (value) => {
this.observe(
this.#workspaceContext?.unique,
(value) => {
if (!value) return;
this.#requestStructure();
});
},
'observeUnique',
);
this.observe(this.#workspaceContext?.isNew, (value) => {
if (value === undefined) return;
this.observe(
this.#workspaceContext?.isNew,
(value) => {
// Workspace has changed from new to existing
if (value === false && this.#isNew === true) {
// TODO: We do not need to request here as we already know the structure and unique
this.#requestStructure();
});
}
this.#isNew = value;
},
'observeIsNew',
);
});
}

View File

@@ -10,6 +10,7 @@ import { UmbAncestorsEntityContext, UmbParentEntityContext, type UmbEntityModel
import {
UMB_SUBMITTABLE_TREE_ENTITY_WORKSPACE_CONTEXT,
UMB_VARIANT_WORKSPACE_CONTEXT,
UMB_WORKSPACE_PATH_PATTERN,
} from '@umbraco-cms/backoffice/workspace';
import { linkEntityExpansionEntries } from '@umbraco-cms/backoffice/utils';
import { UMB_MODAL_CONTEXT } from '@umbraco-cms/backoffice/modal';
@@ -83,19 +84,46 @@ export abstract class UmbMenuVariantTreeStructureWorkspaceContextBase extends Um
'observeUnique',
);
this.observe(this.#workspaceContext?.isNew, (value) => {
this.observe(
this.#workspaceContext?.isNew,
(value) => {
// Workspace has changed from new to existing
if (value === false && this.#isNew === true) {
// TODO: We do not need to request here as we already know the structure and unique
this.#requestStructure();
}
this.#isNew = value;
});
},
'observeIsNew',
);
});
}
getItemHref(structureItem: UmbVariantStructureItemModel): string | undefined {
return `section/${this._sectionContext?.getPathname()}/workspace/${structureItem.entityType}/edit/${structureItem.unique}/${this.#workspaceActiveVariantId?.toCultureString()}`;
const sectionName = this._sectionContext?.getPathname();
if (!sectionName) {
return undefined;
}
UMB_WORKSPACE_PATH_PATTERN.generateAbsolute({
sectionName,
entityType: structureItem.entityType,
});
const path = `section/${this._sectionContext!.getPathname()}/workspace/${structureItem.entityType}/edit/${structureItem.unique}`;
// find related variant id from structure item:
const itemVariantFit = structureItem.variants.find((variant) => {
return (
variant.culture === this.#workspaceActiveVariantId?.culture &&
variant.segment === this.#workspaceActiveVariantId?.segment
);
});
if (itemVariantFit) {
const variantId = UmbVariantId.CreateFromPartial(itemVariantFit);
return `${path}/${variantId.toString()}`;
}
// If no related variantID, then lets the redirect go to the main-variant:
return path;
}
async #requestStructure() {

View File

@@ -1,3 +1,5 @@
import { UMB_DATA_TYPE_WORKSPACE_ALIAS } from '../workspace/constants.js';
import { UMB_DATA_TYPE_FOLDER_WORKSPACE_ALIAS } from '../tree/constants.js';
import { UMB_DATA_TYPE_MENU_ITEM_ALIAS } from './constants.js';
import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace';
@@ -27,7 +29,7 @@ export const manifests: Array<UmbExtensionManifest> = [
conditions: [
{
alias: UMB_WORKSPACE_CONDITION_ALIAS,
match: 'Umb.Workspace.DataType',
match: UMB_DATA_TYPE_WORKSPACE_ALIAS,
},
],
},
@@ -39,7 +41,35 @@ export const manifests: Array<UmbExtensionManifest> = [
conditions: [
{
alias: UMB_WORKSPACE_CONDITION_ALIAS,
match: 'Umb.Workspace.DataType',
match: UMB_DATA_TYPE_WORKSPACE_ALIAS,
},
],
},
{
type: 'workspaceContext',
kind: 'menuStructure',
name: 'Data Type Folder Menu Structure Workspace Context',
alias: 'Umb.Context.DataTypeFolder.Menu.Structure',
api: () => import('./data-type-menu-structure.context.js'),
meta: {
menuItemAlias: UMB_DATA_TYPE_MENU_ITEM_ALIAS,
},
conditions: [
{
alias: UMB_WORKSPACE_CONDITION_ALIAS,
match: UMB_DATA_TYPE_FOLDER_WORKSPACE_ALIAS,
},
],
},
{
type: 'workspaceFooterApp',
kind: 'menuBreadcrumb',
alias: 'Umb.WorkspaceFooterApp.DataTypeFolder.Breadcrumb',
name: 'Data Type Folder Breadcrumb Workspace Footer App',
conditions: [
{
alias: UMB_WORKSPACE_CONDITION_ALIAS,
match: UMB_DATA_TYPE_FOLDER_WORKSPACE_ALIAS,
},
],
},

View File

@@ -1,9 +1,10 @@
export * from './property-dataset-context/document-blueprint-property-dataset-context.token.js';
export * from './entity-actions/constants.js';
export * from './menu/constants.js';
export * from './paths.js';
export * from './property-dataset-context/document-blueprint-property-dataset-context.token.js';
export * from './repository/constants.js';
export * from './tree/constants.js';
export * from './workspace/constants.js';
export * from './paths.js';
export {
UMB_DOCUMENT_BLUEPRINT_ROOT_ENTITY_TYPE,
UMB_DOCUMENT_BLUEPRINT_ENTITY_TYPE,

View File

@@ -1,12 +1,12 @@
import { manifests as entityActionManifests } from './entity-actions/manifests.js';
import { manifests as menuItemManifests } from './menu-item/manifests.js';
import { manifests as menuManifests } from './menu/manifests.js';
import { manifests as repositoryManifests } from './repository/manifests.js';
import { manifests as treeManifests } from './tree/manifests.js';
import { manifests as workspaceManifests } from './workspace/manifests.js';
export const manifests: Array<UmbExtensionManifest> = [
...entityActionManifests,
...menuItemManifests,
...menuManifests,
...repositoryManifests,
...treeManifests,
...workspaceManifests,

View File

@@ -0,0 +1 @@
export const UMB_DOCUMENT_BLUEPRINT_MENU_ITEM_ALIAS = 'Umb.MenuItem.DocumentBlueprints';

View File

@@ -0,0 +1,12 @@
import { UMB_DOCUMENT_BLUEPRINT_TREE_REPOSITORY_ALIAS } from '../tree/index.js';
import { UmbMenuTreeStructureWorkspaceContextBase } from '@umbraco-cms/backoffice/menu';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
export class UmbDocumentBlueprintMenuStructureWorkspaceContext extends UmbMenuTreeStructureWorkspaceContextBase {
constructor(host: UmbControllerHost) {
super(host, { treeRepositoryAlias: UMB_DOCUMENT_BLUEPRINT_TREE_REPOSITORY_ALIAS });
}
}
export { UmbDocumentBlueprintMenuStructureWorkspaceContext as api };

View File

@@ -1,10 +1,11 @@
import { UMB_DOCUMENT_BLUEPRINT_TREE_ALIAS } from '../tree/constants.js';
import { UMB_DOCUMENT_BLUEPRINT_MENU_ITEM_ALIAS } from './constants.js';
export const manifests: Array<UmbExtensionManifest> = [
{
type: 'menuItem',
kind: 'tree',
alias: 'Umb.MenuItem.DocumentBlueprints',
alias: UMB_DOCUMENT_BLUEPRINT_MENU_ITEM_ALIAS,
name: 'Document Blueprints Menu Item',
weight: 100,
meta: {

View File

@@ -1,4 +1,5 @@
import { UMB_DOCUMENT_TYPE_TREE_ALIAS } from '../tree/index.js';
import { UMB_DOCUMENT_TYPE_WORKSPACE_ALIAS } from '../constants.js';
import { UMB_DOCUMENT_TYPE_FOLDER_WORKSPACE_ALIAS, UMB_DOCUMENT_TYPE_TREE_ALIAS } from '../tree/index.js';
import { UMB_DOCUMENT_TYPE_MENU_ITEM_ALIAS } from './constants.js';
import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace';
@@ -27,7 +28,7 @@ export const manifests: Array<UmbExtensionManifest> = [
conditions: [
{
alias: UMB_WORKSPACE_CONDITION_ALIAS,
match: 'Umb.Workspace.DocumentType',
match: UMB_DOCUMENT_TYPE_WORKSPACE_ALIAS,
},
],
},
@@ -43,4 +44,32 @@ export const manifests: Array<UmbExtensionManifest> = [
},
],
},
{
type: 'workspaceContext',
kind: 'menuStructure',
name: 'Document Type Folder Menu Structure Workspace Context',
alias: 'Umb.Context.DocumentTypeFolder.Menu.Structure',
api: () => import('./document-type-menu-structure.context.js'),
meta: {
menuItemAlias: UMB_DOCUMENT_TYPE_MENU_ITEM_ALIAS,
},
conditions: [
{
alias: UMB_WORKSPACE_CONDITION_ALIAS,
match: UMB_DOCUMENT_TYPE_FOLDER_WORKSPACE_ALIAS,
},
],
},
{
type: 'workspaceFooterApp',
kind: 'menuBreadcrumb',
alias: 'Umb.WorkspaceFooterApp.DocumentTypeFolder.Breadcrumb',
name: 'Document Type Folder Breadcrumb Workspace Footer App',
conditions: [
{
alias: UMB_WORKSPACE_CONDITION_ALIAS,
match: UMB_DOCUMENT_TYPE_FOLDER_WORKSPACE_ALIAS,
},
],
},
];

View File

@@ -1,4 +1,5 @@
import { UMB_MEDIA_TYPE_TREE_ALIAS } from '../constants.js';
import { UMB_MEDIA_TYPE_TREE_ALIAS, UMB_MEDIA_TYPE_FOLDER_WORKSPACE_ALIAS } from '../tree/constants.js';
import { UMB_MEDIA_TYPE_WORKSPACE_ALIAS } from '../workspace/constants.js';
import { UMB_MEDIA_TYPE_MENU_ITEM_ALIAS } from './constants.js';
import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace';
@@ -27,7 +28,7 @@ export const manifests: Array<UmbExtensionManifest> = [
conditions: [
{
alias: UMB_WORKSPACE_CONDITION_ALIAS,
match: 'Umb.Workspace.MediaType',
match: UMB_MEDIA_TYPE_WORKSPACE_ALIAS,
},
],
},
@@ -39,7 +40,35 @@ export const manifests: Array<UmbExtensionManifest> = [
conditions: [
{
alias: UMB_WORKSPACE_CONDITION_ALIAS,
match: 'Umb.Workspace.MediaType',
match: UMB_MEDIA_TYPE_WORKSPACE_ALIAS,
},
],
},
{
type: 'workspaceContext',
kind: 'menuStructure',
name: 'Media Type Folder Menu Structure Workspace Context',
alias: 'Umb.Context.MediaTypeFolder.Menu.Structure',
api: () => import('./media-type-menu-structure.context.js'),
meta: {
menuItemAlias: UMB_MEDIA_TYPE_MENU_ITEM_ALIAS,
},
conditions: [
{
alias: UMB_WORKSPACE_CONDITION_ALIAS,
match: UMB_MEDIA_TYPE_FOLDER_WORKSPACE_ALIAS,
},
],
},
{
type: 'workspaceFooterApp',
kind: 'menuBreadcrumb',
alias: 'Umb.WorkspaceFooterApp.MediaTypeFolder.Breadcrumb',
name: 'Media Type Folder Breadcrumb Workspace Footer App',
conditions: [
{
alias: UMB_WORKSPACE_CONDITION_ALIAS,
match: UMB_MEDIA_TYPE_FOLDER_WORKSPACE_ALIAS,
},
],
},

View File

@@ -1,4 +1,5 @@
import { UMB_PARTIAL_VIEW_TREE_ALIAS } from '../tree/index.js';
import { UMB_PARTIAL_VIEW_WORKSPACE_ALIAS } from '../workspace/constants.js';
import { UMB_PARTIAL_VIEW_FOLDER_WORKSPACE_ALIAS, UMB_PARTIAL_VIEW_TREE_ALIAS } from '../tree/constants.js';
import { UMB_PARTIAL_VIEW_MENU_ITEM_ALIAS } from './constants.js';
import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace';
@@ -27,7 +28,7 @@ export const manifests: Array<UmbExtensionManifest> = [
conditions: [
{
alias: UMB_WORKSPACE_CONDITION_ALIAS,
match: 'Umb.Workspace.PartialView',
match: UMB_PARTIAL_VIEW_WORKSPACE_ALIAS,
},
],
},
@@ -39,7 +40,35 @@ export const manifests: Array<UmbExtensionManifest> = [
conditions: [
{
alias: UMB_WORKSPACE_CONDITION_ALIAS,
match: 'Umb.Workspace.PartialView',
match: UMB_PARTIAL_VIEW_WORKSPACE_ALIAS,
},
],
},
{
type: 'workspaceContext',
kind: 'menuStructure',
name: 'Partial View Folder Menu Structure Workspace Context',
alias: 'Umb.Context.PartialViewFolder.Menu.Structure',
api: () => import('./partial-view-menu-structure.context.js'),
meta: {
menuItemAlias: UMB_PARTIAL_VIEW_MENU_ITEM_ALIAS,
},
conditions: [
{
alias: UMB_WORKSPACE_CONDITION_ALIAS,
match: UMB_PARTIAL_VIEW_FOLDER_WORKSPACE_ALIAS,
},
],
},
{
type: 'workspaceFooterApp',
kind: 'menuBreadcrumb',
alias: 'Umb.WorkspaceFooterApp.PartialViewFolder.Breadcrumb',
name: 'Partial View Folder Breadcrumb Workspace Footer App',
conditions: [
{
alias: UMB_WORKSPACE_CONDITION_ALIAS,
match: UMB_PARTIAL_VIEW_FOLDER_WORKSPACE_ALIAS,
},
],
},

View File

@@ -1,4 +1,6 @@
import { UMB_SCRIPT_WORKSPACE_ALIAS } from '../workspace/constants.js';
import { UMB_SCRIPT_TREE_ALIAS } from '../tree/index.js';
import { UMB_SCRIPT_FOLDER_WORKSPACE_ALIAS } from '../tree/constants.js';
import { UMB_SCRIPT_MENU_ITEM_ALIAS } from './constants.js';
import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace';
@@ -27,7 +29,7 @@ export const manifests: Array<UmbExtensionManifest> = [
conditions: [
{
alias: UMB_WORKSPACE_CONDITION_ALIAS,
match: 'Umb.Workspace.Script',
match: UMB_SCRIPT_WORKSPACE_ALIAS,
},
],
},
@@ -39,7 +41,35 @@ export const manifests: Array<UmbExtensionManifest> = [
conditions: [
{
alias: UMB_WORKSPACE_CONDITION_ALIAS,
match: 'Umb.Workspace.Script',
match: UMB_SCRIPT_WORKSPACE_ALIAS,
},
],
},
{
type: 'workspaceContext',
kind: 'menuStructure',
name: 'Script Folder Menu Structure Workspace Context',
alias: 'Umb.Context.ScriptFolder.Menu.Structure',
api: () => import('./script-menu-structure.context.js'),
meta: {
menuItemAlias: UMB_SCRIPT_MENU_ITEM_ALIAS,
},
conditions: [
{
alias: UMB_WORKSPACE_CONDITION_ALIAS,
match: UMB_SCRIPT_FOLDER_WORKSPACE_ALIAS,
},
],
},
{
type: 'workspaceFooterApp',
kind: 'menuBreadcrumb',
alias: 'Umb.WorkspaceFooterApp.ScriptFolder.Breadcrumb',
name: 'Script Folder Breadcrumb Workspace Footer App',
conditions: [
{
alias: UMB_WORKSPACE_CONDITION_ALIAS,
match: UMB_SCRIPT_FOLDER_WORKSPACE_ALIAS,
},
],
},

View File

@@ -1,4 +1,5 @@
import { UMB_STYLESHEET_TREE_ALIAS } from '../constants.js';
import { UMB_STYLESHEET_FOLDER_WORKSPACE_ALIAS, UMB_STYLESHEET_TREE_ALIAS } from '../tree/constants.js';
import { UMB_STYLESHEET_WORKSPACE_ALIAS } from '../workspace/constants.js';
import { UMB_STYLESHEET_MENU_ITEM_ALIAS } from './constants.js';
import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace';
@@ -27,7 +28,7 @@ export const manifests: Array<UmbExtensionManifest> = [
conditions: [
{
alias: UMB_WORKSPACE_CONDITION_ALIAS,
match: 'Umb.Workspace.Stylesheet',
match: UMB_STYLESHEET_WORKSPACE_ALIAS,
},
],
},
@@ -39,7 +40,35 @@ export const manifests: Array<UmbExtensionManifest> = [
conditions: [
{
alias: UMB_WORKSPACE_CONDITION_ALIAS,
match: 'Umb.Workspace.Stylesheet',
match: UMB_STYLESHEET_WORKSPACE_ALIAS,
},
],
},
{
type: 'workspaceContext',
kind: 'menuStructure',
name: 'Stylesheet Folder Menu Structure Workspace Context',
alias: 'Umb.Context.StylesheetFolder.Menu.Structure',
api: () => import('./stylesheet-menu-structure.context.js'),
meta: {
menuItemAlias: UMB_STYLESHEET_MENU_ITEM_ALIAS,
},
conditions: [
{
alias: UMB_WORKSPACE_CONDITION_ALIAS,
match: UMB_STYLESHEET_FOLDER_WORKSPACE_ALIAS,
},
],
},
{
type: 'workspaceFooterApp',
kind: 'menuBreadcrumb',
alias: 'Umb.WorkspaceFooterApp.StylesheetFolder.Breadcrumb',
name: 'Stylesheet Folder Breadcrumb Workspace Footer App',
conditions: [
{
alias: UMB_WORKSPACE_CONDITION_ALIAS,
match: UMB_STYLESHEET_FOLDER_WORKSPACE_ALIAS,
},
],
},