Skip to content

Commit 0845d26

Browse files
nielslyngsoeCopilotjsandraeengijlrAndyButland
authored
Block Editors: variantId inheritance fix (#21101)
* block context example * fix clone method * implement contextual variant id * use contextual variant id * ensure currentExposeOf uses elementType configuration * make hasExposeOf use ElementType configuration for variatId * Update src/Umbraco.Web.UI.Client/src/packages/rte/components/rte-base.element.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * rename to displayVariant * use variantid in this case * update readme * return false * remove unused imports * return undefined * append UmbWorkspaceViewElement interface * remove test * fallback to false * fallback to false * refactor what is not exposed. * Fix #20944: Updating UI Slider number properties to accept decimal values (#20945) * updating UI slider properties min, max, initial1, initial2 and step to accept decimal values * Changes based on Copilot suggestions * Used a smaller step value configuration. --------- Co-authored-by: Niels Lyngsø <nsl@umbraco.dk> Co-authored-by: Engiber Lozada <89547469+engijlr@users.noreply.github.com> Co-authored-by: engjlr <enl@umbraco.dk> * Manifests: Fix misnaming and mis-registering of the document validation manifest (closes #21128) (#21139) Fix misnaming and mis-registering of the document validation manifest. * Performance: Optimize memory footprint of document URL cache (closes #21055) (#21066) * Optimize memory footprint of document URL cache. * Update tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/DocumentUrlServiceTests.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Used properties, added some further comments. * Fixed failing integration tests. * Ensure no edge case exists where the culture code to language Id map isn't up to date with the newly created languages. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Block List: Sort mode (#21060) * Block List: added sort-mode * Fix missing closing bracket * register sort mode toolbar element on start up --------- Co-authored-by: Mads Rasmussen <madsr@hey.com> # Conflicts: # src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/property-editor-ui-block-list.element.ts * Update nightly build to include 16 as 17 is now main (#21144) * Global search items missing Umbraco url segment (#20266) * Add /umbraco url Segament for searched mapped results to aviod 404 og navigation away from backoffice * Applied changes from code review. --------- Co-authored-by: Lucas Bach Bisgaard <lucas.bisgaard@kraftvaerk.com> Co-authored-by: Andy Butland <abutland73@gmail.com> * import --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Jason Andrae <jasona@emergentsoftware.net> Co-authored-by: Engiber Lozada <89547469+engijlr@users.noreply.github.com> Co-authored-by: engjlr <enl@umbraco.dk> Co-authored-by: Andy Butland <abutland73@gmail.com> Co-authored-by: Lee Kelleher <leekelleher@users.noreply.github.com> Co-authored-by: Mads Rasmussen <madsr@hey.com> Co-authored-by: Sven Geusens <sge@umbraco.dk> Co-authored-by: Lucas Bach Bisgaard <rammi@rammi.dk> Co-authored-by: Lucas Bach Bisgaard <lucas.bisgaard@kraftvaerk.com>
1 parent 6c743e8 commit 0845d26

File tree

12 files changed

+206
-27
lines changed

12 files changed

+206
-27
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Consuming Variant Context in a Block Workspace View Example
2+
This example demonstrates how to consume and display the contextual variant information (culture and segment) from the `UMB_VARIANT_CONTEXT` within a block workspace view extension.
3+
4+
## What this example includes
5+
- **Workspace View** – A custom workspace view that reads the current variant context (culture and segment) and displays it to the user.
6+
7+
## How it works
8+
The workspace view extension uses the `UMB_VARIANT_CONTEXT` context token to access the current variant's culture and segment. This allows the extension to react to changes in the variant context and display the relevant information.
9+
This pattern is useful for extension developers who need to make their workspace views aware of the current variant, enabling context-sensitive UI and logic.
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
2+
import { css, html, customElement, state, LitElement } from '@umbraco-cms/backoffice/external/lit';
3+
import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api';
4+
import { UMB_VARIANT_CONTEXT } from '@umbraco-cms/backoffice/variant';
5+
import type { UmbWorkspaceViewElement } from '@umbraco-cms/backoffice/workspace';
6+
7+
@customElement('example-block-workspace-view')
8+
export class ExampleBlockWorkspaceViewElement extends UmbElementMixin(LitElement) implements UmbWorkspaceViewElement {
9+
@state()
10+
private _culture?: string | null;
11+
@state()
12+
private _segment?: string | null;
13+
14+
constructor() {
15+
super();
16+
17+
this.consumeContext(UMB_VARIANT_CONTEXT, (context) => {
18+
this.observe(context?.displayVariantId, (variantId) => {
19+
this._culture = variantId?.culture;
20+
this._segment = variantId?.segment;
21+
});
22+
});
23+
}
24+
25+
override render() {
26+
return html`
27+
<uui-box class="uui-text">
28+
<p class="uui-lead">Current variant context culture: ${this._culture}, & segment of : ${this._segment}</p>
29+
</uui-box>
30+
`;
31+
}
32+
33+
static override styles = [
34+
UmbTextStyles,
35+
css`
36+
:host {
37+
display: block;
38+
padding: var(--uui-size-layout-1);
39+
}
40+
`,
41+
];
42+
}
43+
44+
export default ExampleBlockWorkspaceViewElement;
45+
46+
declare global {
47+
interface HTMLElementTagNameMap {
48+
'example-block-workspace-view': ExampleBlockWorkspaceViewElement;
49+
}
50+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { UMB_BLOCK_WORKSPACE_ALIAS } from '@umbraco-cms/backoffice/block';
2+
import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace';
3+
4+
export const manifests: Array<UmbExtensionManifest> = [
5+
{
6+
type: 'workspaceView',
7+
name: 'Example Block Workspace View',
8+
alias: 'example.workspaceView.block',
9+
element: () => import('./block-workspace-view.js'),
10+
weight: 900,
11+
meta: {
12+
label: 'Counter',
13+
pathname: 'counter',
14+
icon: 'icon-lab',
15+
},
16+
conditions: [
17+
{
18+
alias: UMB_WORKSPACE_CONDITION_ALIAS,
19+
match: UMB_BLOCK_WORKSPACE_ALIAS,
20+
},
21+
],
22+
},
23+
];

src/Umbraco.Web.UI.Client/src/packages/block/block-grid/property-editors/block-grid-editor/property-editor-ui-block-grid.element.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,14 @@ import {
1212
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
1313
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
1414
import { UMB_CONTENT_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/content';
15-
import { UMB_PROPERTY_CONTEXT, UMB_PROPERTY_DATASET_CONTEXT } from '@umbraco-cms/backoffice/property';
15+
import { UMB_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/property';
1616
import type { PropertyValueMap } from '@umbraco-cms/backoffice/external/lit';
1717
import type { UmbBlockTypeGroup } from '@umbraco-cms/backoffice/block-type';
1818
import type {
1919
UmbPropertyEditorUiElement,
2020
UmbPropertyEditorConfigCollection,
2121
} from '@umbraco-cms/backoffice/property-editor';
22+
import { UMB_VARIANT_CONTEXT } from '@umbraco-cms/backoffice/variant';
2223

2324
// TODO: consider moving the components to the property editor folder as they are only used here
2425
import '../../local-components.js';
@@ -232,8 +233,14 @@ export class UmbPropertyEditorUIBlockGridElement
232233
);
233234
});
234235

235-
this.consumeContext(UMB_PROPERTY_DATASET_CONTEXT, (context) => {
236-
this.#managerContext.setVariantId(context?.getVariantId());
236+
this.consumeContext(UMB_VARIANT_CONTEXT, async (context) => {
237+
this.observe(
238+
context?.displayVariantId,
239+
(variantId) => {
240+
this.#managerContext.setVariantId(variantId);
241+
},
242+
'observeContextualVariantId',
243+
);
237244
});
238245

239246
this.observe(this.#managerContext.isSortMode, (isSortMode) => (this._isSortMode = isSortMode ?? false));

src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/property-editor-ui-block-list.element.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
import { jsonStringComparison, observeMultiple } from '@umbraco-cms/backoffice/observable-api';
1515
import { umbDestroyOnDisconnect, UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
1616
import { UmbSorterController } from '@umbraco-cms/backoffice/sorter';
17-
import { UMB_PROPERTY_CONTEXT, UMB_PROPERTY_DATASET_CONTEXT } from '@umbraco-cms/backoffice/property';
17+
import { UMB_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/property';
1818
import { UMB_CONTENT_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/content';
1919
import type { UmbBlockLayoutBaseModel } from '@umbraco-cms/backoffice/block';
2020
import type { UmbBlockTypeBaseModel } from '@umbraco-cms/backoffice/block-type';
@@ -25,6 +25,7 @@ import type {
2525
UmbPropertyEditorUiElement,
2626
} from '@umbraco-cms/backoffice/property-editor';
2727
import type { UmbSorterConfig } from '@umbraco-cms/backoffice/sorter';
28+
import { UMB_VARIANT_CONTEXT } from '@umbraco-cms/backoffice/variant';
2829

2930
import '../../components/block-list-entry/index.js';
3031

@@ -248,8 +249,14 @@ export class UmbPropertyEditorUIBlockListElement
248249
null,
249250
);
250251

251-
this.consumeContext(UMB_PROPERTY_DATASET_CONTEXT, async (context) => {
252-
this.#managerContext.setVariantId(context?.getVariantId());
252+
this.consumeContext(UMB_VARIANT_CONTEXT, async (context) => {
253+
this.observe(
254+
context?.displayVariantId,
255+
(variantId) => {
256+
this.#managerContext.setVariantId(variantId);
257+
},
258+
'observeContextualVariantId',
259+
);
253260
});
254261

255262
this.observe(this.#managerContext.isSortMode, (isSortMode) => (this._isSortMode = isSortMode ?? false));

src/Umbraco.Web.UI.Client/src/packages/block/block-single/property-editors/block-single-editor/property-editor-ui-block-single.element.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import type { UmbBlockLayoutBaseModel } from '@umbraco-cms/backoffice/block';
2626
import type { UmbBlockTypeBaseModel } from '@umbraco-cms/backoffice/block-type';
2727

2828
import '../../components/block-single-entry/index.js';
29-
import { UMB_PROPERTY_CONTEXT, UMB_PROPERTY_DATASET_CONTEXT } from '@umbraco-cms/backoffice/property';
29+
import { UMB_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/property';
3030
import {
3131
extractJsonQueryProps,
3232
UMB_VALIDATION_EMPTY_LOCALIZATION_KEY,
@@ -36,6 +36,7 @@ import {
3636
import { jsonStringComparison, observeMultiple } from '@umbraco-cms/backoffice/observable-api';
3737
import { debounceTime } from '@umbraco-cms/backoffice/external/rxjs';
3838
import { UMB_CONTENT_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/content';
39+
import { UMB_VARIANT_CONTEXT } from '@umbraco-cms/backoffice/variant';
3940

4041
const SORTER_CONFIG: UmbSorterConfig<UmbBlockSingleLayoutModel, UmbBlockSingleEntryElement> = {
4142
getUniqueOfElement: (element) => {
@@ -244,8 +245,14 @@ export class UmbPropertyEditorUIBlockSingleElement
244245
null,
245246
);
246247

247-
this.consumeContext(UMB_PROPERTY_DATASET_CONTEXT, async (context) => {
248-
this.#managerContext.setVariantId(context?.getVariantId());
248+
this.consumeContext(UMB_VARIANT_CONTEXT, async (context) => {
249+
this.observe(
250+
context?.displayVariantId,
251+
(variantId) => {
252+
this.#managerContext.setVariantId(variantId);
253+
},
254+
'observeContextualVariantId',
255+
);
249256
});
250257

251258
this.addValidator(

src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-entry.context.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -714,7 +714,7 @@ export abstract class UmbBlockEntryContext<
714714
this.observe(
715715
this._manager?.hasExposeOf(this.#contentKey, variantId),
716716
(hasExpose) => {
717-
this.#hasExpose.setValue(hasExpose);
717+
this.#hasExpose.setValue(hasExpose ?? false);
718718
},
719719
'observeExpose',
720720
);

src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-manager.context.ts

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -243,20 +243,57 @@ export abstract class UmbBlockManagerContext<
243243
settingsOf(key: string) {
244244
return this.#settings.asObservablePart((source) => source.find((x) => x.key === key));
245245
}
246+
246247
currentExposeOf(contentKey: string) {
247-
const variantId = this.getVariantId();
248-
if (!variantId) return;
249248
return mergeObservables(
250249
[this.#exposes.asObservablePart((source) => source.filter((x) => x.contentKey === contentKey)), this.variantId],
251-
([exposes, variantId]) => (variantId ? exposes.find((x) => variantId.compare(x)) : undefined),
250+
([exposes, variantId]) => {
251+
if (!variantId) {
252+
return undefined;
253+
}
254+
255+
const contentTypeKey = this.getContentTypeKeyOfContentKey(contentKey);
256+
if (!contentTypeKey) {
257+
return false;
258+
}
259+
const contentStructure = this.getStructure(contentTypeKey);
260+
if (!contentStructure) {
261+
throw new Error(`Cannot lookup expose of block, missing content structure for ${contentTypeKey}`);
262+
}
263+
264+
const varyByCulture = contentStructure.getVariesByCulture();
265+
const varyBySegment = contentStructure.getVariesBySegment();
266+
const blockVariantId = variantId.toVariant(varyByCulture, varyBySegment);
267+
268+
return exposes.find((x) => blockVariantId.compare(x));
269+
},
252270
);
253271
}
254272

255273
hasExposeOf(contentKey: string, variantId: UmbVariantId) {
256274
if (!variantId) return;
257-
return this.#exposes.asObservablePart((source) =>
258-
source.some((x) => x.contentKey === contentKey && variantId.compare(x)),
259-
);
275+
276+
const contentTypeKey = this.getContentTypeKeyOfContentKey(contentKey);
277+
if (!contentTypeKey) {
278+
// Not created yet and therefor not exposed.
279+
// (This currently does not give any trouble,
280+
// but this is not returning a observable,
281+
// meaning one trying to observe this would
282+
// not be updated when it is exposed. But it
283+
// is not a problem currently. [NL])
284+
return;
285+
}
286+
const contentStructure = this.getStructure(contentTypeKey);
287+
if (!contentStructure) {
288+
throw new Error(`Cannot lookup expose of block, missing content structure for ${contentTypeKey}`);
289+
}
290+
const varyByCulture = contentStructure.getVariesByCulture();
291+
const varyBySegment = contentStructure.getVariesBySegment();
292+
const blockVariantId = variantId.toVariant(varyByCulture, varyBySegment);
293+
294+
return this.#exposes.asObservablePart((exposes) => {
295+
return exposes.some((x) => x.contentKey === contentKey && blockVariantId.compare(x));
296+
});
260297
}
261298

262299
getBlockTypeOf(contentTypeKey: string) {

src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-workspace.context.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ export class UmbBlockWorkspaceContext<LayoutDataType extends UmbBlockLayoutBaseM
188188
this.observe(
189189
manager.hasExposeOf(contentKey, variantId),
190190
(exposed) => {
191-
this.#exposed.setValue(exposed);
191+
this.#exposed.setValue(exposed ?? false);
192192
},
193193
'observeHasExpose',
194194
);

src/Umbraco.Web.UI.Client/src/packages/core/variant/context/variant.context.ts

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@ import { UmbVariantId } from '../variant-id.class.js';
22
import { UMB_VARIANT_CONTEXT } from './variant.context.token.js';
33
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
44
import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
5-
import { mergeObservables, UmbClassState, UmbStringState } from '@umbraco-cms/backoffice/observable-api';
5+
import {
6+
createObservablePart,
7+
mergeObservables,
8+
UmbClassState,
9+
UmbStringState,
10+
} from '@umbraco-cms/backoffice/observable-api';
611

712
/**
813
* A context for the current variant state.
@@ -11,21 +16,41 @@ import { mergeObservables, UmbClassState, UmbStringState } from '@umbraco-cms/ba
1116
* @implements {UmbVariantContext}
1217
*/
1318
export class UmbVariantContext extends UmbContextBase {
19+
// Local variant ID:
1420
#variantId = new UmbClassState<UmbVariantId | undefined>(undefined);
1521
public readonly variantId = this.#variantId.asObservable();
1622
public readonly culture = this.#variantId.asObservablePart((x) => x?.culture);
1723
public readonly segment = this.#variantId.asObservablePart((x) => x?.segment);
1824

25+
// The inherited variant ID is the inherited variant ID from parent contexts, only set when inherited: [NL]
26+
#inheritedVariantId = new UmbClassState<UmbVariantId | undefined>(undefined);
27+
// The display variant ID observables, this is based on the inherited variantID and adapted with the local variant ID: [NL]
28+
public readonly displayVariantId = mergeObservables(
29+
[this.#inheritedVariantId.asObservable(), this.variantId],
30+
([contextual, local]) => {
31+
// If no context, then provide the local: [NL]
32+
if (!contextual) return local;
33+
// If a context, then adjust with the local variant ID specifics. (But only for defined cultures or segments) [NL]
34+
let variantId = contextual.clone();
35+
if (!local) return variantId;
36+
if (!local.isCultureInvariant()) {
37+
variantId = variantId.toCulture(local.culture);
38+
}
39+
if (!local.isSegmentInvariant()) {
40+
variantId = variantId.toSegment(local.segment);
41+
}
42+
return variantId;
43+
},
44+
);
45+
public readonly displayCulture = createObservablePart(this.displayVariantId, (x) => x?.culture);
46+
public readonly displaySegment = createObservablePart(this.displayVariantId, (x) => x?.segment);
47+
1948
#fallbackCulture = new UmbStringState<string | null | undefined>(undefined);
2049
public fallbackCulture = this.#fallbackCulture.asObservable();
2150

2251
#appCulture = new UmbStringState<string | null | undefined>(undefined);
2352
public appCulture = this.#appCulture.asObservable();
2453

25-
public readonly displayCulture = mergeObservables([this.culture, this.appCulture], ([culture, appCulture]) => {
26-
return culture ?? appCulture;
27-
});
28-
2954
constructor(host: UmbControllerHost) {
3055
super(host, UMB_VARIANT_CONTEXT);
3156
}
@@ -37,6 +62,14 @@ export class UmbVariantContext extends UmbContextBase {
3762
*/
3863
inherit(): UmbVariantContext {
3964
this.consumeContext(UMB_VARIANT_CONTEXT, (context) => {
65+
this.observe(
66+
context?.displayVariantId,
67+
(contextualVariantId) => {
68+
this.#inheritedVariantId.setValue(contextualVariantId);
69+
},
70+
'observeContextualVariantId',
71+
);
72+
4073
this.observe(
4174
context?.fallbackCulture,
4275
(fallbackCulture) => {

0 commit comments

Comments
 (0)