Skip to content

Commit a201bbd

Browse files
Preview segment (#19203)
* add new manifest * wip element * add segment to preview context * add segment icon * open preview route on the same server * Update preview.context.ts * clean up * pass culture and segment to preview window
1 parent 04a10c2 commit a201bbd

File tree

4 files changed

+207
-27
lines changed

4 files changed

+207
-27
lines changed

src/Umbraco.Web.UI.Client/src/apps/preview/apps/manifests.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@ export const manifests: Array<ManifestPreviewAppProvider> = [
1515
element: () => import('./preview-culture.element.js'),
1616
weight: 300,
1717
},
18+
{
19+
type: 'previewApp',
20+
alias: 'Umb.PreviewApps.Segment',
21+
name: 'Preview: Segment Switcher',
22+
element: () => import('./preview-segment.element.js'),
23+
weight: 290,
24+
},
1825
{
1926
type: 'previewApp',
2027
alias: 'Umb.PreviewApps.OpenWebsite',
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { UMB_PREVIEW_CONTEXT } from '../preview.context.js';
2+
import {
3+
css,
4+
customElement,
5+
html,
6+
nothing,
7+
repeat,
8+
state,
9+
type PropertyValues,
10+
} from '@umbraco-cms/backoffice/external/lit';
11+
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
12+
import { UmbSegmentCollectionRepository, type UmbSegmentCollectionItemModel } from '@umbraco-cms/backoffice/segment';
13+
14+
@customElement('umb-preview-segment')
15+
export class UmbPreviewSegmentElement extends UmbLitElement {
16+
#segmentRepository = new UmbSegmentCollectionRepository(this);
17+
18+
@state()
19+
private _segment?: UmbSegmentCollectionItemModel;
20+
21+
@state()
22+
private _segments: Array<UmbSegmentCollectionItemModel> = [];
23+
24+
protected override firstUpdated(_changedProperties: PropertyValues): void {
25+
super.firstUpdated(_changedProperties);
26+
this.#loadSegments();
27+
}
28+
29+
async #loadSegments() {
30+
const { data } = await this.#segmentRepository.requestCollection({ skip: 0, take: 100 });
31+
this._segments = data?.items ?? [];
32+
33+
const searchParams = new URLSearchParams(window.location.search);
34+
const segment = searchParams.get('segment');
35+
36+
if (segment && segment !== this._segment?.unique) {
37+
this._segment = this._segments.find((c) => c.unique === segment);
38+
}
39+
}
40+
41+
async #onClick(segment?: UmbSegmentCollectionItemModel) {
42+
if (this._segment === segment) return;
43+
this._segment = segment;
44+
45+
const previewContext = await this.getContext(UMB_PREVIEW_CONTEXT);
46+
previewContext?.updateIFrame({ segment: segment?.unique });
47+
}
48+
49+
override render() {
50+
if (this._segments.length <= 1) return nothing;
51+
return html`
52+
<uui-button look="primary" popovertarget="segments-popover">
53+
<div>
54+
<uui-icon name="icon-filter"></uui-icon>
55+
<span>${this._segment?.name ?? 'Segments'}</span>
56+
</div>
57+
</uui-button>
58+
<uui-popover-container id="segments-popover" placement="top-end">
59+
<umb-popover-layout>
60+
<uui-menu-item label="Default" ?active=${!this._segment} @click=${() => this.#onClick()}></uui-menu-item>
61+
${repeat(
62+
this._segments,
63+
(item) => item.unique,
64+
(item) => html`
65+
<uui-menu-item
66+
label=${item.name}
67+
?active=${item.unique === this._segment?.unique}
68+
@click=${() => this.#onClick(item)}>
69+
</uui-menu-item>
70+
`,
71+
)}
72+
</umb-popover-layout>
73+
</uui-popover-container>
74+
`;
75+
}
76+
77+
static override styles = [
78+
css`
79+
:host {
80+
display: flex;
81+
border-left: 1px solid var(--uui-color-header-contrast);
82+
--uui-button-font-weight: 400;
83+
--uui-button-padding-left-factor: 3;
84+
--uui-button-padding-right-factor: 3;
85+
}
86+
87+
uui-button > div {
88+
display: flex;
89+
align-items: center;
90+
gap: 5px;
91+
}
92+
93+
umb-popover-layout {
94+
--uui-color-surface: var(--uui-color-header-surface);
95+
--uui-color-border: var(--uui-color-header-surface);
96+
color: var(--uui-color-header-contrast);
97+
}
98+
`,
99+
];
100+
}
101+
102+
export { UmbPreviewSegmentElement as element };
103+
104+
declare global {
105+
interface HTMLElementTagNameMap {
106+
'umb-preview-segment': UmbPreviewSegmentElement;
107+
}
108+
}

src/Umbraco.Web.UI.Client/src/apps/preview/preview.context.ts

Lines changed: 81 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,28 @@ import { UMB_SERVER_CONTEXT } from '@umbraco-cms/backoffice/server';
88

99
const UMB_LOCALSTORAGE_SESSION_KEY = 'umb:previewSessions';
1010

11+
interface UmbPreviewIframeArgs {
12+
className?: string;
13+
culture?: string;
14+
height?: string;
15+
segment?: string;
16+
width?: string;
17+
}
18+
19+
interface UmbPreviewUrlArgs {
20+
culture?: string | null;
21+
rnd?: number;
22+
segment?: string | null;
23+
serverUrl?: string;
24+
unique?: string | null;
25+
}
26+
1127
export class UmbPreviewContext extends UmbContextBase {
28+
#unique?: string | null;
1229
#culture?: string | null;
30+
#segment?: string | null;
1331
#serverUrl: string = '';
1432
#webSocket?: WebSocket;
15-
#unique?: string | null;
1633

1734
#iframeReady = new UmbBooleanState(false);
1835
public readonly iframeReady = this.#iframeReady.asObservable();
@@ -30,8 +47,9 @@ export class UmbPreviewContext extends UmbContextBase {
3047

3148
const params = new URLSearchParams(window.location.search);
3249

33-
this.#culture = params.get('culture');
3450
this.#unique = params.get('id');
51+
this.#culture = params.get('culture');
52+
this.#segment = params.get('segment');
3553

3654
if (!this.#unique) {
3755
console.error('No unique ID found in query string.');
@@ -75,16 +93,46 @@ export class UmbPreviewContext extends UmbContextBase {
7593
return Math.max(Number(localStorage.getItem(UMB_LOCALSTORAGE_SESSION_KEY)), 0) || 0;
7694
}
7795

78-
#setPreviewUrl(args?: { serverUrl?: string; unique?: string | null; culture?: string | null; rnd?: number }) {
96+
#setPreviewUrl(args?: UmbPreviewUrlArgs) {
7997
const host = args?.serverUrl || this.#serverUrl;
80-
const path = args?.unique || this.#unique;
81-
const params = new URLSearchParams();
98+
const unique = args?.unique || this.#unique;
99+
100+
if (!unique) {
101+
throw new Error('No unique ID found in query string.');
102+
}
103+
104+
const url = new URL(unique, host);
105+
const params = new URLSearchParams(url.search);
106+
82107
const culture = args?.culture || this.#culture;
108+
const segment = args?.segment || this.#segment;
83109

84-
if (culture) params.set('culture', culture);
85-
if (args?.rnd) params.set('rnd', args.rnd.toString());
110+
const cultureParam = 'culture';
111+
const rndParam = 'rnd';
112+
const segmentParam = 'segment';
86113

87-
this.#previewUrl.setValue(`${host}/${path}?${params}`);
114+
if (culture) {
115+
params.set(cultureParam, culture);
116+
} else {
117+
params.delete(cultureParam);
118+
}
119+
120+
if (args?.rnd) {
121+
params.set(rndParam, args.rnd.toString());
122+
} else {
123+
params.delete(rndParam);
124+
}
125+
126+
if (segment) {
127+
params.set(segmentParam, segment);
128+
} else {
129+
params.delete(segmentParam);
130+
}
131+
132+
const previewUrl = new URL(url.pathname + '?' + params.toString(), host);
133+
const previewUrlString = previewUrl.toString();
134+
135+
this.#previewUrl.setValue(previewUrlString);
88136
}
89137

90138
#setSessionCount(sessions: number) {
@@ -165,8 +213,9 @@ export class UmbPreviewContext extends UmbContextBase {
165213
this.#setSessionCount(sessions);
166214
}
167215

168-
async updateIFrame(args?: { culture?: string; className?: string; height?: string; width?: string }) {
169-
if (!args) return;
216+
#currentArgs: UmbPreviewIframeArgs = {};
217+
async updateIFrame(args?: UmbPreviewIframeArgs) {
218+
const mergedArgs = { ...this.#currentArgs, ...args };
170219

171220
const wrapper = this.getIFrameWrapper();
172221
if (!wrapper) return;
@@ -185,20 +234,32 @@ export class UmbPreviewContext extends UmbContextBase {
185234
window.addEventListener('resize', scaleIFrame);
186235
wrapper.addEventListener('transitionend', scaleIFrame);
187236

188-
if (args.culture) {
189-
this.#iframeReady.setValue(false);
237+
this.#iframeReady.setValue(false);
190238

191-
const params = new URLSearchParams(window.location.search);
192-
params.set('culture', args.culture);
193-
const newRelativePathQuery = window.location.pathname + '?' + params.toString();
194-
history.pushState(null, '', newRelativePathQuery);
239+
const params = new URLSearchParams(window.location.search);
240+
241+
if (mergedArgs.culture) {
242+
params.set('culture', mergedArgs.culture);
243+
} else {
244+
params.delete('culture');
245+
}
195246

196-
this.#setPreviewUrl({ culture: args.culture });
247+
if (mergedArgs.segment) {
248+
params.set('segment', mergedArgs.segment);
249+
} else {
250+
params.delete('segment');
197251
}
198252

199-
if (args.className) wrapper.className = args.className;
200-
if (args.height) wrapper.style.height = args.height;
201-
if (args.width) wrapper.style.width = args.width;
253+
const newRelativePathQuery = window.location.pathname + '?' + params.toString();
254+
history.pushState(null, '', newRelativePathQuery);
255+
256+
this.#currentArgs = mergedArgs;
257+
258+
this.#setPreviewUrl({ culture: mergedArgs.culture, segment: mergedArgs.segment });
259+
260+
if (mergedArgs.className) wrapper.className = mergedArgs.className;
261+
if (mergedArgs.height) wrapper.style.height = mergedArgs.height;
262+
if (mergedArgs.width) wrapper.style.width = mergedArgs.width;
202263
}
203264
}
204265

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

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { UmbDocumentValidationRepository } from '../repository/validation/index.
2020
import { UMB_DOCUMENT_CONFIGURATION_CONTEXT } from '../index.js';
2121
import { UMB_DOCUMENT_DETAIL_MODEL_VARIANT_SCAFFOLD, UMB_DOCUMENT_WORKSPACE_ALIAS } from './constants.js';
2222
import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity';
23-
import { UMB_INVARIANT_CULTURE, UmbVariantId } from '@umbraco-cms/backoffice/variant';
23+
import { UmbVariantId } from '@umbraco-cms/backoffice/variant';
2424
import {
2525
type UmbPublishableWorkspaceContext,
2626
UmbWorkspaceIsNewRedirectController,
@@ -362,13 +362,13 @@ export class UmbDocumentWorkspaceContext
362362
const unique = this.getUnique();
363363
if (!unique) throw new Error('Unique is missing');
364364

365-
let culture = UMB_INVARIANT_CULTURE;
365+
let firstVariantId = UmbVariantId.CreateInvariant();
366366

367367
// Save document (the active variant) before previewing.
368368
const { selected } = await this._determineVariantOptions();
369369
if (selected.length > 0) {
370-
culture = selected[0];
371-
const variantIds = [UmbVariantId.FromString(culture)];
370+
firstVariantId = UmbVariantId.FromString(selected[0]);
371+
const variantIds = [firstVariantId];
372372
const saveData = await this._data.constructData(variantIds);
373373
await this.runMandatoryValidationForSaveData(saveData);
374374
await this.performCreateOrUpdate(variantIds, saveData);
@@ -383,11 +383,15 @@ export class UmbDocumentWorkspaceContext
383383
}
384384

385385
const backofficePath = serverContext.getBackofficePath();
386-
const previewUrl = new URL(ensurePathEndsWithSlash(backofficePath) + 'preview', serverContext.getServerUrl());
386+
const previewUrl = new URL(ensurePathEndsWithSlash(backofficePath) + 'preview', window.location.origin);
387387
previewUrl.searchParams.set('id', unique);
388388

389-
if (culture && culture !== UMB_INVARIANT_CULTURE) {
390-
previewUrl.searchParams.set('culture', culture);
389+
if (firstVariantId.culture) {
390+
previewUrl.searchParams.set('culture', firstVariantId.culture);
391+
}
392+
393+
if (firstVariantId.segment) {
394+
previewUrl.searchParams.set('segment', firstVariantId.segment);
391395
}
392396

393397
const previewWindow = window.open(previewUrl.toString(), `umbpreview-${unique}`);

0 commit comments

Comments
 (0)