umbraco-workspace

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Umbraco Workspace

Umbraco 工作区

What is it?

什么是工作区?

Workspaces are dedicated editing environments designed for specific entity types in Umbraco. They create isolated areas where users can edit content, media, members, and other entities with specialized interfaces tailored to each type. Workspaces maintain draft copies of entity data separate from published versions and support multiple extension types including contexts, views, actions, and footer apps.
Workspaces是Umbraco中为特定实体类型设计的专用编辑环境。它们创建了隔离的区域,用户可以通过为每种类型量身定制的专用界面编辑内容、媒体、成员及其他实体。工作区会保留与已发布版本分离的实体数据草稿副本,并支持多种扩展类型,包括上下文、视图、操作和页脚应用。

Documentation

文档

CRITICAL: Workspace Kinds

重要提示:工作区类型

kind: 'default'
vs
kind: 'routable'

kind: 'default'
vs
kind: 'routable'

Feature
kind: 'default'
kind: 'routable'
Use caseStatic pages, root workspacesEntity editing with unique IDs
Tree integrationNo selection stateProper selection state
URL routingNo route paramsSupports
edit/:unique
ContextSimpleHas
unique
observable
For tree item navigation, ALWAYS use
kind: 'routable'
- otherwise:
  • Tree item selection won't update when clicking between items
  • Navigation between same-type items won't work
  • "Forever loading" can occur
特性
kind: 'default'
kind: 'routable'
使用场景静态页面、根工作区带唯一ID的实体编辑
树形结构集成无选中状态支持正确的选中状态
URL路由无路由参数支持
edit/:unique
上下文简单类型包含
unique
可观察对象
若关联树形结构导航,请务必使用
kind: 'routable'
- 否则会出现以下问题:
  • 点击不同树形项时,选中状态不会更新
  • 同类型项之间的导航无法正常工作
  • 可能出现“持续加载”的情况

Routable Workspace Context Pattern

可路由工作区上下文模式

