@weaverse/hydrogen 6.1.2 → 6.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +5 -6
- package/dist/index.mjs +1 -2
- package/dist/internal.js +18 -29
- package/dist/internal.mjs +3 -14
- package/dist/next-client-boundary.js +1088 -0
- package/dist/next-client-boundary.js.map +1 -0
- package/dist/next-client-boundary.mjs +1038 -0
- package/dist/next-client-boundary.mjs.map +1 -0
- package/dist/next-server.js +669 -13
- package/dist/next-server.js.map +1 -0
- package/dist/next-server.mjs +666 -2
- package/dist/next-server.mjs.map +1 -0
- package/dist/next.js +33 -41
- package/dist/next.mjs +5 -3
- package/dist/react-router.js +6 -7
- package/dist/react-router.mjs +2 -3
- package/dist/seo-B8u2r3rX.mjs +205 -0
- package/dist/seo-B8u2r3rX.mjs.map +1 -0
- package/dist/seo-D9A9uRQ4.js +264 -0
- package/dist/seo-D9A9uRQ4.js.map +1 -0
- package/dist/translate-Celryv8j.mjs +26 -0
- package/dist/translate-Celryv8j.mjs.map +1 -0
- package/dist/translate-Kt1BYL1n.js +37 -0
- package/dist/translate-Kt1BYL1n.js.map +1 -0
- package/dist/{nested-key-CR3-YMFi.js → translation-store-BmGxBALf.js} +46 -137
- package/dist/translation-store-BmGxBALf.js.map +1 -0
- package/dist/{nested-key-_k9aW_Wd.mjs → translation-store-JFs-4lvf.mjs} +35 -138
- package/dist/translation-store-JFs-4lvf.mjs.map +1 -0
- package/dist/types/hooks/translation-context.d.ts +2 -36
- package/dist/types/hooks/translation-context.d.ts.map +1 -1
- package/dist/types/next/client-boundary.d.ts +25 -0
- package/dist/types/next/client-boundary.d.ts.map +1 -0
- package/dist/types/next/client.d.ts +16 -0
- package/dist/types/next/client.d.ts.map +1 -0
- package/dist/types/next/hooks.d.ts +20 -0
- package/dist/types/next/hooks.d.ts.map +1 -0
- package/dist/types/next/index.d.ts +34 -0
- package/dist/types/next/index.d.ts.map +1 -0
- package/dist/types/next/item.d.ts +60 -0
- package/dist/types/next/item.d.ts.map +1 -0
- package/dist/types/next/loader.d.ts +25 -0
- package/dist/types/next/loader.d.ts.map +1 -0
- package/dist/types/next/main.d.ts +11 -0
- package/dist/types/next/main.d.ts.map +1 -0
- package/dist/types/next/provider.d.ts +85 -0
- package/dist/types/next/provider.d.ts.map +1 -0
- package/dist/types/next/registry.d.ts +15 -0
- package/dist/types/next/registry.d.ts.map +1 -0
- package/dist/types/next/renderer.d.ts +21 -0
- package/dist/types/next/renderer.d.ts.map +1 -0
- package/dist/types/next/request-info.d.ts +19 -0
- package/dist/types/next/request-info.d.ts.map +1 -0
- package/dist/types/next/revalidate-item.d.ts +82 -0
- package/dist/types/next/revalidate-item.d.ts.map +1 -0
- package/dist/types/next/root-provider.d.ts +65 -0
- package/dist/types/next/root-provider.d.ts.map +1 -0
- package/dist/types/next/runtime.d.ts +113 -0
- package/dist/types/next/runtime.d.ts.map +1 -0
- package/dist/types/next/seo.d.ts +55 -0
- package/dist/types/next/seo.d.ts.map +1 -0
- package/dist/types/next/server/configs.d.ts +27 -0
- package/dist/types/next/server/configs.d.ts.map +1 -0
- package/dist/types/next/server/normalize-page-url.d.ts +19 -0
- package/dist/types/next/server/normalize-page-url.d.ts.map +1 -0
- package/dist/types/next/server/revalidate-handler.d.ts +55 -0
- package/dist/types/next/server/revalidate-handler.d.ts.map +1 -0
- package/dist/types/next/server/server-client.d.ts +21 -0
- package/dist/types/next/server/server-client.d.ts.map +1 -0
- package/dist/types/next/server.d.ts +19 -0
- package/dist/types/next/server.d.ts.map +1 -0
- package/dist/types/next/studio-bridge.d.ts +16 -0
- package/dist/types/next/studio-bridge.d.ts.map +1 -0
- package/dist/types/next/studio-connect.d.ts +27 -0
- package/dist/types/next/studio-connect.d.ts.map +1 -0
- package/dist/types/next/studio-router.d.ts +50 -0
- package/dist/types/next/studio-router.d.ts.map +1 -0
- package/dist/types/next/studio-script-src.d.ts +12 -0
- package/dist/types/next/studio-script-src.d.ts.map +1 -0
- package/dist/types/next/theme-settings-store.d.ts +18 -0
- package/dist/types/next/theme-settings-store.d.ts.map +1 -0
- package/dist/types/next/translation-context.d.ts +109 -0
- package/dist/types/next/translation-context.d.ts.map +1 -0
- package/dist/types/next/types.d.ts +674 -0
- package/dist/types/next/types.d.ts.map +1 -0
- package/dist/types/next/use-weaverse-next-studio.d.ts +34 -0
- package/dist/types/next/use-weaverse-next-studio.d.ts.map +1 -0
- package/dist/types/next/utils.d.ts +21 -0
- package/dist/types/next/utils.d.ts.map +1 -0
- package/dist/types/next-server.d.ts +3 -1
- package/dist/types/next-server.d.ts.map +1 -1
- package/dist/types/next.d.ts +4 -3
- package/dist/types/next.d.ts.map +1 -1
- package/dist/types/utils/translate.d.ts +51 -0
- package/dist/types/utils/translate.d.ts.map +1 -0
- package/dist/{weaverse-client-C9IgIH7O.js → weaverse-client-BmceZc5q.js} +49 -771
- package/dist/weaverse-client-BmceZc5q.js.map +1 -0
- package/dist/{weaverse-client-D59mBW0j.mjs → weaverse-client-CXnM9ayH.mjs} +4 -720
- package/dist/weaverse-client-CXnM9ayH.mjs.map +1 -0
- package/package.json +13 -7
- package/src/hooks/translation-context.tsx +7 -84
- package/src/next/client-boundary.ts +51 -0
- package/src/next/client.ts +102 -0
- package/src/next/hooks.ts +52 -0
- package/src/next/index.ts +145 -0
- package/src/next/item.ts +167 -0
- package/src/next/loader.ts +135 -0
- package/src/next/main.tsx +45 -0
- package/src/next/provider.tsx +241 -0
- package/src/next/registry.ts +63 -0
- package/src/next/renderer.tsx +102 -0
- package/src/next/request-info.ts +124 -0
- package/src/next/revalidate-item.ts +211 -0
- package/src/next/root-provider.tsx +201 -0
- package/src/next/runtime.ts +559 -0
- package/src/next/seo.ts +157 -0
- package/src/next/server/configs.ts +178 -0
- package/src/next/server/normalize-page-url.ts +73 -0
- package/src/next/server/revalidate-handler.ts +357 -0
- package/src/next/server/server-client.ts +728 -0
- package/src/next/server.ts +67 -0
- package/src/next/studio-bridge.tsx +82 -0
- package/src/next/studio-connect.tsx +88 -0
- package/src/next/studio-router.ts +85 -0
- package/src/next/studio-script-src.ts +128 -0
- package/src/next/theme-settings-store.ts +47 -0
- package/src/next/translation-context.tsx +182 -0
- package/src/next/types.ts +764 -0
- package/src/next/use-weaverse-next-studio.tsx +106 -0
- package/src/next/utils.ts +48 -0
- package/src/next-server.ts +41 -1
- package/src/next.ts +51 -15
- package/src/utils/translate.ts +101 -0
- package/dist/internal.js.map +0 -1
- package/dist/internal.mjs.map +0 -1
- package/dist/nested-key-CR3-YMFi.js.map +0 -1
- package/dist/nested-key-_k9aW_Wd.mjs.map +0 -1
- package/dist/translation-store-4fAV771j.js +0 -69
- package/dist/translation-store-4fAV771j.js.map +0 -1
- package/dist/translation-store-BBrw0FOk.mjs +0 -58
- package/dist/translation-store-BBrw0FOk.mjs.map +0 -1
- package/dist/weaverse-client-C9IgIH7O.js.map +0 -1
- package/dist/weaverse-client-D59mBW0j.mjs.map +0 -1
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
import { Weaverse } from '@weaverse/react'
|
|
2
|
+
import { TranslationStore } from '../utils/translation-store'
|
|
3
|
+
import {
|
|
4
|
+
collectDeferredItemUpdates,
|
|
5
|
+
ensureNextItemConstructor,
|
|
6
|
+
type WeaverseNextItem,
|
|
7
|
+
} from './item'
|
|
8
|
+
import { buildWeaverseNextRequestInfo } from './request-info'
|
|
9
|
+
import { createWeaverseNextThemeSettingsStore } from './theme-settings-store'
|
|
10
|
+
import type {
|
|
11
|
+
WeaverseNextClient,
|
|
12
|
+
WeaverseNextLoaderData,
|
|
13
|
+
WeaverseNextPageData,
|
|
14
|
+
WeaverseNextRequestInfo,
|
|
15
|
+
WeaverseNextRuntimeConfig,
|
|
16
|
+
WeaverseNextRuntimeInternal,
|
|
17
|
+
WeaverseNextTranslationChanges,
|
|
18
|
+
WeaverseNextTranslationEntry,
|
|
19
|
+
WeaverseNextTranslationMap,
|
|
20
|
+
} from './types'
|
|
21
|
+
|
|
22
|
+
declare global {
|
|
23
|
+
interface Window {
|
|
24
|
+
__weaverse?: WeaverseNextRuntime
|
|
25
|
+
__weaverses?: Record<string, WeaverseNextRuntime>
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type StudioBoundRuntime = WeaverseNextRuntime & {
|
|
30
|
+
__weaverseNextStudioBound?: boolean
|
|
31
|
+
__weaverseNextRequestKey?: string
|
|
32
|
+
__weaverseNextLatestData?: WeaverseNextRenderablePage
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getRuntimeWindow(): Window | undefined {
|
|
36
|
+
return typeof window === 'undefined' ? undefined : window
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function getRenderablePage(
|
|
40
|
+
data: WeaverseNextLoaderData
|
|
41
|
+
): WeaverseNextRenderablePage {
|
|
42
|
+
let page = data.page
|
|
43
|
+
let rootId =
|
|
44
|
+
page.rootId ??
|
|
45
|
+
page.items.find((item) => item.type === 'main')?.id ??
|
|
46
|
+
page.items[0]?.id ??
|
|
47
|
+
page.id
|
|
48
|
+
return { ...page, rootId }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function getConfigString(
|
|
52
|
+
configs: Record<string, unknown> | undefined,
|
|
53
|
+
key: string
|
|
54
|
+
): string | undefined {
|
|
55
|
+
let value = configs?.[key]
|
|
56
|
+
return typeof value === 'string' ? value : undefined
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Resolve a non-published mode flag from the request context OR the serialized
|
|
61
|
+
* loader configs.
|
|
62
|
+
*
|
|
63
|
+
* These flags only ever suppress Studio rendering, so the two sources are
|
|
64
|
+
* OR-ed rather than nullish-coalesced. A
|
|
65
|
+
* client-supplied `requestContext` that explicitly sets `false` must not shadow
|
|
66
|
+
* a `true` the server resolved for this request.
|
|
67
|
+
*/
|
|
68
|
+
function resolveModeFlag(
|
|
69
|
+
config: WeaverseNextRuntimeConfig,
|
|
70
|
+
key: 'isDesignMode' | 'isPreviewMode' | 'isRevisionPreview'
|
|
71
|
+
): boolean {
|
|
72
|
+
return (
|
|
73
|
+
config.client?.requestContext?.[key] === true ||
|
|
74
|
+
config.data.configs?.[key] === true
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Section preview type from either source; see {@link resolveModeFlag}. */
|
|
79
|
+
function resolveSectionType(
|
|
80
|
+
config: WeaverseNextRuntimeConfig
|
|
81
|
+
): string | undefined {
|
|
82
|
+
return (
|
|
83
|
+
config.client?.requestContext?.sectionType ||
|
|
84
|
+
getConfigString(config.data.configs, 'sectionType') ||
|
|
85
|
+
undefined
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function getRecordString(
|
|
90
|
+
record: Record<string, unknown> | undefined,
|
|
91
|
+
key: string
|
|
92
|
+
): string | undefined {
|
|
93
|
+
let value = record?.[key]
|
|
94
|
+
return typeof value === 'string' ? value : undefined
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function resolveProjectId(
|
|
98
|
+
client: WeaverseNextClient | undefined,
|
|
99
|
+
data: WeaverseNextLoaderData,
|
|
100
|
+
fallback: string
|
|
101
|
+
): string {
|
|
102
|
+
let projectId = client?.projectId
|
|
103
|
+
if (projectId) {
|
|
104
|
+
return projectId
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
let configProjectId = getConfigString(data.configs, 'projectId')
|
|
108
|
+
if (configProjectId) {
|
|
109
|
+
return configProjectId
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return getRecordString(data.project, 'id') ?? fallback
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function resolveDataContext(config: WeaverseNextRuntimeConfig) {
|
|
116
|
+
if (config.dataContext) {
|
|
117
|
+
return config.dataContext
|
|
118
|
+
}
|
|
119
|
+
if (config.client?.dataContext) {
|
|
120
|
+
return config.client.dataContext
|
|
121
|
+
}
|
|
122
|
+
return {}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function getRuntimeKey(pageId: string, requestInfo: WeaverseNextRequestInfo) {
|
|
126
|
+
return `${pageId}:${requestInfo.pathname}:${requestInfo.search}`
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function registerRuntime(runtime: WeaverseNextRuntime) {
|
|
130
|
+
let runtimeWindow = getRuntimeWindow()
|
|
131
|
+
if (!runtimeWindow) {
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
runtimeWindow.__weaverses ??= {}
|
|
136
|
+
runtimeWindow.__weaverses[runtime.pageId] = runtime
|
|
137
|
+
runtimeWindow.__weaverse = runtime
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Lifecycle invariant: every item store rendered by a runtime must point back
|
|
142
|
+
* at that same runtime (`item.weaverse === runtime`).
|
|
143
|
+
*
|
|
144
|
+
* Core keeps `Weaverse.itemInstances` process-wide and keyed by item id only,
|
|
145
|
+
* so a client navigation that recreates the runtime under a new request key
|
|
146
|
+
* (e.g. `/` → `/fr-fr`) reuses the existing stores and merely calls
|
|
147
|
+
* `setData()` on them — their `weaverse` back-reference is never updated and
|
|
148
|
+
* keeps pointing at the previous runtime. Anything an item reads through that
|
|
149
|
+
* reference then stays stale: `WeaverseNextItem.getSnapShot()` reads
|
|
150
|
+
* `this.weaverse.translationMap` (locale sidecar), and `Element` /
|
|
151
|
+
* `requestInfo` consumers read the old locale's runtime until a full reload
|
|
152
|
+
* builds clean instances.
|
|
153
|
+
*
|
|
154
|
+
* Rebind only the items belonging to the freshly rendered page — other
|
|
155
|
+
* runtimes co-located on the same document own their own item ids and must
|
|
156
|
+
* keep their bindings.
|
|
157
|
+
*/
|
|
158
|
+
function rebindPageItemsToRuntime(runtime: WeaverseNextRuntime) {
|
|
159
|
+
let items = (runtime.data as WeaverseNextPageData | undefined)?.items
|
|
160
|
+
if (!items) {
|
|
161
|
+
return
|
|
162
|
+
}
|
|
163
|
+
for (let { id } of items) {
|
|
164
|
+
let instance = runtime.itemInstances.get(id) as WeaverseNextItem | undefined
|
|
165
|
+
if (instance && instance.weaverse !== runtime) {
|
|
166
|
+
instance.weaverse = runtime
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Weaverse page data with the root component resolved for rendering. */
|
|
172
|
+
export interface WeaverseNextRenderablePage extends WeaverseNextPageData {
|
|
173
|
+
/** Component item ID used as the root of the rendered page tree. */
|
|
174
|
+
rootId: string
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Browser runtime that adapts a Next.js loader payload to `@weaverse/react`
|
|
179
|
+
* and exposes the navigation, settings, and translation state used by Studio.
|
|
180
|
+
*/
|
|
181
|
+
export class WeaverseNextRuntime extends Weaverse {
|
|
182
|
+
/** Stable ID of the page owned by this runtime. */
|
|
183
|
+
pageId: string
|
|
184
|
+
/** Mutable callbacks and stores consumed by the Studio bridge. */
|
|
185
|
+
internal: WeaverseNextRuntimeInternal
|
|
186
|
+
/** Path, query, and locale metadata for Studio navigation. */
|
|
187
|
+
requestInfo: WeaverseNextRequestInfo
|
|
188
|
+
/** Whether this runtime renders a component preview. */
|
|
189
|
+
isPreviewMode: boolean
|
|
190
|
+
/** Whether this runtime renders a saved revision. */
|
|
191
|
+
isRevisionPreview: boolean
|
|
192
|
+
/** Component type rendered in section preview mode. */
|
|
193
|
+
sectionType?: string
|
|
194
|
+
|
|
195
|
+
// ─── Item-level translation sidecar (design mode only) ─────────────
|
|
196
|
+
/**
|
|
197
|
+
* Translation map received from Builder in design mode, keyed by item ID.
|
|
198
|
+
* `WeaverseNextItem.getSnapShot()` overlays these onto the data handed to
|
|
199
|
+
* components, so translations render without mutating the base `_store`.
|
|
200
|
+
*/
|
|
201
|
+
translationMap: WeaverseNextTranslationMap = {}
|
|
202
|
+
/** Locale associated with the item-level translation sidecar. */
|
|
203
|
+
translationLocale = String()
|
|
204
|
+
/** Weaverse language ID used when saving translation changes. */
|
|
205
|
+
translationLanguageId = String()
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Fresh page data captured while React was rendering. Runtime reuse happens
|
|
209
|
+
* inside the renderer's render phase (`useMemo`), where applying it through
|
|
210
|
+
* `setProjectData()` would emit item updates to `useSyncExternalStore`
|
|
211
|
+
* subscribers mid-render. The renderer applies it post-commit via
|
|
212
|
+
* `flushRenderPhaseUpdates()`. Never set on design-mode runtimes — the live
|
|
213
|
+
* Studio tree owns the page data there.
|
|
214
|
+
*/
|
|
215
|
+
pendingProjectData?: WeaverseNextRenderablePage
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Reused item instances whose data was refreshed while this runtime was
|
|
219
|
+
* constructed during render (core `initProject()` calls `setData()` on
|
|
220
|
+
* them). Their data is already fresh; only the subscriber notification was
|
|
221
|
+
* deferred to `flushRenderPhaseUpdates()`.
|
|
222
|
+
*/
|
|
223
|
+
pendingItemUpdates: WeaverseNextItem[] = []
|
|
224
|
+
|
|
225
|
+
/** Create a browser runtime from a loaded page and its request context. */
|
|
226
|
+
constructor(config: WeaverseNextRuntimeConfig) {
|
|
227
|
+
let { client, data } = config
|
|
228
|
+
let page = getRenderablePage(data)
|
|
229
|
+
let configs = data.configs
|
|
230
|
+
let requestContext = client?.requestContext
|
|
231
|
+
let isDesignMode = resolveModeFlag(config, 'isDesignMode')
|
|
232
|
+
let isPreviewMode = resolveModeFlag(config, 'isPreviewMode')
|
|
233
|
+
let isRevisionPreview = resolveModeFlag(config, 'isRevisionPreview')
|
|
234
|
+
let sectionType = resolveSectionType(config)
|
|
235
|
+
let projectId = resolveProjectId(client, data, page.id)
|
|
236
|
+
|
|
237
|
+
ensureNextItemConstructor()
|
|
238
|
+
super({
|
|
239
|
+
projectId,
|
|
240
|
+
data: page,
|
|
241
|
+
isDesignMode,
|
|
242
|
+
weaverseHost: getConfigString(configs, 'weaverseHost'),
|
|
243
|
+
weaverseVersion: getConfigString(configs, 'weaverseVersion'),
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
this.pageId = page.id
|
|
247
|
+
this.dataContext = resolveDataContext(config)
|
|
248
|
+
this.requestInfo = buildWeaverseNextRequestInfo(requestContext)
|
|
249
|
+
this.isDesignMode = isDesignMode
|
|
250
|
+
this.isPreviewMode = isPreviewMode
|
|
251
|
+
this.isRevisionPreview = isRevisionPreview
|
|
252
|
+
this.sectionType = sectionType
|
|
253
|
+
|
|
254
|
+
// One store instance backs both the canonical `translationStore` and the
|
|
255
|
+
// deprecated `themeTextStore` alias, so Builder's `updateStaticText()` RPC
|
|
256
|
+
// (which reads `internal.themeTextStore`) mutates the same store the
|
|
257
|
+
// `TranslationProvider` subscribes to.
|
|
258
|
+
let translationStore = config.translationStore ?? new TranslationStore()
|
|
259
|
+
|
|
260
|
+
this.internal = {
|
|
261
|
+
merchantOverrides: config.merchantOverrides,
|
|
262
|
+
navigate: config.navigate,
|
|
263
|
+
pageAssignment: data.pageAssignment,
|
|
264
|
+
project: data.project,
|
|
265
|
+
revalidate: config.revalidate,
|
|
266
|
+
themeSettingsStore:
|
|
267
|
+
config.themeSettingsStore ??
|
|
268
|
+
createWeaverseNextThemeSettingsStore({
|
|
269
|
+
schema: client?.themeSchema,
|
|
270
|
+
settings: client?.themeSettings,
|
|
271
|
+
}),
|
|
272
|
+
studioSelectionStore: config.studioSelectionStore,
|
|
273
|
+
translationStore,
|
|
274
|
+
themeTextStore: translationStore,
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Read any item-level translation sidecar the loader attached to the page
|
|
278
|
+
// data. Runs after `super()` has built `this.data` and the item stores.
|
|
279
|
+
this.extractTranslationSidecar()
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Pull `translationMap` / `translationLocale` / `translationLanguageId` off
|
|
284
|
+
* the current page data (Builder attaches them in design mode). Unlike
|
|
285
|
+
* Hydrogen, this also clears the sidecar to defaults when the new page data
|
|
286
|
+
* carries none, so a reused runtime / project-data swap can't leave a stale
|
|
287
|
+
* map overlaying the fresh tree.
|
|
288
|
+
*/
|
|
289
|
+
extractTranslationSidecar = () => {
|
|
290
|
+
let pageData = this.data as WeaverseNextPageData | undefined
|
|
291
|
+
let translationMap = pageData?.translationMap as
|
|
292
|
+
| WeaverseNextTranslationMap
|
|
293
|
+
| undefined
|
|
294
|
+
if (translationMap) {
|
|
295
|
+
this.translationMap = translationMap
|
|
296
|
+
this.translationLocale = (pageData?.translationLocale as string) || ''
|
|
297
|
+
this.translationLanguageId =
|
|
298
|
+
(pageData?.translationLanguageId as string) || ''
|
|
299
|
+
} else {
|
|
300
|
+
this.translationMap = {}
|
|
301
|
+
this.translationLocale = ''
|
|
302
|
+
this.translationLanguageId = ''
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Replace the translation sidecar (Builder pushes this when translation data
|
|
308
|
+
* arrives or the active language changes) and refresh every item so their
|
|
309
|
+
* merged snapshots recompute.
|
|
310
|
+
*/
|
|
311
|
+
setTranslationSidecar = (
|
|
312
|
+
map: WeaverseNextTranslationMap = {},
|
|
313
|
+
locale = '',
|
|
314
|
+
languageId = ''
|
|
315
|
+
) => {
|
|
316
|
+
this.translationMap = map
|
|
317
|
+
this.translationLocale = locale
|
|
318
|
+
this.translationLanguageId = languageId
|
|
319
|
+
this.refreshAllItems()
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Update a single translatable field for one item. Assigns a new per-item
|
|
324
|
+
* entry object so `WeaverseNextItem.getSnapShot()`'s translation-ref cache
|
|
325
|
+
* invalidates, then triggers only that item's subscribers.
|
|
326
|
+
*/
|
|
327
|
+
updateTranslation = (
|
|
328
|
+
itemId: string,
|
|
329
|
+
key: string,
|
|
330
|
+
originalValue: string,
|
|
331
|
+
translatedValue: string
|
|
332
|
+
) => {
|
|
333
|
+
if (!this.translationMap[itemId]) {
|
|
334
|
+
this.translationMap[itemId] = {}
|
|
335
|
+
}
|
|
336
|
+
// New entry reference so the item's snapshot cache detects the change.
|
|
337
|
+
this.translationMap[itemId] = {
|
|
338
|
+
...this.translationMap[itemId],
|
|
339
|
+
[key]: { originalValue, translatedValue },
|
|
340
|
+
}
|
|
341
|
+
let instance = this.itemInstances.get(itemId)
|
|
342
|
+
if (instance) {
|
|
343
|
+
instance.triggerUpdate()
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Collect translation changes for the save flow. Returns `undefined` unless
|
|
349
|
+
* both the locale and language ID are set and at least one entry exists;
|
|
350
|
+
* each entry key is namespaced as `data.<field>` to match Builder.
|
|
351
|
+
*/
|
|
352
|
+
getTranslationChanges = (): WeaverseNextTranslationChanges | undefined => {
|
|
353
|
+
let languageId = this.translationLanguageId
|
|
354
|
+
if (!(this.translationLocale && languageId)) {
|
|
355
|
+
return
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
let entries: WeaverseNextTranslationEntry[] = []
|
|
359
|
+
for (let [itemId, fields] of Object.entries(this.translationMap)) {
|
|
360
|
+
for (let [key, entry] of Object.entries(fields)) {
|
|
361
|
+
entries.push({
|
|
362
|
+
itemId,
|
|
363
|
+
key: `data.${key}`,
|
|
364
|
+
originalValue: entry.originalValue,
|
|
365
|
+
translatedValue: entry.translatedValue,
|
|
366
|
+
})
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (entries.length === 0) {
|
|
371
|
+
return
|
|
372
|
+
}
|
|
373
|
+
return { languageId, entries }
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Re-extract the translation sidecar whenever project data is replaced (route
|
|
378
|
+
* change / non-design reuse), before rebuilding item stores. Takes the
|
|
379
|
+
* renderable page shape (`rootId` resolved) that `getRenderablePage()` and
|
|
380
|
+
* the reuse branch pass in.
|
|
381
|
+
*/
|
|
382
|
+
setProjectData = (data: WeaverseNextRenderablePage) => {
|
|
383
|
+
this.data = data
|
|
384
|
+
this.extractTranslationSidecar()
|
|
385
|
+
this.initProject()
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Apply updates deferred off the render phase: swap in any pending page data
|
|
390
|
+
* captured on runtime reuse, then notify items whose data was refreshed
|
|
391
|
+
* during construction. The renderer calls this from a layout effect right
|
|
392
|
+
* after commit, so subscribers re-render before paint (no content flash) and
|
|
393
|
+
* without React's setState-in-render warning.
|
|
394
|
+
*/
|
|
395
|
+
flushRenderPhaseUpdates = () => {
|
|
396
|
+
let pendingData = this.pendingProjectData
|
|
397
|
+
if (pendingData) {
|
|
398
|
+
this.pendingProjectData = undefined
|
|
399
|
+
this.setProjectData(pendingData)
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
let pendingItems = this.pendingItemUpdates
|
|
403
|
+
if (pendingItems.length > 0) {
|
|
404
|
+
this.pendingItemUpdates = []
|
|
405
|
+
for (let item of pendingItems) {
|
|
406
|
+
item.triggerUpdate()
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Create a browser runtime or reuse the runtime already registered for the
|
|
414
|
+
* same page and request. Reuse preserves unsaved Studio state in design mode.
|
|
415
|
+
*/
|
|
416
|
+
export function createWeaverseNextRuntime(
|
|
417
|
+
config: WeaverseNextRuntimeConfig
|
|
418
|
+
): WeaverseNextRuntime {
|
|
419
|
+
let requestInfo = buildWeaverseNextRequestInfo(config.client?.requestContext)
|
|
420
|
+
let page = getRenderablePage(config.data)
|
|
421
|
+
let runtimeWindow = getRuntimeWindow()
|
|
422
|
+
let existing = runtimeWindow?.__weaverses?.[page.id] as
|
|
423
|
+
| StudioBoundRuntime
|
|
424
|
+
| undefined
|
|
425
|
+
let requestKey = getRuntimeKey(page.id, requestInfo)
|
|
426
|
+
|
|
427
|
+
if (existing?.__weaverseNextRequestKey === requestKey) {
|
|
428
|
+
let configs = config.data.configs
|
|
429
|
+
let nextIsDesignMode = resolveModeFlag(config, 'isDesignMode')
|
|
430
|
+
let nextIsPreviewMode = resolveModeFlag(config, 'isPreviewMode')
|
|
431
|
+
let nextIsRevisionPreview = resolveModeFlag(config, 'isRevisionPreview')
|
|
432
|
+
let nextSectionType = resolveSectionType(config)
|
|
433
|
+
// In design mode the live Studio runtime owns the page tree, including
|
|
434
|
+
// unsaved drafts. Reapplying loader `page` data here would clobber those
|
|
435
|
+
// edits, so leave the project data untouched and let
|
|
436
|
+
// `bindWeaverseNextStudioRuntime` push the latest state via `refreshStudio`.
|
|
437
|
+
// Published / preview reuse has no draft state, so fresh data still wins —
|
|
438
|
+
// but this factory runs during React render (the renderer's `useMemo`),
|
|
439
|
+
// and `setProjectData()` → `initProject()` → `setData()` would emit item
|
|
440
|
+
// updates to `useSyncExternalStore` subscribers mid-render (React's
|
|
441
|
+
// setState-in-render warning). Stash the page instead; the renderer
|
|
442
|
+
// applies it post-commit via `flushRenderPhaseUpdates()`.
|
|
443
|
+
if (existing.isDesignMode || nextIsDesignMode) {
|
|
444
|
+
existing.pendingProjectData = undefined
|
|
445
|
+
} else {
|
|
446
|
+
existing.pendingProjectData = page
|
|
447
|
+
}
|
|
448
|
+
// Always capture the latest server payload. In design mode the live tree
|
|
449
|
+
// (`existing.data`) is deliberately left untouched above, so it goes stale
|
|
450
|
+
// after an RSC refresh — `bindWeaverseNextStudioRuntime` must report this
|
|
451
|
+
// fresh payload to `refreshStudio`, not the stale tree, or a revalidation
|
|
452
|
+
// (e.g. resource-picker pick) merges back old per-item `loaderData` and the
|
|
453
|
+
// preview keeps rendering the previous resource. Mirrors Hydrogen, which
|
|
454
|
+
// always passes the fresh loader params to `refreshStudio`.
|
|
455
|
+
existing.__weaverseNextLatestData = page
|
|
456
|
+
existing.dataContext = resolveDataContext(config)
|
|
457
|
+
existing.requestInfo = requestInfo
|
|
458
|
+
existing.projectId = resolveProjectId(config.client, config.data, page.id)
|
|
459
|
+
existing.isDesignMode = nextIsDesignMode
|
|
460
|
+
existing.isPreviewMode = nextIsPreviewMode
|
|
461
|
+
existing.isRevisionPreview = nextIsRevisionPreview
|
|
462
|
+
existing.sectionType = nextSectionType
|
|
463
|
+
existing.weaverseHost =
|
|
464
|
+
getConfigString(configs, 'weaverseHost') ?? existing.weaverseHost
|
|
465
|
+
existing.weaverseVersion =
|
|
466
|
+
getConfigString(configs, 'weaverseVersion') ?? existing.weaverseVersion
|
|
467
|
+
existing.internal.pageAssignment = config.data.pageAssignment
|
|
468
|
+
existing.internal.project = config.data.project
|
|
469
|
+
if (config.navigate) {
|
|
470
|
+
existing.internal.navigate = config.navigate
|
|
471
|
+
}
|
|
472
|
+
if (config.revalidate) {
|
|
473
|
+
existing.internal.revalidate = config.revalidate
|
|
474
|
+
}
|
|
475
|
+
if (config.themeSettingsStore) {
|
|
476
|
+
existing.internal.themeSettingsStore = config.themeSettingsStore
|
|
477
|
+
}
|
|
478
|
+
if (config.studioSelectionStore) {
|
|
479
|
+
existing.internal.studioSelectionStore = config.studioSelectionStore
|
|
480
|
+
}
|
|
481
|
+
if (config.translationStore) {
|
|
482
|
+
existing.internal.translationStore = config.translationStore
|
|
483
|
+
// Keep the deprecated alias pointing at the same adopted instance.
|
|
484
|
+
existing.internal.themeTextStore = config.translationStore
|
|
485
|
+
}
|
|
486
|
+
// Always assign, including `undefined`, so locale/navigation changes can
|
|
487
|
+
// clear previously attached merchant overrides instead of leaking stale
|
|
488
|
+
// static-text values into the next render.
|
|
489
|
+
existing.internal.merchantOverrides = config.merchantOverrides
|
|
490
|
+
registerRuntime(existing)
|
|
491
|
+
return existing
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Constructing a fresh runtime also happens during render, and core
|
|
495
|
+
// `initProject()` reuses process-wide item instances holding the same item
|
|
496
|
+
// ids (locale/client navigation recreates the runtime under a new request
|
|
497
|
+
// key) by calling `setData()` on them. Defer those subscriber emits to the
|
|
498
|
+
// renderer's post-commit `flushRenderPhaseUpdates()` as well.
|
|
499
|
+
let [runtime, refreshedItems] = collectDeferredItemUpdates(
|
|
500
|
+
() => new WeaverseNextRuntime(config) as StudioBoundRuntime
|
|
501
|
+
)
|
|
502
|
+
runtime.pendingItemUpdates = refreshedItems
|
|
503
|
+
// Reused stores are still bound to the runtime that built them; rebind before
|
|
504
|
+
// returning so consumers read snapshots through this runtime (see
|
|
505
|
+
// `rebindPageItemsToRuntime`). Rebinding touches no subscriber, so it stays
|
|
506
|
+
// render-phase safe alongside the deferred item updates above.
|
|
507
|
+
rebindPageItemsToRuntime(runtime)
|
|
508
|
+
runtime.__weaverseNextRequestKey = requestKey
|
|
509
|
+
runtime.__weaverseNextLatestData = page
|
|
510
|
+
registerRuntime(runtime)
|
|
511
|
+
return runtime
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Bind a design-mode runtime to the global Builder Studio bridge.
|
|
516
|
+
*
|
|
517
|
+
* @returns `true` when Studio was initialized or refreshed, otherwise `false`.
|
|
518
|
+
*/
|
|
519
|
+
export function bindWeaverseNextStudioRuntime(runtime: WeaverseNextRuntime) {
|
|
520
|
+
if (!runtime.isDesignMode) {
|
|
521
|
+
return false
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
let studio = getRuntimeWindow()?.weaverseStudio
|
|
525
|
+
if (!studio) {
|
|
526
|
+
return false
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
let boundRuntime = runtime as StudioBoundRuntime
|
|
530
|
+
// Builder's bridge tracks a single active runtime (`studio.weaverse`). When
|
|
531
|
+
// an already-bound runtime is reused after navigating away and back (Home ->
|
|
532
|
+
// PDP -> Home), the bridge is still on the other URL, and Builder ignores
|
|
533
|
+
// `refreshStudio` for a non-active pageId — Studio would stay disconnected
|
|
534
|
+
// (outline skeleton, publish disabled). Re-init only when the active runtime
|
|
535
|
+
// belongs to a different URL; same-URL co-located runtimes must stay on the
|
|
536
|
+
// refresh path so the SDK does not override Builder's editable-instance
|
|
537
|
+
// choice.
|
|
538
|
+
let activeRuntime = studio.weaverse as WeaverseNextRuntime | undefined
|
|
539
|
+
let activeRequestInfo = activeRuntime?.requestInfo
|
|
540
|
+
let isActiveUrl =
|
|
541
|
+
!activeRequestInfo ||
|
|
542
|
+
(activeRequestInfo.pathname === runtime.requestInfo.pathname &&
|
|
543
|
+
activeRequestInfo.search === runtime.requestInfo.search)
|
|
544
|
+
if (!(boundRuntime.__weaverseNextStudioBound && isActiveUrl)) {
|
|
545
|
+
studio.init?.(runtime)
|
|
546
|
+
boundRuntime.__weaverseNextStudioBound = true
|
|
547
|
+
return true
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// Report the latest server payload (fresh per-item `loaderData`), not the
|
|
551
|
+
// live tree: Builder's `refreshStudio` merges the editor draft's structural
|
|
552
|
+
// edits back on top and only takes the loader payload from this data.
|
|
553
|
+
studio.refreshStudio?.({
|
|
554
|
+
data: boundRuntime.__weaverseNextLatestData ?? runtime.data,
|
|
555
|
+
pageId: runtime.pageId,
|
|
556
|
+
requestInfo: runtime.requestInfo,
|
|
557
|
+
})
|
|
558
|
+
return true
|
|
559
|
+
}
|