@accelerated-agency/visual-editor 0.2.1 → 0.2.3
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 +115 -113
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5,6 +5,11 @@ import { persist } from 'zustand/middleware';
|
|
|
5
5
|
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
6
6
|
import { HexColorPicker } from 'react-colorful';
|
|
7
7
|
|
|
8
|
+
var PERSIST_NAME_PREFIX = "conversion-editor-variations-v3-";
|
|
9
|
+
function variationsPersistStorageName(experimentId) {
|
|
10
|
+
const id = experimentId?.trim();
|
|
11
|
+
return `${PERSIST_NAME_PREFIX}${id && id.length > 0 ? id : "standalone"}`;
|
|
12
|
+
}
|
|
8
13
|
var VARIATION_COLORS = [
|
|
9
14
|
"#0084D1",
|
|
10
15
|
"#F54A00",
|
|
@@ -77,16 +82,12 @@ var useVariationsStore = create()(
|
|
|
77
82
|
}));
|
|
78
83
|
},
|
|
79
84
|
setActiveVariation: (id) => set({ activeVariationId: id }),
|
|
80
|
-
addMutationToActive: (mutation) => set((s) => {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
// Clear redo stack for active variation on new mutation
|
|
87
|
-
redoStacks: { ...s.redoStacks, [s.activeVariationId]: [] }
|
|
88
|
-
};
|
|
89
|
-
}),
|
|
85
|
+
addMutationToActive: (mutation) => set((s) => ({
|
|
86
|
+
variations: s.variations.map(
|
|
87
|
+
(v) => v.id === s.activeVariationId ? { ...v, mutations: [...v.mutations, mutation] } : v
|
|
88
|
+
),
|
|
89
|
+
redoStacks: { ...s.redoStacks, [s.activeVariationId]: [] }
|
|
90
|
+
})),
|
|
90
91
|
removeLastMutationFromActive: () => set((s) => {
|
|
91
92
|
const active = s.variations.find((v) => v.id === s.activeVariationId);
|
|
92
93
|
const removed = active?.mutations[active.mutations.length - 1];
|
|
@@ -149,7 +150,8 @@ var useVariationsStore = create()(
|
|
|
149
150
|
}
|
|
150
151
|
}),
|
|
151
152
|
{
|
|
152
|
-
name:
|
|
153
|
+
name: variationsPersistStorageName(void 0),
|
|
154
|
+
skipHydration: true,
|
|
153
155
|
partialize: (state) => ({
|
|
154
156
|
variations: state.variations,
|
|
155
157
|
activeVariationId: state.activeVariationId
|
|
@@ -157,6 +159,15 @@ var useVariationsStore = create()(
|
|
|
157
159
|
}
|
|
158
160
|
)
|
|
159
161
|
);
|
|
162
|
+
async function hydrateVariationsFromStorage(experimentId) {
|
|
163
|
+
useVariationsStore.persist.setOptions({
|
|
164
|
+
name: variationsPersistStorageName(experimentId)
|
|
165
|
+
});
|
|
166
|
+
try {
|
|
167
|
+
await useVariationsStore.persist.rehydrate();
|
|
168
|
+
} catch {
|
|
169
|
+
}
|
|
170
|
+
}
|
|
160
171
|
function TopBar({
|
|
161
172
|
connectionStatus,
|
|
162
173
|
onLoadUrl,
|
|
@@ -1832,13 +1843,10 @@ function ElementIcon({ tag }) {
|
|
|
1832
1843
|
return /* @__PURE__ */ jsx("span", { className: "shrink-0 w-5 h-5 rounded flex items-center justify-center", style: { backgroundColor: bgColor }, children: /* @__PURE__ */ jsx("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", children: /* @__PURE__ */ jsx("rect", { x: "1.5", y: "1.5", width: "9", height: "9", rx: "1.5", stroke: iconColor, strokeWidth: "1.2" }) }) });
|
|
1833
1844
|
}
|
|
1834
1845
|
var CHANNEL = "conversion-editor";
|
|
1835
|
-
var IFRAME_LOAD_GUARD_MS = 13e4;
|
|
1836
1846
|
function IframeCanvas({ url, password, proxyBaseUrl = "", onBridgeReady, onPong }) {
|
|
1837
1847
|
const iframeElRef = useRef(null);
|
|
1838
|
-
const loadGuardRef = useRef(null);
|
|
1839
1848
|
const setSelectedElement = useMutationsStore((s) => s.setSelectedElement);
|
|
1840
1849
|
const addMutationToActive = useVariationsStore((s) => s.addMutationToActive);
|
|
1841
|
-
const [loading, setLoading] = useState(false);
|
|
1842
1850
|
useEffect(() => {
|
|
1843
1851
|
setIframeRef(iframeElRef.current);
|
|
1844
1852
|
return () => setIframeRef(null);
|
|
@@ -1877,25 +1885,6 @@ function IframeCanvas({ url, password, proxyBaseUrl = "", onBridgeReady, onPong
|
|
|
1877
1885
|
window.addEventListener("message", handleMessage);
|
|
1878
1886
|
return () => window.removeEventListener("message", handleMessage);
|
|
1879
1887
|
}, [handleMessage]);
|
|
1880
|
-
useEffect(() => {
|
|
1881
|
-
if (!url) return;
|
|
1882
|
-
setLoading(true);
|
|
1883
|
-
if (loadGuardRef.current) clearTimeout(loadGuardRef.current);
|
|
1884
|
-
loadGuardRef.current = setTimeout(() => {
|
|
1885
|
-
loadGuardRef.current = null;
|
|
1886
|
-
setLoading(false);
|
|
1887
|
-
}, IFRAME_LOAD_GUARD_MS);
|
|
1888
|
-
return () => {
|
|
1889
|
-
if (loadGuardRef.current) clearTimeout(loadGuardRef.current);
|
|
1890
|
-
};
|
|
1891
|
-
}, [url]);
|
|
1892
|
-
const clearLoadGuard = useCallback(() => {
|
|
1893
|
-
if (loadGuardRef.current) {
|
|
1894
|
-
clearTimeout(loadGuardRef.current);
|
|
1895
|
-
loadGuardRef.current = null;
|
|
1896
|
-
}
|
|
1897
|
-
setLoading(false);
|
|
1898
|
-
}, []);
|
|
1899
1888
|
let resolvedUrl;
|
|
1900
1889
|
if (url.toLowerCase() === "test") {
|
|
1901
1890
|
resolvedUrl = "/test";
|
|
@@ -1910,27 +1899,16 @@ function IframeCanvas({ url, password, proxyBaseUrl = "", onBridgeReady, onPong
|
|
|
1910
1899
|
/* @__PURE__ */ jsx("p", { className: "text-sm text-gray-400", children: 'Load a page from the Page dropdown in the top bar, or type "test" for the local test page' })
|
|
1911
1900
|
] });
|
|
1912
1901
|
}
|
|
1913
|
-
return /* @__PURE__ */
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
"iframe",
|
|
1924
|
-
{
|
|
1925
|
-
ref: iframeElRef,
|
|
1926
|
-
src: resolvedUrl,
|
|
1927
|
-
className: "w-full h-full border-0",
|
|
1928
|
-
sandbox: "allow-scripts allow-same-origin allow-forms allow-popups",
|
|
1929
|
-
onLoad: clearLoadGuard,
|
|
1930
|
-
onError: clearLoadGuard
|
|
1931
|
-
}
|
|
1932
|
-
)
|
|
1933
|
-
] });
|
|
1902
|
+
return /* @__PURE__ */ jsx("div", { className: "relative w-full h-full", children: /* @__PURE__ */ jsx(
|
|
1903
|
+
"iframe",
|
|
1904
|
+
{
|
|
1905
|
+
ref: iframeElRef,
|
|
1906
|
+
src: resolvedUrl,
|
|
1907
|
+
className: "w-full h-full border-0",
|
|
1908
|
+
sandbox: "allow-scripts allow-same-origin allow-forms allow-popups",
|
|
1909
|
+
title: "Visual editor page"
|
|
1910
|
+
}
|
|
1911
|
+
) });
|
|
1934
1912
|
}
|
|
1935
1913
|
function ElementOverlayToolbar() {
|
|
1936
1914
|
const selectedElement = useMutationsStore((s) => s.selectedElement);
|
|
@@ -4078,6 +4056,9 @@ function sendToPlatform(type, payload) {
|
|
|
4078
4056
|
data: { channel: PLATFORM_CHANNEL, type, payload }
|
|
4079
4057
|
}));
|
|
4080
4058
|
}
|
|
4059
|
+
function experimentIframeContextKey(exp) {
|
|
4060
|
+
return [exp.experimentId ?? "", exp.pageUrl ?? "", exp.editorPassword ?? ""].join("");
|
|
4061
|
+
}
|
|
4081
4062
|
function EditorShell({ initialExperiment, embeddedMode, proxyBaseUrl }) {
|
|
4082
4063
|
const [url, setUrl] = useState("");
|
|
4083
4064
|
const [password, setPassword] = useState("");
|
|
@@ -4090,7 +4071,8 @@ function EditorShell({ initialExperiment, embeddedMode, proxyBaseUrl }) {
|
|
|
4090
4071
|
);
|
|
4091
4072
|
const [experimentData, setExperimentData] = useState(null);
|
|
4092
4073
|
const experimentDataRef = useRef(null);
|
|
4093
|
-
const
|
|
4074
|
+
const lastAppliedRunKeyRef = useRef("");
|
|
4075
|
+
const iframeContextKeyRef = useRef("");
|
|
4094
4076
|
useEffect(() => {
|
|
4095
4077
|
experimentDataRef.current = experimentData;
|
|
4096
4078
|
}, [experimentData]);
|
|
@@ -4108,7 +4090,6 @@ function EditorShell({ initialExperiment, embeddedMode, proxyBaseUrl }) {
|
|
|
4108
4090
|
const clearAll = useVariationsStore((s) => s.clearAll);
|
|
4109
4091
|
const setSelectedElement = useMutationsStore((s) => s.setSelectedElement);
|
|
4110
4092
|
const selectedElement = useMutationsStore((s) => s.selectedElement);
|
|
4111
|
-
const activeVariationId = useVariationsStore((s) => s.activeVariationId);
|
|
4112
4093
|
const variations = useVariationsStore((s) => s.variations);
|
|
4113
4094
|
const removeLastMutationFromActive = useVariationsStore((s) => s.removeLastMutationFromActive);
|
|
4114
4095
|
const removeMutationsForSelector = useVariationsStore((s) => s.removeMutationsForSelector);
|
|
@@ -4159,7 +4140,6 @@ function EditorShell({ initialExperiment, embeddedMode, proxyBaseUrl }) {
|
|
|
4159
4140
|
}, [embedded, experimentData, toast, buildPlatformVariations]);
|
|
4160
4141
|
const pingIntervalRef = useRef(null);
|
|
4161
4142
|
const pongTimeoutRef = useRef(null);
|
|
4162
|
-
const syncDebounceRef = useRef(null);
|
|
4163
4143
|
const stopHeartbeat = useCallback(() => {
|
|
4164
4144
|
if (pingIntervalRef.current) {
|
|
4165
4145
|
clearInterval(pingIntervalRef.current);
|
|
@@ -4193,15 +4173,24 @@ function EditorShell({ initialExperiment, embeddedMode, proxyBaseUrl }) {
|
|
|
4193
4173
|
},
|
|
4194
4174
|
[stopHeartbeat, clearAll, setSelectedElement]
|
|
4195
4175
|
);
|
|
4176
|
+
useEffect(() => {
|
|
4177
|
+
if (initialExperiment) return;
|
|
4178
|
+
let cancelled = false;
|
|
4179
|
+
void (async () => {
|
|
4180
|
+
await hydrateVariationsFromStorage(void 0);
|
|
4181
|
+
if (cancelled) return;
|
|
4182
|
+
})();
|
|
4183
|
+
return () => {
|
|
4184
|
+
cancelled = true;
|
|
4185
|
+
};
|
|
4186
|
+
}, [initialExperiment]);
|
|
4196
4187
|
useEffect(() => {
|
|
4197
4188
|
if (!initialExperiment) return;
|
|
4198
|
-
const
|
|
4199
|
-
if (
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
handleLoadUrl(initialExperiment.pageUrl, initialExperiment.editorPassword || void 0);
|
|
4204
|
-
}
|
|
4189
|
+
const runKey = JSON.stringify(initialExperiment);
|
|
4190
|
+
if (lastAppliedRunKeyRef.current === runKey) return;
|
|
4191
|
+
const ctx = experimentIframeContextKey(initialExperiment);
|
|
4192
|
+
const sameIframeContext = iframeContextKeyRef.current !== "" && iframeContextKeyRef.current === ctx;
|
|
4193
|
+
let cancelled = false;
|
|
4205
4194
|
const sourceVariations = Array.isArray(initialExperiment.variations) ? initialExperiment.variations : [];
|
|
4206
4195
|
const editorVariations = sourceVariations.map((v, idx) => {
|
|
4207
4196
|
let mutations = [];
|
|
@@ -4221,7 +4210,31 @@ function EditorShell({ initialExperiment, embeddedMode, proxyBaseUrl }) {
|
|
|
4221
4210
|
mutations
|
|
4222
4211
|
};
|
|
4223
4212
|
});
|
|
4224
|
-
|
|
4213
|
+
setExperimentData(initialExperiment);
|
|
4214
|
+
if (sameIframeContext) {
|
|
4215
|
+
void (async () => {
|
|
4216
|
+
await hydrateVariationsFromStorage(initialExperiment.experimentId);
|
|
4217
|
+
if (cancelled) return;
|
|
4218
|
+
loadExperimentVariations(editorVariations);
|
|
4219
|
+
lastAppliedRunKeyRef.current = runKey;
|
|
4220
|
+
})();
|
|
4221
|
+
return () => {
|
|
4222
|
+
cancelled = true;
|
|
4223
|
+
};
|
|
4224
|
+
}
|
|
4225
|
+
if (initialExperiment.pageUrl) {
|
|
4226
|
+
handleLoadUrl(initialExperiment.pageUrl, initialExperiment.editorPassword || void 0);
|
|
4227
|
+
iframeContextKeyRef.current = ctx;
|
|
4228
|
+
}
|
|
4229
|
+
void (async () => {
|
|
4230
|
+
await hydrateVariationsFromStorage(initialExperiment.experimentId);
|
|
4231
|
+
if (cancelled) return;
|
|
4232
|
+
loadExperimentVariations(editorVariations);
|
|
4233
|
+
lastAppliedRunKeyRef.current = runKey;
|
|
4234
|
+
})();
|
|
4235
|
+
return () => {
|
|
4236
|
+
cancelled = true;
|
|
4237
|
+
};
|
|
4225
4238
|
}, [initialExperiment, handleLoadUrl, loadExperimentVariations]);
|
|
4226
4239
|
const handleBridgeReady = useCallback(() => {
|
|
4227
4240
|
setConnectionStatus("connected");
|
|
@@ -4251,33 +4264,44 @@ function EditorShell({ initialExperiment, embeddedMode, proxyBaseUrl }) {
|
|
|
4251
4264
|
switch (msg.type) {
|
|
4252
4265
|
case "load-experiment": {
|
|
4253
4266
|
const data = msg.payload;
|
|
4254
|
-
setExperimentData(data);
|
|
4255
4267
|
experimentDataRef.current = data;
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
try {
|
|
4263
|
-
const chainSets = JSON.parse(v.changesets || "[]");
|
|
4264
|
-
if (Array.isArray(chainSets) && chainSets.length > 0) {
|
|
4265
|
-
mutations = convertChainSetsToMutations(chainSets);
|
|
4266
|
-
}
|
|
4267
|
-
} catch {
|
|
4268
|
+
const editorVariations = Array.isArray(data.variations) ? data.variations.map((v, idx) => {
|
|
4269
|
+
let mutations = [];
|
|
4270
|
+
try {
|
|
4271
|
+
const chainSets = JSON.parse(v.changesets || "[]");
|
|
4272
|
+
if (Array.isArray(chainSets) && chainSets.length > 0) {
|
|
4273
|
+
mutations = convertChainSetsToMutations(chainSets);
|
|
4268
4274
|
}
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4275
|
+
} catch {
|
|
4276
|
+
}
|
|
4277
|
+
return {
|
|
4278
|
+
id: v._id || `v_${idx}`,
|
|
4279
|
+
platformIid: v.iid,
|
|
4280
|
+
name: v.name,
|
|
4281
|
+
isControl: v.baseline || false,
|
|
4282
|
+
traffic_allocation: v.traffic_allocation,
|
|
4283
|
+
mutations
|
|
4284
|
+
};
|
|
4285
|
+
}) : [];
|
|
4286
|
+
setExperimentData(data);
|
|
4287
|
+
const ctx = experimentIframeContextKey(data);
|
|
4288
|
+
let reloadedIframe = false;
|
|
4289
|
+
if (data.pageUrl && !data.skipUrlReload) {
|
|
4290
|
+
if (iframeContextKeyRef.current !== ctx) {
|
|
4291
|
+
handleLoadUrl(data.pageUrl, data.editorPassword || void 0);
|
|
4292
|
+
iframeContextKeyRef.current = ctx;
|
|
4293
|
+
reloadedIframe = true;
|
|
4294
|
+
}
|
|
4279
4295
|
}
|
|
4280
|
-
|
|
4296
|
+
void (async () => {
|
|
4297
|
+
await hydrateVariationsFromStorage(data.experimentId);
|
|
4298
|
+
if (Array.isArray(data.variations)) {
|
|
4299
|
+
loadExperimentVariations(editorVariations);
|
|
4300
|
+
}
|
|
4301
|
+
if (reloadedIframe) {
|
|
4302
|
+
toast(`Loaded experiment: ${data.name || "Untitled"}`, "info");
|
|
4303
|
+
}
|
|
4304
|
+
})();
|
|
4281
4305
|
break;
|
|
4282
4306
|
}
|
|
4283
4307
|
case "request-save": {
|
|
@@ -4351,27 +4375,6 @@ function EditorShell({ initialExperiment, embeddedMode, proxyBaseUrl }) {
|
|
|
4351
4375
|
useEffect(() => {
|
|
4352
4376
|
sendToBridge({ type: "setMode", mode: interactionMode });
|
|
4353
4377
|
}, [interactionMode]);
|
|
4354
|
-
useEffect(() => {
|
|
4355
|
-
if (syncDebounceRef.current) {
|
|
4356
|
-
clearTimeout(syncDebounceRef.current);
|
|
4357
|
-
syncDebounceRef.current = null;
|
|
4358
|
-
}
|
|
4359
|
-
if (connectionStatus !== "connected") return;
|
|
4360
|
-
syncDebounceRef.current = setTimeout(() => {
|
|
4361
|
-
const currentActiveMutations = useVariationsStore.getState().getActiveMutations();
|
|
4362
|
-
sendToBridge({ type: "clearAllMutations" });
|
|
4363
|
-
if (currentActiveMutations.length > 0) {
|
|
4364
|
-
sendToBridge({ type: "applyMutationBatch", mutations: currentActiveMutations });
|
|
4365
|
-
}
|
|
4366
|
-
syncDebounceRef.current = null;
|
|
4367
|
-
}, 350);
|
|
4368
|
-
return () => {
|
|
4369
|
-
if (syncDebounceRef.current) {
|
|
4370
|
-
clearTimeout(syncDebounceRef.current);
|
|
4371
|
-
syncDebounceRef.current = null;
|
|
4372
|
-
}
|
|
4373
|
-
};
|
|
4374
|
-
}, [connectionStatus, activeVariationId, experimentData?.experimentId]);
|
|
4375
4378
|
useEffect(() => {
|
|
4376
4379
|
const handler = (e) => {
|
|
4377
4380
|
const meta = e.metaKey || e.ctrlKey;
|
|
@@ -4574,7 +4577,7 @@ function PlatformVisualEditor({
|
|
|
4574
4577
|
renderLoading,
|
|
4575
4578
|
renderError
|
|
4576
4579
|
}) {
|
|
4577
|
-
console.log(experiment);
|
|
4580
|
+
console.log("experiment", experiment);
|
|
4578
4581
|
const [editorReady, setEditorReady] = useState(false);
|
|
4579
4582
|
const [dirty, setDirty] = useState(false);
|
|
4580
4583
|
const dirtyRef = useRef(false);
|
|
@@ -4788,7 +4791,6 @@ function PlatformVisualEditorV2({
|
|
|
4788
4791
|
renderLoading,
|
|
4789
4792
|
renderError
|
|
4790
4793
|
}) {
|
|
4791
|
-
console.log(experiment);
|
|
4792
4794
|
const iframeRef = useRef(null);
|
|
4793
4795
|
const [editorReady, setEditorReady] = useState(false);
|
|
4794
4796
|
const [dirty, setDirty] = useState(false);
|
|
@@ -4978,4 +4980,4 @@ function PlatformVisualEditorV2({
|
|
|
4978
4980
|
] });
|
|
4979
4981
|
}
|
|
4980
4982
|
|
|
4981
|
-
export { EditorShell, PlatformVisualEditor, PlatformVisualEditorV2, ToastProvider, useToast };
|
|
4983
|
+
export { EditorShell, PlatformVisualEditor, PlatformVisualEditorV2, ToastProvider, hydrateVariationsFromStorage, useToast, variationsPersistStorageName };
|