For
kind: 'routable'
workspaces, you MUST create a workspace context class:
typescript
import { UmbWorkspaceRouteManager, UMB_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace';
import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
import { LitElement, html } from '@umbraco-cms/backoffice/external/lit';
import { customElement } from '@umbraco-cms/backoffice/external/lit';

// Workspace editor element that renders views
@customElement('my-workspace-editor')
class MyWorkspaceEditorElement extends LitElement {
  override render() {
    return html`<umb-workspace-editor></umb-workspace-editor>`;
  }
}

interface MyEntityData {
  unique: string;
  name?: string;
}

export class MyWorkspaceContext extends UmbContextBase {
  public readonly workspaceAlias = 'My.Workspace';

  #data = new UmbObjectState<MyEntityData | undefined>(undefined);
  readonly data = this.#data.asObservable();

  // CRITICAL: Observable unique for workspace views to consume
  readonly unique = this.#data.asObservablePart((data) => data?.unique);
  readonly name = this.#data.asObservablePart((data) => data?.name);

  readonly routes = new UmbWorkspaceRouteManager(this);

  constructor(host: UmbControllerHost) {
    super(host, UMB_WORKSPACE_CONTEXT);

    // Route pattern for tree item navigation
    this.routes.setRoutes([
      {
        path: 'edit/:unique',
        component: MyWorkspaceEditorElement,
        setup: (_component, info) => {
          const unique = info.match.params.unique;
          this.load(unique);
        },
      },
    ]);
  }

  async load(unique: string) {
    // Load entity data and update state
    this.#data.setValue({ unique });
  }

  getUnique() {
    return this.#data.getValue()?.unique;
  }

  getEntityType() {
    return 'my-entity';  // Must match tree item entityType!
  }

  public override destroy(): void {
    this.#data.destroy();
    super.destroy();
  }
}

export { MyWorkspaceContext as api };
对于
kind: 'routable'
类型的工作区,必须创建工作区上下文类:
typescript
import { UmbWorkspaceRouteManager, UMB_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace';
import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
import { LitElement, html } from '@umbraco-cms/backoffice/external/lit';
import { customElement } from '@umbraco-cms/backoffice/external/lit';

// 渲染视图的工作区编辑器元素
@customElement('my-workspace-editor')
class MyWorkspaceEditorElement extends LitElement {
  override render() {
    return html`<umb-workspace-editor></umb-workspace-editor>`;
  }
}

interface MyEntityData {
  unique: string;
  name?: string;
}

export class MyWorkspaceContext extends UmbContextBase {
  public readonly workspaceAlias = 'My.Workspace';

  #data = new UmbObjectState<MyEntityData | undefined>(undefined);
  readonly data = this.#data.asObservable();

  // 重要:供工作区视图使用的unique可观察对象
  readonly unique = this.#data.asObservablePart((data) => data?.unique);
  readonly name = this.#data.asObservablePart((data) => data?.name);

  readonly routes = new UmbWorkspaceRouteManager(this);

  constructor(host: UmbControllerHost) {
    super(host, UMB_WORKSPACE_CONTEXT);

    // 树形项导航的路由规则
    this.routes.setRoutes([
      {
        path: 'edit/:unique',
        component: MyWorkspaceEditorElement,
        setup: (_component, info) => {
          const unique = info.match.params.unique;
          this.load(unique);
        },
      },
    ]);
  }

  async load(unique: string) {
    // 加载实体数据并更新状态
    this.#data.setValue({ unique });
  }

  getUnique() {
    return this.#data.getValue()?.unique;
  }

  getEntityType() {
    return 'my-entity';  // 必须与树形项的entityType匹配!
  }

  public override destroy(): void {
    this.#data.destroy();
    super.destroy();
  }
}

export { MyWorkspaceContext as api };

Workspace View Consuming Context

消费上下文的工作区视图

Workspace views observe the context's
unique
to react to navigation:
typescript
import { UMB_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace';

override connectedCallback() {
  super.connectedCallback();

  this.consumeContext(UMB_WORKSPACE_CONTEXT, (context) => {
    if (!context) return;

    // Observe unique - will fire when navigating between items
    this.observe((context as any).unique, (unique: string | null) => {
      if (unique) {
        this._loadData(unique);
      }
    });
  });
}
工作区视图通过监听上下文的
unique
属性来响应导航:
typescript
import { UMB_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace';

override connectedCallback() {
  super.connectedCallback();

  this.consumeContext(UMB_WORKSPACE_CONTEXT, (context) => {
    if (!context) return;

    // 监听unique属性 - 在切换项时触发
    this.observe((context as any).unique, (unique: string | null) => {
      if (unique) {
        this._loadData(unique);
      }
    });
  });
}

Reference Examples

参考示例

The Umbraco source includes working examples:
Workspace Context Counter:
/Umbraco-CMS/src/Umbraco.Web.UI.Client/examples/workspace-context-counter/
This example demonstrates a workspace with context, views, and footer apps. Includes unit tests.
Workspace Context Initial Name:
/Umbraco-CMS/src/Umbraco.Web.UI.Client/examples/workspace-context-initial-name/
This example shows workspace context initialization patterns.
Workspace View Hint:
/Umbraco-CMS/src/Umbraco.Web.UI.Client/examples/workspace-view-hint/
This example demonstrates workspace view hints and metadata.
Umbraco源码中包含可用的示例:
工作区上下文计数器
/Umbraco-CMS/src/Umbraco.Web.UI.Client/examples/workspace-context-counter/
该示例展示了包含上下文、视图和页脚应用的工作区,还包含单元测试。
工作区上下文初始名称
/Umbraco-CMS/src/Umbraco.Web.UI.Client/examples/workspace-context-initial-name/
该示例展示了工作区上下文的初始化模式。
工作区视图提示
/Umbraco-CMS/src/Umbraco.Web.UI.Client/examples/workspace-view-hint/
该示例展示了工作区视图的提示信息和元数据。

Related Foundation Skills

相关基础技能

If you need to explain these foundational concepts when implementing workspaces, reference these skills:
  • Context API: When implementing workspace contexts, context consumption, or explaining workspace extension communication
    • Reference skill:
      umbraco-context-api
  • State Management: When implementing draft state, observables, reactive updates, or workspace data management
    • Reference skill:
      umbraco-state-management
  • Umbraco Element: When implementing workspace view elements, explaining UmbElementMixin, or creating workspace components
    • Reference skill:
      umbraco-umbraco-element
  • Controllers: When implementing workspace actions, controllers, side effects, or action logic
    • Reference skill:
      umbraco-controllers
  • Trees: When workspace is linked to tree navigation
    • Reference skill:
      umbraco-tree
在实现工作区时,如果需要解释以下基础概念,可以参考这些技能:
  • Context API:实现工作区上下文、上下文消费,或解释工作区扩展通信时
    • 参考技能:
      umbraco-context-api
  • 状态管理:实现草稿状态、可观察对象、响应式更新,或工作区数据管理时
    • 参考技能:
      umbraco-state-management
  • Umbraco Element:实现工作区视图元素、解释UmbElementMixin,或创建工作区组件时
    • 参考技能:
      umbraco-umbraco-element
  • 控制器:实现工作区操作、控制器、副作用,或操作逻辑时
    • 参考技能:
      umbraco-controllers
  • 树形结构:工作区关联树形导航时
    • 参考技能:
      umbraco-tree

Workflow

工作流程

  1. Fetch docs - Use WebFetch on the documentation URLs above to get current code examples and patterns
  2. Ask questions - What entity type? What views needed? What actions? Is this linked to a tree?
  3. Choose kind - Use
    kind: 'routable'
    for tree navigation,
    kind: 'default'
    for static pages
  4. Generate files - Create manifest + workspace context + views + actions based on the fetched docs
  5. Add project reference - The extension must be referenced by the main Umbraco project to work:
    • Search for
      .csproj
      files in the current working directory
    • If exactly one Umbraco instance is found, add the reference to it
    • If multiple Umbraco instances are found, ask the user which one to use
    • If no Umbraco instance is found, ask the user for the path
  6. Explain - Show what was created and how to test
  1. 获取文档 - 使用WebFetch访问上述文档链接,获取最新的代码示例和模式
  2. 确认需求 - 明确实体类型、所需视图、操作,以及是否关联树形结构
  3. 选择类型 - 关联树形导航时使用
    kind: 'routable'
    ,静态页面使用
    kind: 'default'
  4. 生成文件 - 根据获取的文档创建清单、工作区上下文、视图和操作
  5. 添加项目引用 - 扩展必须被主Umbraco项目引用才能生效:
    • 在当前工作目录中搜索
      .csproj
      文件
    • 如果找到唯一的Umbraco实例,自动添加引用
    • 如果找到多个Umbraco实例,请询问用户选择哪一个
    • 如果未找到Umbraco实例,请询问用户提供路径
  6. 说明解释 - 展示创建的内容及测试方法

Minimal Manifest Example

最小化清单示例

typescript
export const manifests: UmbExtensionManifest[] = [
  // Routable workspace for tree integration
  {
    type: 'workspace',
    kind: 'routable',
    alias: 'My.Workspace',
    name: 'My Workspace',
    api: () => import('./my-workspace.context.js'),
    meta: {
      entityType: 'my-entity',  // Must match tree item entityType!
    },
  },
  // Workspace view
  {
    type: 'workspaceView',
    alias: 'My.WorkspaceView',
    name: 'My Workspace View',
    element: () => import('./my-workspace-view.element.js'),
    weight: 100,
    meta: {
      label: 'Details',
      pathname: 'details',
      icon: 'icon-info',
    },
    conditions: [
      {
        alias: 'Umb.Condition.WorkspaceAlias',
        match: 'My.Workspace',
      },
    ],
  },
];
Always fetch fresh docs before generating code - the API and patterns may have changed.
typescript
export const manifests: UmbExtensionManifest[] = [
  // 关联树形结构的可路由工作区
  {
    type: 'workspace',
    kind: 'routable',
    alias: 'My.Workspace',
    name: 'My Workspace',
    api: () => import('./my-workspace.context.js'),
    meta: {
      entityType: 'my-entity',  // 必须与树形项的entityType匹配!
    },
  },
  // 工作区视图
  {
    type: 'workspaceView',
    alias: 'My.WorkspaceView',
    name: 'My Workspace View',
    element: () => import('./my-workspace-view.element.js'),
    weight: 100,
    meta: {
      label: 'Details',
      pathname: 'details',
      icon: 'icon-info',
    },
    conditions: [
      {
        alias: 'Umb.Condition.WorkspaceAlias',
        match: 'My.Workspace',
      },
    ],
  },
];
生成代码前请务必获取最新文档 - API和模式可能已发生变更。