@powerhousedao/connect 6.1.0 → 6.2.0-dev.1
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/.env +11 -49
- package/README.md +4 -0
- package/dist/{AddDriveModal-Cg2qcYp6.js → AddDriveModal-kzL41qdg.js} +17 -7
- package/dist/AddDriveModal-kzL41qdg.js.map +1 -0
- package/dist/{ClearStorageModal-D2K3LkHV.js → ClearStorageModal-DIKwqiia.js} +7 -7
- package/dist/{ClearStorageModal-D2K3LkHV.js.map → ClearStorageModal-DIKwqiia.js.map} +1 -1
- package/dist/{CookiesPolicyModal-4QzcllDR.js → CookiesPolicyModal-1nOh6oN3.js} +5 -5
- package/dist/{CookiesPolicyModal-4QzcllDR.js.map → CookiesPolicyModal-1nOh6oN3.js.map} +1 -1
- package/dist/{CreateDocumentModal-7vgZ8nbM.js → CreateDocumentModal-nQbLVDq3.js} +4 -4
- package/dist/{CreateDocumentModal-7vgZ8nbM.js.map → CreateDocumentModal-nQbLVDq3.js.map} +1 -1
- package/dist/{DebugSettingsModal-B1FRL4rp.js → DebugSettingsModal-CJunSkZB.js} +8 -8
- package/dist/{DebugSettingsModal-B1FRL4rp.js.map → DebugSettingsModal-CJunSkZB.js.map} +1 -1
- package/dist/{DeleteDriveModal-ABue_rOY.js → DeleteDriveModal-6P0T1QgD.js} +6 -6
- package/dist/{DeleteDriveModal-ABue_rOY.js.map → DeleteDriveModal-6P0T1QgD.js.map} +1 -1
- package/dist/{DeleteItemModal-B6aHnYPD.js → DeleteItemModal-BmV1aQyZ.js} +7 -7
- package/dist/{DeleteItemModal-B6aHnYPD.js.map → DeleteItemModal-BmV1aQyZ.js.map} +1 -1
- package/dist/{DisclaimerModal-CfDeiBz8.js → DisclaimerModal-CVTFfN0_.js} +6 -6
- package/dist/{DisclaimerModal-CfDeiBz8.js.map → DisclaimerModal-CVTFfN0_.js.map} +1 -1
- package/dist/{DownloadDocumentWithErrorsModal-CaAPGg04.js → DownloadDocumentWithErrorsModal-CXeFUtaz.js} +5 -5
- package/dist/{DownloadDocumentWithErrorsModal-CaAPGg04.js.map → DownloadDocumentWithErrorsModal-CXeFUtaz.js.map} +1 -1
- package/dist/{DriveSettingsModal-CNtCO4-y.js → DriveSettingsModal-L5sM-rIh.js} +5 -5
- package/dist/{DriveSettingsModal-CNtCO4-y.js.map → DriveSettingsModal-L5sM-rIh.js.map} +1 -1
- package/dist/{InspectorModal-ywEL5GKA.js → InspectorModal-C7pIf6OO.js} +8 -8
- package/dist/{InspectorModal-ywEL5GKA.js.map → InspectorModal-C7pIf6OO.js.map} +1 -1
- package/dist/{MissingPackageModal-CUdcDjSO.js → MissingPackageModal-D_N96M6a.js} +5 -5
- package/dist/{MissingPackageModal-CUdcDjSO.js.map → MissingPackageModal-D_N96M6a.js.map} +1 -1
- package/dist/{SettingsModal-5dMD1lq9.js → SettingsModal-Dz3u5m_G.js} +11 -11
- package/dist/{SettingsModal-5dMD1lq9.js.map → SettingsModal-Dz3u5m_G.js.map} +1 -1
- package/dist/{UpgradeDriveModal-BwI5E5k3.js → UpgradeDriveModal-CyAUDCU7.js} +6 -6
- package/dist/{UpgradeDriveModal-BwI5E5k3.js.map → UpgradeDriveModal-CyAUDCU7.js.map} +1 -1
- package/dist/app-loader-C2ua5JsF.js +4 -0
- package/dist/{sidebar-CO3JjI3G.js → app-loader-CCPp_d0K.js} +894 -854
- package/dist/app-loader-CCPp_d0K.js.map +1 -0
- package/dist/{build-info-B3CWTvMu.js → build-info-DaWwuuKb.js} +4 -4
- package/dist/{build-info-B3CWTvMu.js.map → build-info-DaWwuuKb.js.map} +1 -1
- package/dist/components-CILkbSlg.js +5 -0
- package/dist/connect.config-aPx3rHZQ.js +161 -0
- package/dist/connect.config-aPx3rHZQ.js.map +1 -0
- package/dist/{load-D-7n7rPU.js → load-DalJBk1C.js} +13 -25
- package/dist/load-DalJBk1C.js.map +1 -0
- package/dist/main.js +5 -3
- package/dist/main.js.map +1 -1
- package/dist/{package-Dk3L0oWJ.js → package-BPfA1bwo.js} +6 -5
- package/dist/package-BPfA1bwo.js.map +1 -0
- package/dist/{pglite-runtime-Btp8ZXVH.js → pglite-runtime-toc1-TWa.js} +12 -6
- package/dist/pglite-runtime-toc1-TWa.js.map +1 -0
- package/dist/{pglite-seed-b1mJUaip.js → pglite-seed-D8oSKkep.js} +7 -7
- package/dist/pglite-seed-D8oSKkep.js.map +1 -0
- package/dist/pglite.worker.js +5 -5
- package/dist/pglite.worker.js.map +1 -1
- package/dist/pglite.worker.legacy.js +4 -4
- package/dist/pglite.worker.legacy.js.map +1 -1
- package/dist/{reactor-BixVsAMj.js → reactor-BlxTyHZ5.js} +92 -45
- package/dist/reactor-BlxTyHZ5.js.map +1 -0
- package/dist/{registerServiceWorker-BkUnxLqB.js → registerServiceWorker-B7uKpkci.js} +4 -4
- package/dist/{registerServiceWorker-BkUnxLqB.js.map → registerServiceWorker-B7uKpkci.js.map} +1 -1
- package/dist/{i18n-BuVT7wFQ.js → reload-connect-toast-CwvSc6F-.js} +19 -5
- package/dist/reload-connect-toast-CwvSc6F-.js.map +1 -0
- package/dist/runtime-config-DL3QjcfM.js +24 -0
- package/dist/runtime-config-DL3QjcfM.js.map +1 -0
- package/dist/start-connect.d.ts.map +1 -1
- package/dist/start-connect.js +5 -3
- package/dist/start-connect.js.map +1 -1
- package/dist/style.css +7 -0
- package/dist/{toast-DnODOv28.js → toast-BEy42aYx.js} +3 -3
- package/dist/{toast-DnODOv28.js.map → toast-BEy42aYx.js.map} +1 -1
- package/dist/{useRegistryPackages-PUiMxd73.js → useRegistryPackages-Bh0luCvV.js} +6 -6
- package/dist/useRegistryPackages-Bh0luCvV.js.map +1 -0
- package/package.json +11 -10
- package/public/powerhouse.config.json +33 -0
- package/dist/AddDriveModal-Cg2qcYp6.js.map +0 -1
- package/dist/connect.config-B2xP5Iez.js +0 -148
- package/dist/connect.config-B2xP5Iez.js.map +0 -1
- package/dist/i18n-BuVT7wFQ.js.map +0 -1
- package/dist/load-D-7n7rPU.js.map +0 -1
- package/dist/package-Dk3L0oWJ.js.map +0 -1
- package/dist/packages.config-CiH8KJVN.js +0 -32
- package/dist/packages.config-CiH8KJVN.js.map +0 -1
- package/dist/pglite-runtime-Btp8ZXVH.js.map +0 -1
- package/dist/pglite-seed-b1mJUaip.js.map +0 -1
- package/dist/reactor-BixVsAMj.js.map +0 -1
- package/dist/sidebar-CO3JjI3G.js.map +0 -1
- package/dist/useRegistryPackages-PUiMxd73.js.map +0 -1
- package/public/ph-packages.json +0 -4
|
@@ -1,129 +1,434 @@
|
|
|
1
1
|
|
|
2
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
3
|
-
import { n as
|
|
4
|
-
import { n as
|
|
5
|
-
import {
|
|
6
|
-
import { c as subscribeReactorPgMajor, d as createFileDataStore, f as idbError, l as IDB_STORE_NAME, m as readPgVersionFile, p as openIdb, r as getCachedReactorPgMajor, u as PRIMARY_IDB_NAMES } from "./pglite-runtime-
|
|
7
|
-
import { t as serviceWorkerManager } from "./registerServiceWorker-
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
2
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="ce32a32d-843f-5968-a8c8-3c3a319dc387")}catch(e){}}();
|
|
3
|
+
import { n as getRuntimeConfig } from "./runtime-config-DL3QjcfM.js";
|
|
4
|
+
import { n as i18n } from "./reload-connect-toast-CwvSc6F-.js";
|
|
5
|
+
import { a as defaultPHDocumentEditorConfig, i as defaultPHAppConfig, r as connectConfig } from "./connect.config-aPx3rHZQ.js";
|
|
6
|
+
import { c as subscribeReactorPgMajor, d as createFileDataStore, f as idbError, l as IDB_STORE_NAME, m as readPgVersionFile, p as openIdb, r as getCachedReactorPgMajor, u as PRIMARY_IDB_NAMES } from "./pglite-runtime-toc1-TWa.js";
|
|
7
|
+
import { t as serviceWorkerManager } from "./registerServiceWorker-B7uKpkci.js";
|
|
8
|
+
import { n as toast, t as ToastContainer } from "./toast-BEy42aYx.js";
|
|
9
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
10
|
+
import { PGlite } from "@electric-sql/pglite";
|
|
11
|
+
import { Trans, useTranslation } from "react-i18next";
|
|
10
12
|
import React, { StrictMode, Suspense, lazy, useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
13
|
+
import { driveCollectionId, getRevisionFromDate, initTheme, logout, openRenown, setPHAppConfig, setPHDocumentEditorConfig, setRevisionHistoryVisible, setSelectedDrive, setSelectedNode, showPHModal, useAppModuleById, useDefaultAppModule, useDocumentById, useDocumentModelModuleById, useDocumentOperations, useDrives, useEditorModuleById, useFallbackEditorModule, useIsAddDriveEnabled, useNodeParentFolderById, usePHModal, usePackageDiscoveryService, useReactorClientModule, useRevisionHistoryVisible, useSelectedDocument, useSelectedDocumentId, useSelectedDriveSafe, useSelectedFolder, useSelectedTimelineItem, useSyncList, useTheme, useUser, useVetraPackageManager, useVetraPackages } from "@powerhousedao/reactor-browser";
|
|
14
|
+
import { childLogger } from "document-model";
|
|
15
|
+
import { ConnectSidebar, ConnectTooltipProvider, CookieBanner, DefaultEditorLoader, DropZoneWrapper, HomeScreen, HomeScreenAddDriveItem, HomeScreenItem, LogoAnimation, PackageInstallModal, RevisionHistory, SidebarAddDriveItem, SidebarItem, useEns } from "@powerhousedao/design-system/connect";
|
|
11
16
|
import { z } from "zod";
|
|
12
|
-
import { Trans, useTranslation } from "react-i18next";
|
|
13
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
14
17
|
import { Outlet, RouterProvider, createBrowserRouter, createRoutesFromChildren, matchRoutes, useLocation, useNavigationType } from "react-router-dom";
|
|
15
18
|
import { useHotkeys } from "react-hotkeys-hook";
|
|
16
|
-
import { ConnectSidebar, ConnectTooltipProvider, CookieBanner, DefaultEditorLoader, DropZoneWrapper, HomeScreen, HomeScreenAddDriveItem, HomeScreenItem, LogoAnimation, PackageInstallModal, RevisionHistory, SidebarAddDriveItem, SidebarItem, useEns } from "@powerhousedao/design-system/connect";
|
|
17
|
-
import { GenericDriveExplorer } from "@powerhousedao/powerhouse-vetra-packages/editors";
|
|
18
19
|
import { Icon, Modal, PowerhouseButton } from "@powerhousedao/design-system";
|
|
19
20
|
import { ErrorBoundary } from "react-error-boundary";
|
|
20
|
-
import {
|
|
21
|
+
import { GenericDriveExplorer } from "@powerhousedao/powerhouse-vetra-packages/editors";
|
|
21
22
|
import { twMerge } from "tailwind-merge";
|
|
22
23
|
import { redo, undo } from "@powerhousedao/shared/document-model";
|
|
23
|
-
//#region src/
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
function getInitial$1() {
|
|
28
|
-
try {
|
|
29
|
-
const value = localStorage.getItem(ACCEPTED_COOKIES_KEY_STORAGE);
|
|
30
|
-
return value ? JSON.parse(value) : {
|
|
31
|
-
analytics: false,
|
|
32
|
-
marketing: false,
|
|
33
|
-
functional: false
|
|
34
|
-
};
|
|
35
|
-
} catch {
|
|
36
|
-
return {
|
|
37
|
-
analytics: false,
|
|
38
|
-
marketing: false,
|
|
39
|
-
functional: false
|
|
40
|
-
};
|
|
41
|
-
}
|
|
24
|
+
//#region src/utils/browser.ts
|
|
25
|
+
function getBasePath() {
|
|
26
|
+
if (typeof document === "undefined") return "/";
|
|
27
|
+
return document.querySelector("base")?.getAttribute("href") || "/";
|
|
42
28
|
}
|
|
43
|
-
|
|
44
|
-
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region src/utils/document-editor-debug-tools.ts
|
|
31
|
+
var DocumentEditorDebugTools = class {
|
|
32
|
+
document;
|
|
33
|
+
operations = [];
|
|
34
|
+
constructor(document) {
|
|
35
|
+
if (document) this.document = document;
|
|
36
|
+
}
|
|
37
|
+
operationsToTableObject(operations) {
|
|
38
|
+
return operations.map((op) => ({
|
|
39
|
+
...op,
|
|
40
|
+
input: JSON.stringify(op.action.input)
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
setDocument(document) {
|
|
44
|
+
this.document = document;
|
|
45
|
+
}
|
|
46
|
+
getDocument() {
|
|
47
|
+
return this.document;
|
|
48
|
+
}
|
|
49
|
+
getOperations() {
|
|
50
|
+
return this.operations;
|
|
51
|
+
}
|
|
52
|
+
pushOperation(operation) {
|
|
53
|
+
this.operations.push(operation);
|
|
54
|
+
}
|
|
55
|
+
operationsTable() {
|
|
56
|
+
if (!this.document) console.warn("No document");
|
|
57
|
+
const ops = Object.values(this.document?.operations || {}).filter((array) => array !== void 0).flatMap((array) => array).sort((a, b) => a.index - b.index);
|
|
58
|
+
console.table(this.operationsToTableObject(ops));
|
|
59
|
+
}
|
|
60
|
+
scopeOperationsTable(scope) {
|
|
61
|
+
if (!this.document) console.warn("No document");
|
|
62
|
+
const ops = this.document?.operations[scope] || [];
|
|
63
|
+
console.table(this.operationsToTableObject(ops));
|
|
64
|
+
}
|
|
65
|
+
operationsLog() {
|
|
66
|
+
console.log(this.operations);
|
|
67
|
+
}
|
|
68
|
+
operationsLogTable() {
|
|
69
|
+
console.table(this.operationsToTableObject(this.operations));
|
|
70
|
+
}
|
|
71
|
+
clear() {
|
|
72
|
+
this.operations = [];
|
|
73
|
+
this.document = void 0;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
//#endregion
|
|
77
|
+
//#region src/components/migration-status.ts
|
|
78
|
+
let current = null;
|
|
79
|
+
const listeners$3 = /* @__PURE__ */ new Set();
|
|
80
|
+
function setMigrationStatus(status) {
|
|
81
|
+
current = status;
|
|
82
|
+
for (const l of listeners$3) l();
|
|
45
83
|
}
|
|
46
|
-
function
|
|
47
|
-
|
|
48
|
-
localStorage.setItem(ACCEPTED_COOKIES_KEY_STORAGE, JSON.stringify(cookies));
|
|
49
|
-
listeners$3.forEach((fn) => fn());
|
|
84
|
+
function getMigrationStatus() {
|
|
85
|
+
return current;
|
|
50
86
|
}
|
|
51
|
-
function
|
|
52
|
-
listeners$3.add(
|
|
53
|
-
return () =>
|
|
87
|
+
function subscribeMigrationStatus(cb) {
|
|
88
|
+
listeners$3.add(cb);
|
|
89
|
+
return () => {
|
|
90
|
+
listeners$3.delete(cb);
|
|
91
|
+
};
|
|
54
92
|
}
|
|
55
|
-
const useAcceptedCookies = () => {
|
|
56
|
-
return [useSyncExternalStore(subscribe$1, getCookies, getCookies), setCookies];
|
|
57
|
-
};
|
|
58
93
|
//#endregion
|
|
59
|
-
//#region src/
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
94
|
+
//#region src/utils/pglite-migration.ts
|
|
95
|
+
const BACKUP_PREFIX = "ph-pglite-backup::";
|
|
96
|
+
const BACKUP_INDEX_KEY = "ph:pglite-backups";
|
|
97
|
+
const BACKUP_RETENTION_MS = 720 * 60 * 60 * 1e3;
|
|
98
|
+
const LEGACY_DUMPERS = { 16: async () => {
|
|
99
|
+
const [{ PGlite: LegacyPGlite }, { pgDump }] = await Promise.all([import("pglite-legacy-02"), import("pglite-tools-legacy-02/pg_dump")]);
|
|
100
|
+
return async (idbName) => {
|
|
101
|
+
const pg = new LegacyPGlite(`idb://${idbName.replace(/^\/pglite\//, "")}`);
|
|
102
|
+
try {
|
|
103
|
+
await pg.waitReady;
|
|
104
|
+
return await (await pgDump({ pg })).text();
|
|
105
|
+
} finally {
|
|
106
|
+
await pg.close();
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
} };
|
|
110
|
+
function deleteIdb(name, timeoutMs = 15e3) {
|
|
111
|
+
return new Promise((resolve, reject) => {
|
|
112
|
+
const req = indexedDB.deleteDatabase(name);
|
|
113
|
+
let settled = false;
|
|
114
|
+
const done = (fn) => {
|
|
115
|
+
if (settled) return;
|
|
116
|
+
settled = true;
|
|
117
|
+
fn();
|
|
118
|
+
};
|
|
119
|
+
const timer = setTimeout(() => done(() => reject(/* @__PURE__ */ new Error(`IDB delete timed out for ${name} after ${timeoutMs}ms`))), timeoutMs);
|
|
120
|
+
req.onsuccess = () => done(() => {
|
|
121
|
+
clearTimeout(timer);
|
|
122
|
+
resolve();
|
|
123
|
+
});
|
|
124
|
+
req.onerror = () => done(() => {
|
|
125
|
+
clearTimeout(timer);
|
|
126
|
+
reject(idbError(req, `IDB delete failed for ${name}`));
|
|
127
|
+
});
|
|
128
|
+
});
|
|
64
129
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
*
|
|
68
|
-
* The outer try/catch handles synchronous throws from `client.track`; the
|
|
69
|
-
* inner `.catch()` handles rejected promises. Both are needed because
|
|
70
|
-
* `Promise.resolve(x).catch()` cannot catch a throw from evaluating `x`.
|
|
71
|
-
*/
|
|
72
|
-
function forward(client, name, props) {
|
|
130
|
+
async function clearFileData(name) {
|
|
131
|
+
const db = await openIdb(name, 21, createFileDataStore);
|
|
73
132
|
try {
|
|
74
|
-
Promise
|
|
75
|
-
|
|
76
|
-
|
|
133
|
+
await new Promise((resolve, reject) => {
|
|
134
|
+
const tx = db.transaction(IDB_STORE_NAME, "readwrite");
|
|
135
|
+
tx.oncomplete = () => resolve();
|
|
136
|
+
tx.onerror = () => reject(idbError(tx, `FILE_DATA clear failed for ${name}`));
|
|
137
|
+
tx.onabort = () => reject(idbError(tx, `FILE_DATA clear aborted for ${name}`));
|
|
138
|
+
tx.objectStore(IDB_STORE_NAME).clear();
|
|
139
|
+
});
|
|
140
|
+
} finally {
|
|
141
|
+
db.close();
|
|
77
142
|
}
|
|
78
143
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
144
|
+
async function cloneFileData(sourceName, destName) {
|
|
145
|
+
const source = await openIdb(sourceName, 21, createFileDataStore);
|
|
146
|
+
try {
|
|
147
|
+
const dest = await openIdb(destName, 21, createFileDataStore);
|
|
148
|
+
try {
|
|
149
|
+
const readStore = source.transaction(IDB_STORE_NAME, "readonly").objectStore(IDB_STORE_NAME);
|
|
150
|
+
const entries = await new Promise((resolve, reject) => {
|
|
151
|
+
const out = [];
|
|
152
|
+
const req = readStore.openCursor();
|
|
153
|
+
req.onerror = () => reject(idbError(req, `FILE_DATA read cursor failed for ${sourceName}`));
|
|
154
|
+
req.onsuccess = () => {
|
|
155
|
+
const cursor = req.result;
|
|
156
|
+
if (!cursor) return resolve(out);
|
|
157
|
+
out.push({
|
|
158
|
+
key: cursor.key,
|
|
159
|
+
value: cursor.value
|
|
160
|
+
});
|
|
161
|
+
cursor.continue();
|
|
162
|
+
};
|
|
163
|
+
});
|
|
164
|
+
await new Promise((resolve, reject) => {
|
|
165
|
+
const writeTx = dest.transaction(IDB_STORE_NAME, "readwrite");
|
|
166
|
+
writeTx.oncomplete = () => resolve();
|
|
167
|
+
writeTx.onerror = () => reject(idbError(writeTx, `FILE_DATA write failed for ${destName}`));
|
|
168
|
+
writeTx.onabort = () => reject(idbError(writeTx, `FILE_DATA write aborted for ${destName}`));
|
|
169
|
+
const writeStore = writeTx.objectStore(IDB_STORE_NAME);
|
|
170
|
+
writeStore.clear();
|
|
171
|
+
for (const { key, value } of entries) writeStore.put(value, key);
|
|
172
|
+
});
|
|
173
|
+
} finally {
|
|
174
|
+
dest.close();
|
|
175
|
+
}
|
|
176
|
+
} finally {
|
|
177
|
+
source.close();
|
|
178
|
+
}
|
|
87
179
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
180
|
+
function readBackupIndex() {
|
|
181
|
+
try {
|
|
182
|
+
const raw = localStorage.getItem(BACKUP_INDEX_KEY);
|
|
183
|
+
if (!raw) return [];
|
|
184
|
+
const parsed = JSON.parse(raw);
|
|
185
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
186
|
+
} catch {
|
|
187
|
+
return [];
|
|
188
|
+
}
|
|
95
189
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
* Returns the cached `OpenPanel` singleton, building it on first call.
|
|
101
|
-
*
|
|
102
|
-
* When `config.clientId` is empty it returns `undefined` without importing
|
|
103
|
-
* `@openpanel/web`, so the bundler can tree-shake the SDK out of production.
|
|
104
|
-
*/
|
|
105
|
-
async function getOpenPanelClient(config) {
|
|
106
|
-
if (!config.clientId) return;
|
|
107
|
-
if (client) return client;
|
|
108
|
-
const { OpenPanel: OpenPanelClass } = await import("@openpanel/web");
|
|
109
|
-
client = new OpenPanelClass({
|
|
110
|
-
clientId: config.clientId,
|
|
111
|
-
...config.apiUrl ? { apiUrl: config.apiUrl } : {}
|
|
112
|
-
});
|
|
113
|
-
drainOpenPanelBuffer(client);
|
|
114
|
-
return client;
|
|
190
|
+
function writeBackupIndex(records) {
|
|
191
|
+
try {
|
|
192
|
+
localStorage.setItem(BACKUP_INDEX_KEY, JSON.stringify(records));
|
|
193
|
+
} catch {}
|
|
115
194
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
* revocation or during tests; buffered events are discarded, not replayed.
|
|
119
|
-
*/
|
|
120
|
-
function resetOpenPanelClient() {
|
|
121
|
-
client = void 0;
|
|
122
|
-
clearOpenPanelBuffer();
|
|
195
|
+
function registerBackup(rec) {
|
|
196
|
+
writeBackupIndex([...readBackupIndex(), rec]);
|
|
123
197
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
198
|
+
function forgetBackup(name) {
|
|
199
|
+
writeBackupIndex(readBackupIndex().filter((r) => r.name !== name));
|
|
200
|
+
}
|
|
201
|
+
async function janitor() {
|
|
202
|
+
const cutoff = Date.now() - BACKUP_RETENTION_MS;
|
|
203
|
+
const keep = [];
|
|
204
|
+
for (const rec of readBackupIndex()) {
|
|
205
|
+
if (rec.createdAt >= cutoff) {
|
|
206
|
+
keep.push(rec);
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
try {
|
|
210
|
+
await deleteIdb(rec.name);
|
|
211
|
+
} catch (err) {
|
|
212
|
+
console.warn(`Failed to delete expired pglite backup ${rec.name}`, err);
|
|
213
|
+
keep.push(rec);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
writeBackupIndex(keep);
|
|
217
|
+
}
|
|
218
|
+
function publishPhase(idbName, phase) {
|
|
219
|
+
setMigrationStatus({
|
|
220
|
+
idbName,
|
|
221
|
+
phase
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
async function migrateIdbIfNeeded(idbName) {
|
|
225
|
+
const major = await readPgVersionFile(idbName);
|
|
226
|
+
if (major === null || major === 17) return;
|
|
227
|
+
const loader = LEGACY_DUMPERS[major];
|
|
228
|
+
if (!loader) throw new Error(`Unsupported legacy PGlite data dir: PG_VERSION=${major} for ${idbName}`);
|
|
229
|
+
const backupName = `${BACKUP_PREFIX}${idbName}::${(/* @__PURE__ */ new Date()).toISOString()}`;
|
|
230
|
+
const backupRec = {
|
|
231
|
+
name: backupName,
|
|
232
|
+
idbName,
|
|
233
|
+
createdAt: Date.now()
|
|
234
|
+
};
|
|
235
|
+
publishPhase(idbName, "clone");
|
|
236
|
+
await cloneFileData(idbName, backupName);
|
|
237
|
+
registerBackup(backupRec);
|
|
238
|
+
let sql;
|
|
239
|
+
try {
|
|
240
|
+
publishPhase(idbName, "dump");
|
|
241
|
+
sql = await (await loader())(idbName);
|
|
242
|
+
} catch (err) {
|
|
243
|
+
await deleteIdb(backupName).catch(() => {});
|
|
244
|
+
forgetBackup(backupName);
|
|
245
|
+
throw err;
|
|
246
|
+
}
|
|
247
|
+
try {
|
|
248
|
+
await clearFileData(idbName);
|
|
249
|
+
publishPhase(idbName, "restore");
|
|
250
|
+
const pg = new PGlite(`idb://${idbName.replace(/^\/pglite\//, "")}`, { relaxedDurability: false });
|
|
251
|
+
try {
|
|
252
|
+
await pg.waitReady;
|
|
253
|
+
console.info(`[pglite-migration] Dump preamble (first 500 chars):\n${sql.slice(0, 500)}`);
|
|
254
|
+
try {
|
|
255
|
+
await pg.exec("SET standard_conforming_strings = off;");
|
|
256
|
+
} catch (gucErr) {
|
|
257
|
+
console.warn("[pglite-migration] Could not force standard_conforming_strings=off", gucErr);
|
|
258
|
+
}
|
|
259
|
+
try {
|
|
260
|
+
await pg.exec(sql);
|
|
261
|
+
} catch (execErr) {
|
|
262
|
+
logRestoreFailure(idbName, sql, execErr);
|
|
263
|
+
throw execErr;
|
|
264
|
+
}
|
|
265
|
+
} finally {
|
|
266
|
+
await pg.close();
|
|
267
|
+
}
|
|
268
|
+
} catch (err) {
|
|
269
|
+
await rollbackFromBackup(idbName, backupName, err);
|
|
270
|
+
throw err;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
function logRestoreFailure(idbName, sql, err) {
|
|
274
|
+
const errObj = err;
|
|
275
|
+
const position = typeof errObj.position === "string" ? parseInt(errObj.position, 10) : typeof errObj.position === "number" ? errObj.position : NaN;
|
|
276
|
+
console.error(`[pglite-migration] Restore failed for ${idbName}`, {
|
|
277
|
+
message: errObj.message,
|
|
278
|
+
code: errObj.code,
|
|
279
|
+
severity: errObj.severity,
|
|
280
|
+
detail: errObj.detail,
|
|
281
|
+
where: errObj.where,
|
|
282
|
+
position: errObj.position,
|
|
283
|
+
sqlLength: sql.length
|
|
284
|
+
});
|
|
285
|
+
if (Number.isFinite(position) && position > 0) {
|
|
286
|
+
const zeroBased = position - 1;
|
|
287
|
+
const start = Math.max(0, zeroBased - 200);
|
|
288
|
+
const end = Math.min(sql.length, zeroBased + 200);
|
|
289
|
+
const before = sql.slice(start, zeroBased);
|
|
290
|
+
const at = sql.slice(zeroBased, zeroBased + 1);
|
|
291
|
+
const after = sql.slice(zeroBased + 1, end);
|
|
292
|
+
console.error(`[pglite-migration] SQL context around position ${position}:\n${before}»${at}«${after}`);
|
|
293
|
+
const lineStart = sql.lastIndexOf("\n", zeroBased) + 1;
|
|
294
|
+
const lineEnd = sql.indexOf("\n", zeroBased);
|
|
295
|
+
const line = sql.slice(lineStart, lineEnd === -1 ? sql.length : lineEnd);
|
|
296
|
+
console.error(`[pglite-migration] Failing line (truncated to 500 chars):\n${line.slice(0, 500)}`);
|
|
297
|
+
} else console.error(`[pglite-migration] No position info. First 2000 chars of dump:\n${sql.slice(0, 2e3)}`);
|
|
298
|
+
}
|
|
299
|
+
async function rollbackFromBackup(idbName, backupName, originalError) {
|
|
300
|
+
try {
|
|
301
|
+
await clearFileData(idbName).catch(() => {});
|
|
302
|
+
await cloneFileData(backupName, idbName);
|
|
303
|
+
} catch (rollbackErr) {
|
|
304
|
+
console.error(`PGlite migration failed AND rollback failed for ${idbName}. Primary error:`, originalError, "Rollback error:", rollbackErr);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
await deleteIdb(backupName).catch(() => {});
|
|
308
|
+
forgetBackup(backupName);
|
|
309
|
+
}
|
|
310
|
+
async function migrateAllLocked() {
|
|
311
|
+
for (const idbName of PRIMARY_IDB_NAMES) await migrateIdbIfNeeded(idbName);
|
|
312
|
+
setMigrationStatus(null);
|
|
313
|
+
}
|
|
314
|
+
async function migrateAllIfNeeded() {
|
|
315
|
+
janitor().catch((err) => console.warn("PGlite backup janitor failed", err));
|
|
316
|
+
if (!(typeof navigator !== "undefined" && "locks" in navigator && typeof navigator.locks.request === "function")) {
|
|
317
|
+
await migrateAllLocked();
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
await navigator.locks.request("ph-pglite-migration", { mode: "exclusive" }, async () => {
|
|
321
|
+
await migrateAllLocked();
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
//#endregion
|
|
325
|
+
//#region src/hooks/useAcceptedCookies.ts
|
|
326
|
+
const ACCEPTED_COOKIES_KEY_STORAGE = `${connectConfig.routerBasename}:acceptedCookies`;
|
|
327
|
+
const listeners$2 = /* @__PURE__ */ new Set();
|
|
328
|
+
let cookies = getInitial$1();
|
|
329
|
+
function getInitial$1() {
|
|
330
|
+
try {
|
|
331
|
+
const value = localStorage.getItem(ACCEPTED_COOKIES_KEY_STORAGE);
|
|
332
|
+
return value ? JSON.parse(value) : {
|
|
333
|
+
analytics: false,
|
|
334
|
+
marketing: false,
|
|
335
|
+
functional: false
|
|
336
|
+
};
|
|
337
|
+
} catch {
|
|
338
|
+
return {
|
|
339
|
+
analytics: false,
|
|
340
|
+
marketing: false,
|
|
341
|
+
functional: false
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
function getCookies() {
|
|
346
|
+
return cookies;
|
|
347
|
+
}
|
|
348
|
+
function setCookies(setter) {
|
|
349
|
+
cookies = setter(cookies);
|
|
350
|
+
localStorage.setItem(ACCEPTED_COOKIES_KEY_STORAGE, JSON.stringify(cookies));
|
|
351
|
+
listeners$2.forEach((fn) => fn());
|
|
352
|
+
}
|
|
353
|
+
function subscribe$1(fn) {
|
|
354
|
+
listeners$2.add(fn);
|
|
355
|
+
return () => listeners$2.delete(fn);
|
|
356
|
+
}
|
|
357
|
+
const useAcceptedCookies = () => {
|
|
358
|
+
return [useSyncExternalStore(subscribe$1, getCookies, getCookies), setCookies];
|
|
359
|
+
};
|
|
360
|
+
//#endregion
|
|
361
|
+
//#region src/services/openpanel/buffer.ts
|
|
362
|
+
const buffer = [];
|
|
363
|
+
let errorHandler;
|
|
364
|
+
function defaultOnError(err) {
|
|
365
|
+
console.warn("[useOpenPanelAnalytics] track failed:", err);
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Forward a single event to the active client, swallowing any throw.
|
|
369
|
+
*
|
|
370
|
+
* The outer try/catch handles synchronous throws from `client.track`; the
|
|
371
|
+
* inner `.catch()` handles rejected promises. Both are needed because
|
|
372
|
+
* `Promise.resolve(x).catch()` cannot catch a throw from evaluating `x`.
|
|
373
|
+
*/
|
|
374
|
+
function forward(client, name, props) {
|
|
375
|
+
try {
|
|
376
|
+
Promise.resolve(client.track(name, props)).catch((err) => (errorHandler ?? defaultOnError)(err));
|
|
377
|
+
} catch (err) {
|
|
378
|
+
(errorHandler ?? defaultOnError)(err);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Drain the pre-init buffer in FIFO order through `client.track`, then forward
|
|
383
|
+
* subsequent events directly. Called once from `getOpenPanelClient()`.
|
|
384
|
+
*/
|
|
385
|
+
function drainOpenPanelBuffer(client, onError) {
|
|
386
|
+
errorHandler = onError;
|
|
387
|
+
const pending = buffer.splice(0, buffer.length);
|
|
388
|
+
for (const [name, props] of pending) forward(client, name, props);
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Clear the buffer and reset the active client. Buffered pre-consent events
|
|
392
|
+
* are discarded so they are not replayed if the user later re-accepts.
|
|
393
|
+
*/
|
|
394
|
+
function clearOpenPanelBuffer() {
|
|
395
|
+
buffer.length = 0;
|
|
396
|
+
errorHandler = void 0;
|
|
397
|
+
}
|
|
398
|
+
//#endregion
|
|
399
|
+
//#region src/services/openpanel/client.ts
|
|
400
|
+
let client;
|
|
401
|
+
/**
|
|
402
|
+
* Returns the cached `OpenPanel` singleton, building it on first call.
|
|
403
|
+
*
|
|
404
|
+
* When `config.clientId` is empty it returns `undefined` without importing
|
|
405
|
+
* `@openpanel/web`, so the bundler can tree-shake the SDK out of production.
|
|
406
|
+
*/
|
|
407
|
+
async function getOpenPanelClient(config) {
|
|
408
|
+
if (!config.clientId) return;
|
|
409
|
+
if (client) return client;
|
|
410
|
+
const { OpenPanel: OpenPanelClass } = await import("@openpanel/web");
|
|
411
|
+
client = new OpenPanelClass({
|
|
412
|
+
clientId: config.clientId,
|
|
413
|
+
...config.apiUrl ? { apiUrl: config.apiUrl } : {},
|
|
414
|
+
trackScreenViews: true,
|
|
415
|
+
trackOutgoingLinks: true
|
|
416
|
+
});
|
|
417
|
+
client.setGlobalProperties({ app: "connect" });
|
|
418
|
+
drainOpenPanelBuffer(client);
|
|
419
|
+
return client;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Clears the cached singleton and the pre-init buffer. Call on consent
|
|
423
|
+
* revocation or during tests; buffered events are discarded, not replayed.
|
|
424
|
+
*/
|
|
425
|
+
function resetOpenPanelClient() {
|
|
426
|
+
client = void 0;
|
|
427
|
+
clearOpenPanelBuffer();
|
|
428
|
+
}
|
|
429
|
+
//#endregion
|
|
430
|
+
//#region src/services/openpanel/events.json
|
|
431
|
+
var events_default = [
|
|
127
432
|
{
|
|
128
433
|
"documentType": "powerhouse/document-drive",
|
|
129
434
|
"actionTypes": [
|
|
@@ -212,15 +517,17 @@ function deriveEventName(mapping, op) {
|
|
|
212
517
|
if (mapping.alias) return mapping.alias;
|
|
213
518
|
return `${normalize(op.context.documentType)}.${op.operation.action.type.toLowerCase()}`;
|
|
214
519
|
}
|
|
215
|
-
/**
|
|
520
|
+
/**
|
|
521
|
+
* Builds the default properties attached to every OpenPanel event. The `app`
|
|
522
|
+
* property is stamped globally by the client, not here.
|
|
523
|
+
*/
|
|
216
524
|
function buildDefaultProperties(op) {
|
|
217
525
|
return {
|
|
218
526
|
documentType: op.context.documentType,
|
|
219
527
|
actionType: op.operation.action.type,
|
|
220
528
|
documentId: op.context.documentId,
|
|
221
529
|
scope: op.context.scope,
|
|
222
|
-
branch: op.context.branch
|
|
223
|
-
app: "connect"
|
|
530
|
+
branch: op.context.branch
|
|
224
531
|
};
|
|
225
532
|
}
|
|
226
533
|
//#endregion
|
|
@@ -295,7 +602,7 @@ function useIsEmbedded() {
|
|
|
295
602
|
return getIsEmbedded();
|
|
296
603
|
}
|
|
297
604
|
const COOKIE_BANNER_KEY_STORAGE = `${connectConfig.routerBasename}:display-cookie-banner`;
|
|
298
|
-
const listeners$
|
|
605
|
+
const listeners$1 = /* @__PURE__ */ new Set();
|
|
299
606
|
let bannerShown = getInitial();
|
|
300
607
|
function getInitial() {
|
|
301
608
|
if (getIsEmbedded()) return false;
|
|
@@ -314,8 +621,8 @@ function setCookieBannerState(state) {
|
|
|
314
621
|
localStorage.setItem(COOKIE_BANNER_KEY_STORAGE, JSON.stringify(state));
|
|
315
622
|
}
|
|
316
623
|
function subscribe(fn) {
|
|
317
|
-
listeners$
|
|
318
|
-
return () => listeners$
|
|
624
|
+
listeners$1.add(fn);
|
|
625
|
+
return () => listeners$1.delete(fn);
|
|
319
626
|
}
|
|
320
627
|
const useCookieBanner = () => {
|
|
321
628
|
return [useSyncExternalStore(subscribe, getCookieBannerState, getCookieBannerState), setCookieBannerState];
|
|
@@ -335,7 +642,6 @@ async function getSentry() {
|
|
|
335
642
|
return await import("@sentry/react");
|
|
336
643
|
}
|
|
337
644
|
async function initSentry() {
|
|
338
|
-
connectConfig.sentry;
|
|
339
645
|
const Sentry = await getSentry();
|
|
340
646
|
const integrations = [
|
|
341
647
|
Sentry.httpClientIntegration(),
|
|
@@ -352,7 +658,7 @@ async function initSentry() {
|
|
|
352
658
|
}));
|
|
353
659
|
Sentry.init({
|
|
354
660
|
release: connectConfig.sentry.release,
|
|
355
|
-
dsn: connectConfig.sentry.dsn,
|
|
661
|
+
dsn: connectConfig.sentry.dsn ?? void 0,
|
|
356
662
|
environment: connectConfig.sentry.env,
|
|
357
663
|
integrations,
|
|
358
664
|
ignoreErrors: [
|
|
@@ -384,7 +690,7 @@ function useInitSentry() {
|
|
|
384
690
|
closeClient().catch((error) => logger$2.error("@error", error));
|
|
385
691
|
return;
|
|
386
692
|
}
|
|
387
|
-
if (clientStarted ||
|
|
693
|
+
if (clientStarted || connectConfig.sentry.dsn === null || connectConfig.sentry.dsn === void 0 || connectConfig.sentry.dsn === "") return;
|
|
388
694
|
initSentry().catch((error) => logger$2.error("@error", error));
|
|
389
695
|
}, [analytics]);
|
|
390
696
|
}
|
|
@@ -424,200 +730,81 @@ const useUndoRedoShortcuts = (props) => {
|
|
|
424
730
|
}, {}, [canRedo, redo]);
|
|
425
731
|
};
|
|
426
732
|
//#endregion
|
|
427
|
-
//#region src/components/
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
const [{ analytics }] = useAcceptedCookies();
|
|
434
|
-
const useAnalytics = gaTrackingId && analytics;
|
|
435
|
-
useEffect(() => {
|
|
436
|
-
if (useAnalytics) {
|
|
437
|
-
const script = document.createElement("script");
|
|
438
|
-
script.src = `https://www.googletagmanager.com/gtag/js?id=${gaTrackingId}`;
|
|
439
|
-
script.async = true;
|
|
440
|
-
document.head.appendChild(script);
|
|
441
|
-
window.dataLayer = window.dataLayer || [];
|
|
442
|
-
gtag("js", /* @__PURE__ */ new Date());
|
|
443
|
-
gtag("config", gaTrackingId);
|
|
444
|
-
return () => {
|
|
445
|
-
document.head.removeChild(script);
|
|
446
|
-
};
|
|
447
|
-
}
|
|
448
|
-
}, [useAnalytics, gaTrackingId]);
|
|
449
|
-
return null;
|
|
733
|
+
//#region src/components/app-skeleton.tsx
|
|
734
|
+
const LOADER_DELAY = 250;
|
|
735
|
+
const PHASE_LABEL = {
|
|
736
|
+
clone: "Backing up local database…",
|
|
737
|
+
dump: "Exporting data from previous version…",
|
|
738
|
+
restore: "Restoring data into the new database…"
|
|
450
739
|
};
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
* Builds the OpenPanel identity traits from a renown User.
|
|
455
|
-
*
|
|
456
|
-
* Rules:
|
|
457
|
-
* - `credential` (contains a JWT) is **never** forwarded.
|
|
458
|
-
* - `did` / `profileId` is sent as the top-level `profileId` key in the
|
|
459
|
-
* `client.identify()` call — it is **not** duplicated inside traits.
|
|
460
|
-
* - Optional fields (`ens`, `profile`) are only included when their value is
|
|
461
|
-
* non-nullish (guards against `null` from `RenownProfile` fields as well
|
|
462
|
-
* as plain `undefined`).
|
|
463
|
-
*
|
|
464
|
-
* Mirror the Sentry pattern in `store/user.ts`: destructure off `credential`
|
|
465
|
-
* first, then assemble the trait payload from the remainder.
|
|
466
|
-
*/
|
|
467
|
-
function buildTraits(user) {
|
|
468
|
-
const { credential: _credential, ...rest } = user;
|
|
469
|
-
const traits = {
|
|
470
|
-
address: rest.address,
|
|
471
|
-
networkId: rest.networkId,
|
|
472
|
-
chainId: rest.chainId
|
|
473
|
-
};
|
|
474
|
-
if (rest.ens?.name != null) traits.ensName = rest.ens.name;
|
|
475
|
-
if (rest.ens?.avatarUrl != null) traits.ensAvatar = rest.ens.avatarUrl;
|
|
476
|
-
if (rest.profile?.username != null) traits.username = rest.profile.username;
|
|
477
|
-
if (rest.profile?.userImage != null) traits.userImage = rest.profile.userImage;
|
|
478
|
-
if (rest.profile?.documentId != null) traits.profileDocumentId = rest.profile.documentId;
|
|
479
|
-
if (rest.profile?.createdAt != null) traits.profileCreatedAt = rest.profile.createdAt;
|
|
480
|
-
return traits;
|
|
481
|
-
}
|
|
482
|
-
//#endregion
|
|
483
|
-
//#region src/components/openpanel.tsx
|
|
484
|
-
/**
|
|
485
|
-
* `OpenPanel` is a `null`-rendering component that manages the full lifecycle
|
|
486
|
-
* of the OpenPanel analytics subsystem.
|
|
487
|
-
*
|
|
488
|
-
* Gates:
|
|
489
|
-
* - `useAcceptedCookies().analytics === true` — the existing analytics cookie.
|
|
490
|
-
* - `connectConfig.openPanel.clientId` non-empty — no client ID → no-op.
|
|
491
|
-
*
|
|
492
|
-
* Behaviour when both gates pass:
|
|
493
|
-
* 1. Lazy-imports `@openpanel/web` and builds the singleton via
|
|
494
|
-
* `getOpenPanelClient(connectConfig.openPanel)`.
|
|
495
|
-
* 2. Exposes the client on `window.openPanel` for the card-6 `useOpenPanel()`
|
|
496
|
-
* hook to consume.
|
|
497
|
-
* 3. If `connectConfig.openPanel.trackOperations`, registers the processor
|
|
498
|
-
* factory against `processorManager` so document operations are forwarded.
|
|
499
|
-
* 4. Calls `client.identify()` when the renown user transitions from
|
|
500
|
-
* `undefined` → defined (login).
|
|
501
|
-
* 5. Calls `client.clear()` when the user transitions from defined →
|
|
502
|
-
* `undefined` (logout).
|
|
503
|
-
*
|
|
504
|
-
* Teardown (unmount **or** consent revocation):
|
|
505
|
-
* - `unregisterFactory("openpanel")` to remove the processor.
|
|
506
|
-
* - `resetOpenPanelClient()` to clear the singleton.
|
|
507
|
-
* - `window.openPanel` is cleared.
|
|
508
|
-
*
|
|
509
|
-
* Mirrors the Sentry pattern in `store/user.ts` for login/logout detection.
|
|
510
|
-
* Analytics failures are caught and forwarded to `console.warn` — they must
|
|
511
|
-
* never throw into the application.
|
|
512
|
-
*
|
|
513
|
-
* Mounted next to `<Analytics />` in `app.tsx`.
|
|
514
|
-
*/
|
|
515
|
-
function OpenPanel() {
|
|
516
|
-
const [{ analytics }] = useAcceptedCookies();
|
|
517
|
-
const user = useUser();
|
|
518
|
-
const reactorClientModule = useReactorClientModule();
|
|
519
|
-
const [client, setClient] = useState();
|
|
520
|
-
/**
|
|
521
|
-
* Tracks the previous `user` value so we can detect the two transitions:
|
|
522
|
-
* undefined → defined (login)
|
|
523
|
-
* defined → undefined (logout)
|
|
524
|
-
*/
|
|
525
|
-
const prevUserRef = useRef(void 0);
|
|
526
|
-
const enabled = analytics && !!connectConfig.openPanel.clientId;
|
|
527
|
-
const processorManager = reactorClientModule?.reactorModule?.processorManager;
|
|
528
|
-
useEffect(() => {
|
|
529
|
-
if (!enabled) return;
|
|
530
|
-
let cancelled = false;
|
|
531
|
-
(async () => {
|
|
532
|
-
let builtClient;
|
|
533
|
-
try {
|
|
534
|
-
builtClient = await getOpenPanelClient(connectConfig.openPanel);
|
|
535
|
-
} catch (err) {
|
|
536
|
-
console.warn("[OpenPanel] Failed to build client:", err);
|
|
537
|
-
return;
|
|
538
|
-
}
|
|
539
|
-
if (cancelled || !builtClient) return;
|
|
540
|
-
setClient(builtClient);
|
|
541
|
-
window.openPanel = builtClient;
|
|
542
|
-
if (connectConfig.openPanel.trackOperations && processorManager) try {
|
|
543
|
-
await processorManager.registerFactory("openpanel", createOpenPanelProcessorFactory({
|
|
544
|
-
client: builtClient,
|
|
545
|
-
events: {
|
|
546
|
-
mappings: eventMappings,
|
|
547
|
-
lookupMap: eventLookupMap
|
|
548
|
-
},
|
|
549
|
-
startFrom: "current"
|
|
550
|
-
}));
|
|
551
|
-
if (cancelled) await processorManager.unregisterFactory("openpanel").catch((err) => console.warn("[OpenPanel] Failed to unregister factory after cancellation:", err));
|
|
552
|
-
} catch (err) {
|
|
553
|
-
console.warn("[OpenPanel] Failed to register processor factory:", err);
|
|
554
|
-
}
|
|
555
|
-
})();
|
|
556
|
-
return () => {
|
|
557
|
-
cancelled = true;
|
|
558
|
-
prevUserRef.current = void 0;
|
|
559
|
-
if (connectConfig.openPanel.trackOperations && processorManager) processorManager.unregisterFactory("openpanel").catch((err) => console.warn("[OpenPanel] Failed to unregister processor factory:", err));
|
|
560
|
-
resetOpenPanelClient();
|
|
561
|
-
window.openPanel = void 0;
|
|
562
|
-
setClient(void 0);
|
|
563
|
-
};
|
|
564
|
-
}, [enabled, processorManager]);
|
|
565
|
-
useEffect(() => {
|
|
566
|
-
if (!enabled || !client) return;
|
|
567
|
-
const prev = prevUserRef.current;
|
|
568
|
-
prevUserRef.current = user;
|
|
569
|
-
if (!prev && user) try {
|
|
570
|
-
client.identify({
|
|
571
|
-
profileId: user.did,
|
|
572
|
-
properties: buildTraits(user)
|
|
573
|
-
});
|
|
574
|
-
} catch (err) {
|
|
575
|
-
console.warn("[OpenPanel] Failed to identify user:", err);
|
|
576
|
-
}
|
|
577
|
-
else if (prev && !user) try {
|
|
578
|
-
client.clear();
|
|
579
|
-
} catch (err) {
|
|
580
|
-
console.warn("[OpenPanel] Failed to clear user:", err);
|
|
581
|
-
}
|
|
582
|
-
}, [
|
|
583
|
-
client,
|
|
584
|
-
user,
|
|
585
|
-
enabled
|
|
586
|
-
]);
|
|
587
|
-
return null;
|
|
588
|
-
}
|
|
589
|
-
//#endregion
|
|
590
|
-
//#region src/components/document-editor-container.tsx
|
|
591
|
-
function DocumentEditorContainer() {
|
|
592
|
-
const [selectedDocument] = useSelectedDocument();
|
|
593
|
-
const parentFolder = useNodeParentFolderById(selectedDocument.header.id);
|
|
594
|
-
useMemo(() => {
|
|
595
|
-
return async () => {};
|
|
596
|
-
}, []);
|
|
597
|
-
useCallback(() => {
|
|
598
|
-
setSelectedNode(parentFolder);
|
|
599
|
-
}, [parentFolder]);
|
|
740
|
+
const MigrationOverlay = () => {
|
|
741
|
+
const status = useSyncExternalStore(subscribeMigrationStatus, getMigrationStatus, () => null);
|
|
742
|
+
if (!status) return null;
|
|
600
743
|
return /* @__PURE__ */ jsx("div", {
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
744
|
+
className: "absolute inset-0 z-20 flex items-center justify-center",
|
|
745
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
746
|
+
className: "rounded-lg bg-white/90 px-6 py-4 text-sm text-gray-900 shadow-lg dark:bg-slate-900/90 dark:text-slate-50",
|
|
747
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
748
|
+
className: "font-medium",
|
|
749
|
+
children: "Upgrading local database…"
|
|
750
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
751
|
+
className: "text-gray-700 dark:text-slate-200",
|
|
752
|
+
children: PHASE_LABEL[status.phase]
|
|
753
|
+
})]
|
|
754
|
+
})
|
|
605
755
|
});
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
756
|
+
};
|
|
757
|
+
const Loader = ({ delay = LOADER_DELAY }) => {
|
|
758
|
+
const isSSR = typeof window === "undefined";
|
|
759
|
+
const showInitialLoader = typeof document !== "undefined" && document.body.getAttribute("data-show-loader") === "true";
|
|
760
|
+
if (!isSSR) initTheme();
|
|
761
|
+
const [showLoading, setShowLoading] = useState(!delay || showInitialLoader);
|
|
611
762
|
useEffect(() => {
|
|
612
|
-
setTimeout(() => {
|
|
763
|
+
const id = setTimeout(() => {
|
|
613
764
|
setShowLoading(true);
|
|
614
|
-
},
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
765
|
+
}, delay);
|
|
766
|
+
return () => clearTimeout(id);
|
|
767
|
+
}, []);
|
|
768
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
769
|
+
className: `skeleton-loader absolute inset-0 z-10 flex items-center justify-center ${showLoading ? "" : "hidden"}`,
|
|
770
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
771
|
+
className: "animate-pulse overflow-hidden rounded-full shadow-lg",
|
|
772
|
+
children: /* @__PURE__ */ jsx(LogoAnimation, {})
|
|
773
|
+
}), isSSR ? /* @__PURE__ */ jsx("script", { dangerouslySetInnerHTML: { __html: `setTimeout(() => {
|
|
774
|
+
document.querySelector('.skeleton-loader')?.classList.remove('hidden');
|
|
775
|
+
document.body.setAttribute('data-show-loader', 'true');
|
|
776
|
+
}, ${delay})` } }) : null]
|
|
777
|
+
});
|
|
778
|
+
};
|
|
779
|
+
const AppSkeleton = (props) => {
|
|
780
|
+
const isSSR = typeof window === "undefined";
|
|
781
|
+
const isHomeScreen = !isSSR && window.location.pathname === getBasePath();
|
|
782
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
783
|
+
className: "flex h-screen overflow-hidden bg-gray-50 dark:bg-slate-700",
|
|
784
|
+
children: [
|
|
785
|
+
!(!isSSR && getIsEmbedded()) && /* @__PURE__ */ jsx(ConnectSidebar, {
|
|
786
|
+
className: "animate-pulse",
|
|
787
|
+
onLogin: void 0,
|
|
788
|
+
onDisconnect: void 0,
|
|
789
|
+
onClickSettings: void 0,
|
|
790
|
+
address: void 0
|
|
791
|
+
}),
|
|
792
|
+
/* @__PURE__ */ jsx(HomeScreen, {
|
|
793
|
+
containerClassName: isSSR || !isHomeScreen ? "hidden home-screen" : void 0,
|
|
794
|
+
children: props.children ?? null
|
|
795
|
+
}),
|
|
796
|
+
isSSR ? /* @__PURE__ */ jsx("script", { dangerouslySetInnerHTML: { __html: `
|
|
797
|
+
const baseEl = document.querySelector('base');
|
|
798
|
+
const href = baseEl?.getAttribute('href');
|
|
799
|
+
const basePath = href || '/';
|
|
800
|
+
if (window.location.pathname === basePath) {
|
|
801
|
+
document.querySelector('.home-screen')?.classList.remove('hidden')
|
|
802
|
+
}` } }) : null,
|
|
803
|
+
!props.children ? /* @__PURE__ */ jsx(Loader, {}) : null,
|
|
804
|
+
/* @__PURE__ */ jsx(MigrationOverlay, {})
|
|
805
|
+
]
|
|
806
|
+
});
|
|
807
|
+
};
|
|
621
808
|
//#endregion
|
|
622
809
|
//#region src/components/error-boundary.tsx
|
|
623
810
|
const logger$1 = childLogger(["Connect"]);
|
|
@@ -762,564 +949,253 @@ function ErrorBoundary$1(props) {
|
|
|
762
949
|
});
|
|
763
950
|
}
|
|
764
951
|
//#endregion
|
|
765
|
-
//#region src/components/
|
|
766
|
-
function
|
|
767
|
-
|
|
768
|
-
const selectedDocumentId = useSelectedDocumentId();
|
|
769
|
-
const app = useAppModuleById(selectedDrive?.header.meta?.preferredEditor);
|
|
770
|
-
const defaultApp = useDefaultAppModule();
|
|
771
|
-
if (!selectedDrive) return /* @__PURE__ */ jsx(EditorLoader, {});
|
|
772
|
-
const AppComponent = app?.Component ?? defaultApp?.Component ?? GenericDriveExplorer.Component;
|
|
773
|
-
if (!AppComponent) throw new Error("No app component found");
|
|
774
|
-
return /* @__PURE__ */ jsx(ErrorBoundary$1, {
|
|
775
|
-
variant: "detailed",
|
|
776
|
-
resetKeys: [selectedDrive.header.id],
|
|
777
|
-
loggerContext: ["Connect", "App"],
|
|
778
|
-
children: /* @__PURE__ */ jsx(Suspense, {
|
|
779
|
-
fallback: /* @__PURE__ */ jsx(EditorLoader, {}),
|
|
780
|
-
children: /* @__PURE__ */ jsx(DropZoneWrapper, {
|
|
781
|
-
className: "flex h-full flex-col overflow-auto",
|
|
782
|
-
children: /* @__PURE__ */ jsx(AppComponent, { children: selectedDocumentId ? /* @__PURE__ */ jsx(DocumentEditorContainer, {}) : null })
|
|
783
|
-
})
|
|
784
|
-
})
|
|
785
|
-
});
|
|
786
|
-
}
|
|
787
|
-
//#endregion
|
|
788
|
-
//#region src/utils/browser.ts
|
|
789
|
-
function getBasePath() {
|
|
790
|
-
if (typeof document === "undefined") return "/";
|
|
791
|
-
return document.querySelector("base")?.getAttribute("href") || "/";
|
|
952
|
+
//#region src/components/analytics.tsx
|
|
953
|
+
function gtag(...args) {
|
|
954
|
+
window.dataLayer?.push(...args);
|
|
792
955
|
}
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
}
|
|
813
|
-
getOperations() {
|
|
814
|
-
return this.operations;
|
|
815
|
-
}
|
|
816
|
-
pushOperation(operation) {
|
|
817
|
-
this.operations.push(operation);
|
|
818
|
-
}
|
|
819
|
-
operationsTable() {
|
|
820
|
-
if (!this.document) console.warn("No document");
|
|
821
|
-
const ops = Object.values(this.document?.operations || {}).filter((array) => array !== void 0).flatMap((array) => array).sort((a, b) => a.index - b.index);
|
|
822
|
-
console.table(this.operationsToTableObject(ops));
|
|
823
|
-
}
|
|
824
|
-
scopeOperationsTable(scope) {
|
|
825
|
-
if (!this.document) console.warn("No document");
|
|
826
|
-
const ops = this.document?.operations[scope] || [];
|
|
827
|
-
console.table(this.operationsToTableObject(ops));
|
|
828
|
-
}
|
|
829
|
-
operationsLog() {
|
|
830
|
-
console.log(this.operations);
|
|
831
|
-
}
|
|
832
|
-
operationsLogTable() {
|
|
833
|
-
console.table(this.operationsToTableObject(this.operations));
|
|
834
|
-
}
|
|
835
|
-
clear() {
|
|
836
|
-
this.operations = [];
|
|
837
|
-
this.document = void 0;
|
|
838
|
-
}
|
|
956
|
+
const Analytics = () => {
|
|
957
|
+
const gaTrackingId = connectConfig.gaTrackingId;
|
|
958
|
+
const [{ analytics }] = useAcceptedCookies();
|
|
959
|
+
const useAnalytics = gaTrackingId && analytics;
|
|
960
|
+
useEffect(() => {
|
|
961
|
+
if (useAnalytics) {
|
|
962
|
+
const script = document.createElement("script");
|
|
963
|
+
script.src = `https://www.googletagmanager.com/gtag/js?id=${gaTrackingId}`;
|
|
964
|
+
script.async = true;
|
|
965
|
+
document.head.appendChild(script);
|
|
966
|
+
window.dataLayer = window.dataLayer || [];
|
|
967
|
+
gtag("js", /* @__PURE__ */ new Date());
|
|
968
|
+
gtag("config", gaTrackingId);
|
|
969
|
+
return () => {
|
|
970
|
+
document.head.removeChild(script);
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
}, [useAnalytics, gaTrackingId]);
|
|
974
|
+
return null;
|
|
839
975
|
};
|
|
840
976
|
//#endregion
|
|
841
|
-
//#region src/components/
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
977
|
+
//#region src/components/openpanel-traits.ts
|
|
978
|
+
/**
|
|
979
|
+
* Builds the OpenPanel identity traits from a renown User.
|
|
980
|
+
*
|
|
981
|
+
* Canonical trait schema (shared with Renown and Vetra — each app sends every
|
|
982
|
+
* field it has data for, under these names):
|
|
983
|
+
* - `address`, `did`, `networkId`, `chainId`, `caip2` — wallet/chain facts.
|
|
984
|
+
* - `ensName`, `ensAvatar` — raw ENS facts.
|
|
985
|
+
* - `username`, `userImage`, `profileDocumentId`, `profileCreatedAt` — raw
|
|
986
|
+
* Renown profile facts.
|
|
987
|
+
* - `accountType` — wallet-adapter concept; only Renown has it.
|
|
988
|
+
*
|
|
989
|
+
* Rules:
|
|
990
|
+
* - `credential` (contains a JWT) is **never** forwarded.
|
|
991
|
+
* - Traits carry raw facts only; resolved presentation values (avatar,
|
|
992
|
+
* display name) go in OpenPanel's native identify fields — see
|
|
993
|
+
* {@link buildIdentifyPayload}.
|
|
994
|
+
* - Optional fields (`ens`, `profile`) are only included when their value is
|
|
995
|
+
* non-nullish (guards against `null` from `RenownProfile` fields as well
|
|
996
|
+
* as plain `undefined`).
|
|
997
|
+
*
|
|
998
|
+
* Mirror the Sentry pattern in `store/user.ts`: destructure off `credential`
|
|
999
|
+
* first, then assemble the trait payload from the remainder.
|
|
1000
|
+
*/
|
|
1001
|
+
function buildTraits(user) {
|
|
1002
|
+
const { credential: _credential, ...rest } = user;
|
|
1003
|
+
const traits = {
|
|
1004
|
+
address: rest.address,
|
|
1005
|
+
did: rest.did,
|
|
1006
|
+
networkId: rest.networkId,
|
|
1007
|
+
chainId: rest.chainId,
|
|
1008
|
+
caip2: `${rest.networkId}:${rest.chainId}`
|
|
1009
|
+
};
|
|
1010
|
+
if (rest.ens?.name != null) traits.ensName = rest.ens.name;
|
|
1011
|
+
if (rest.ens?.avatarUrl != null) traits.ensAvatar = rest.ens.avatarUrl;
|
|
1012
|
+
if (rest.profile?.username != null) traits.username = rest.profile.username;
|
|
1013
|
+
if (rest.profile?.userImage != null) traits.userImage = rest.profile.userImage;
|
|
1014
|
+
if (rest.profile?.documentId != null) traits.profileDocumentId = rest.profile.documentId;
|
|
1015
|
+
if (rest.profile?.createdAt != null) traits.profileCreatedAt = rest.profile.createdAt;
|
|
1016
|
+
return traits;
|
|
850
1017
|
}
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
1018
|
+
/**
|
|
1019
|
+
* Builds the full `identify()` payload:
|
|
1020
|
+
* - `profileId` is the wallet address — the cross-app profile key shared with
|
|
1021
|
+
* Renown and Vetra.
|
|
1022
|
+
* - `avatar` / `firstName` are OpenPanel's native profile fields (they drive
|
|
1023
|
+
* the picture and name in the dashboard UI), resolved as
|
|
1024
|
+
* `userImage ?? ensAvatar` and `ensName ?? username`.
|
|
1025
|
+
* - `properties` are the raw traits from {@link buildTraits}.
|
|
1026
|
+
*/
|
|
1027
|
+
function buildIdentifyPayload(user) {
|
|
1028
|
+
const avatar = user.profile?.userImage ?? user.ens?.avatarUrl;
|
|
1029
|
+
const firstName = user.ens?.name ?? user.profile?.username;
|
|
1030
|
+
return {
|
|
1031
|
+
profileId: user.address,
|
|
1032
|
+
...avatar != null ? { avatar } : {},
|
|
1033
|
+
...firstName != null ? { firstName } : {},
|
|
1034
|
+
properties: buildTraits(user)
|
|
855
1035
|
};
|
|
856
1036
|
}
|
|
857
1037
|
//#endregion
|
|
858
|
-
//#region src/
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
1038
|
+
//#region src/components/openpanel.tsx
|
|
1039
|
+
/**
|
|
1040
|
+
* `OpenPanel` is a `null`-rendering component that manages the full lifecycle
|
|
1041
|
+
* of the OpenPanel analytics subsystem.
|
|
1042
|
+
*
|
|
1043
|
+
* Gates:
|
|
1044
|
+
* - `useAcceptedCookies().analytics === true` — the existing analytics cookie.
|
|
1045
|
+
* - `connectConfig.openPanel.clientId` non-empty — no client ID → no-op.
|
|
1046
|
+
*
|
|
1047
|
+
* Behaviour when both gates pass:
|
|
1048
|
+
* 1. Lazy-imports `@openpanel/web` and builds the singleton via
|
|
1049
|
+
* `getOpenPanelClient(connectConfig.openPanel)`.
|
|
1050
|
+
* 2. Exposes the client on `window.openPanel` for the card-6
|
|
1051
|
+
* `useOpenPanelAnalytics()` hook to consume.
|
|
1052
|
+
* 3. If `connectConfig.openPanel.trackOperations`, registers the processor
|
|
1053
|
+
* factory against `processorManager` so document operations are forwarded.
|
|
1054
|
+
* 4. Calls `client.identify()` when the renown user transitions from
|
|
1055
|
+
* `undefined` → defined (login). The profile ID is the wallet address —
|
|
1056
|
+
* the cross-app key shared with Renown and Vetra.
|
|
1057
|
+
* 5. Calls `client.clear()` when the user transitions from defined →
|
|
1058
|
+
* `undefined` (logout).
|
|
1059
|
+
*
|
|
1060
|
+
* Teardown (unmount **or** consent revocation):
|
|
1061
|
+
* - `unregisterFactory("openpanel")` to remove the processor.
|
|
1062
|
+
* - `resetOpenPanelClient()` to clear the singleton.
|
|
1063
|
+
* - `window.openPanel` is cleared.
|
|
1064
|
+
*
|
|
1065
|
+
* Mirrors the Sentry pattern in `store/user.ts` for login/logout detection.
|
|
1066
|
+
* Analytics failures are caught and forwarded to `console.warn` — they must
|
|
1067
|
+
* never throw into the application.
|
|
1068
|
+
*
|
|
1069
|
+
* Mounted next to `<Analytics />` in `app.tsx`.
|
|
1070
|
+
*/
|
|
1071
|
+
function OpenPanel() {
|
|
1072
|
+
const [{ analytics }] = useAcceptedCookies();
|
|
1073
|
+
const user = useUser();
|
|
1074
|
+
const reactorClientModule = useReactorClientModule();
|
|
1075
|
+
const [client, setClient] = useState();
|
|
1076
|
+
/**
|
|
1077
|
+
* Tracks the previous `user` value so we can detect the two transitions:
|
|
1078
|
+
* undefined → defined (login)
|
|
1079
|
+
* defined → undefined (logout)
|
|
1080
|
+
*/
|
|
1081
|
+
const prevUserRef = useRef(void 0);
|
|
1082
|
+
const enabled = analytics && !!connectConfig.openPanel.clientId;
|
|
1083
|
+
const processorManager = reactorClientModule?.reactorModule?.processorManager;
|
|
1084
|
+
useEffect(() => {
|
|
1085
|
+
if (!enabled) return;
|
|
1086
|
+
let cancelled = false;
|
|
1087
|
+
const isCancelled = () => cancelled;
|
|
1088
|
+
const setup = async () => {
|
|
1089
|
+
let builtClient;
|
|
1090
|
+
try {
|
|
1091
|
+
builtClient = await getOpenPanelClient(connectConfig.openPanel);
|
|
1092
|
+
} catch (err) {
|
|
1093
|
+
console.warn("[OpenPanel] Failed to build client:", err);
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1096
|
+
if (isCancelled() || !builtClient) return;
|
|
1097
|
+
setClient(builtClient);
|
|
1098
|
+
window.openPanel = builtClient;
|
|
1099
|
+
if (connectConfig.openPanel.trackOperations && processorManager) try {
|
|
1100
|
+
await processorManager.registerFactory("openpanel", createOpenPanelProcessorFactory({
|
|
1101
|
+
client: builtClient,
|
|
1102
|
+
events: {
|
|
1103
|
+
mappings: eventMappings,
|
|
1104
|
+
lookupMap: eventLookupMap
|
|
1105
|
+
},
|
|
1106
|
+
startFrom: "current"
|
|
1107
|
+
}));
|
|
1108
|
+
if (isCancelled()) await processorManager.unregisterFactory("openpanel").catch((err) => console.warn("[OpenPanel] Failed to unregister factory after cancellation:", err));
|
|
1109
|
+
} catch (err) {
|
|
1110
|
+
console.warn("[OpenPanel] Failed to register processor factory:", err);
|
|
1111
|
+
}
|
|
882
1112
|
};
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
}
|
|
892
|
-
});
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
tx.oncomplete = () => resolve();
|
|
900
|
-
tx.onerror = () => reject(idbError(tx, `FILE_DATA clear failed for ${name}`));
|
|
901
|
-
tx.onabort = () => reject(idbError(tx, `FILE_DATA clear aborted for ${name}`));
|
|
902
|
-
tx.objectStore(IDB_STORE_NAME).clear();
|
|
903
|
-
});
|
|
904
|
-
} finally {
|
|
905
|
-
db.close();
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
async function cloneFileData(sourceName, destName) {
|
|
909
|
-
const source = await openIdb(sourceName, 21, createFileDataStore);
|
|
910
|
-
try {
|
|
911
|
-
const dest = await openIdb(destName, 21, createFileDataStore);
|
|
912
|
-
try {
|
|
913
|
-
const readStore = source.transaction(IDB_STORE_NAME, "readonly").objectStore(IDB_STORE_NAME);
|
|
914
|
-
const entries = await new Promise((resolve, reject) => {
|
|
915
|
-
const out = [];
|
|
916
|
-
const req = readStore.openCursor();
|
|
917
|
-
req.onerror = () => reject(idbError(req, `FILE_DATA read cursor failed for ${sourceName}`));
|
|
918
|
-
req.onsuccess = () => {
|
|
919
|
-
const cursor = req.result;
|
|
920
|
-
if (!cursor) return resolve(out);
|
|
921
|
-
out.push({
|
|
922
|
-
key: cursor.key,
|
|
923
|
-
value: cursor.value
|
|
924
|
-
});
|
|
925
|
-
cursor.continue();
|
|
926
|
-
};
|
|
927
|
-
});
|
|
928
|
-
await new Promise((resolve, reject) => {
|
|
929
|
-
const writeTx = dest.transaction(IDB_STORE_NAME, "readwrite");
|
|
930
|
-
writeTx.oncomplete = () => resolve();
|
|
931
|
-
writeTx.onerror = () => reject(idbError(writeTx, `FILE_DATA write failed for ${destName}`));
|
|
932
|
-
writeTx.onabort = () => reject(idbError(writeTx, `FILE_DATA write aborted for ${destName}`));
|
|
933
|
-
const writeStore = writeTx.objectStore(IDB_STORE_NAME);
|
|
934
|
-
writeStore.clear();
|
|
935
|
-
for (const { key, value } of entries) writeStore.put(value, key);
|
|
936
|
-
});
|
|
937
|
-
} finally {
|
|
938
|
-
dest.close();
|
|
939
|
-
}
|
|
940
|
-
} finally {
|
|
941
|
-
source.close();
|
|
942
|
-
}
|
|
943
|
-
}
|
|
944
|
-
function readBackupIndex() {
|
|
945
|
-
try {
|
|
946
|
-
const raw = localStorage.getItem(BACKUP_INDEX_KEY);
|
|
947
|
-
if (!raw) return [];
|
|
948
|
-
const parsed = JSON.parse(raw);
|
|
949
|
-
return Array.isArray(parsed) ? parsed : [];
|
|
950
|
-
} catch {
|
|
951
|
-
return [];
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
function writeBackupIndex(records) {
|
|
955
|
-
try {
|
|
956
|
-
localStorage.setItem(BACKUP_INDEX_KEY, JSON.stringify(records));
|
|
957
|
-
} catch {}
|
|
958
|
-
}
|
|
959
|
-
function registerBackup(rec) {
|
|
960
|
-
writeBackupIndex([...readBackupIndex(), rec]);
|
|
961
|
-
}
|
|
962
|
-
function forgetBackup(name) {
|
|
963
|
-
writeBackupIndex(readBackupIndex().filter((r) => r.name !== name));
|
|
964
|
-
}
|
|
965
|
-
async function janitor() {
|
|
966
|
-
const cutoff = Date.now() - BACKUP_RETENTION_MS;
|
|
967
|
-
const keep = [];
|
|
968
|
-
for (const rec of readBackupIndex()) {
|
|
969
|
-
if (rec.createdAt >= cutoff) {
|
|
970
|
-
keep.push(rec);
|
|
971
|
-
continue;
|
|
972
|
-
}
|
|
973
|
-
try {
|
|
974
|
-
await deleteIdb(rec.name);
|
|
1113
|
+
setup();
|
|
1114
|
+
return () => {
|
|
1115
|
+
cancelled = true;
|
|
1116
|
+
prevUserRef.current = void 0;
|
|
1117
|
+
if (connectConfig.openPanel.trackOperations && processorManager) processorManager.unregisterFactory("openpanel").catch((err) => console.warn("[OpenPanel] Failed to unregister processor factory:", err));
|
|
1118
|
+
resetOpenPanelClient();
|
|
1119
|
+
window.openPanel = void 0;
|
|
1120
|
+
setClient(void 0);
|
|
1121
|
+
};
|
|
1122
|
+
}, [enabled, processorManager]);
|
|
1123
|
+
useEffect(() => {
|
|
1124
|
+
if (!enabled || !client) return;
|
|
1125
|
+
const prev = prevUserRef.current;
|
|
1126
|
+
prevUserRef.current = user;
|
|
1127
|
+
if (!prev && user) try {
|
|
1128
|
+
client.identify(buildIdentifyPayload(user));
|
|
975
1129
|
} catch (err) {
|
|
976
|
-
console.warn(
|
|
977
|
-
keep.push(rec);
|
|
1130
|
+
console.warn("[OpenPanel] Failed to identify user:", err);
|
|
978
1131
|
}
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
}
|
|
982
|
-
|
|
983
|
-
setMigrationStatus({
|
|
984
|
-
idbName,
|
|
985
|
-
phase
|
|
986
|
-
});
|
|
987
|
-
}
|
|
988
|
-
async function migrateIdbIfNeeded(idbName) {
|
|
989
|
-
const major = await readPgVersionFile(idbName);
|
|
990
|
-
if (major === null || major === 17) return;
|
|
991
|
-
const loader = LEGACY_DUMPERS[major];
|
|
992
|
-
if (!loader) throw new Error(`Unsupported legacy PGlite data dir: PG_VERSION=${major} for ${idbName}`);
|
|
993
|
-
const backupName = `${BACKUP_PREFIX}${idbName}::${(/* @__PURE__ */ new Date()).toISOString()}`;
|
|
994
|
-
const backupRec = {
|
|
995
|
-
name: backupName,
|
|
996
|
-
idbName,
|
|
997
|
-
createdAt: Date.now()
|
|
998
|
-
};
|
|
999
|
-
publishPhase(idbName, "clone");
|
|
1000
|
-
await cloneFileData(idbName, backupName);
|
|
1001
|
-
registerBackup(backupRec);
|
|
1002
|
-
let sql;
|
|
1003
|
-
try {
|
|
1004
|
-
publishPhase(idbName, "dump");
|
|
1005
|
-
sql = await (await loader())(idbName);
|
|
1006
|
-
} catch (err) {
|
|
1007
|
-
await deleteIdb(backupName).catch(() => {});
|
|
1008
|
-
forgetBackup(backupName);
|
|
1009
|
-
throw err;
|
|
1010
|
-
}
|
|
1011
|
-
try {
|
|
1012
|
-
await clearFileData(idbName);
|
|
1013
|
-
publishPhase(idbName, "restore");
|
|
1014
|
-
const pg = new PGlite(`idb://${idbName.replace(/^\/pglite\//, "")}`, { relaxedDurability: false });
|
|
1015
|
-
try {
|
|
1016
|
-
await pg.waitReady;
|
|
1017
|
-
console.info(`[pglite-migration] Dump preamble (first 500 chars):\n${sql.slice(0, 500)}`);
|
|
1018
|
-
try {
|
|
1019
|
-
await pg.exec("SET standard_conforming_strings = off;");
|
|
1020
|
-
} catch (gucErr) {
|
|
1021
|
-
console.warn("[pglite-migration] Could not force standard_conforming_strings=off", gucErr);
|
|
1022
|
-
}
|
|
1023
|
-
try {
|
|
1024
|
-
await pg.exec(sql);
|
|
1025
|
-
} catch (execErr) {
|
|
1026
|
-
logRestoreFailure(idbName, sql, execErr);
|
|
1027
|
-
throw execErr;
|
|
1028
|
-
}
|
|
1029
|
-
} finally {
|
|
1030
|
-
await pg.close();
|
|
1132
|
+
else if (prev && !user) try {
|
|
1133
|
+
client.clear();
|
|
1134
|
+
} catch (err) {
|
|
1135
|
+
console.warn("[OpenPanel] Failed to clear user:", err);
|
|
1031
1136
|
}
|
|
1032
|
-
}
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
const after = sql.slice(zeroBased + 1, end);
|
|
1056
|
-
console.error(`[pglite-migration] SQL context around position ${position}:\n${before}»${at}«${after}`);
|
|
1057
|
-
const lineStart = sql.lastIndexOf("\n", zeroBased) + 1;
|
|
1058
|
-
const lineEnd = sql.indexOf("\n", zeroBased);
|
|
1059
|
-
const line = sql.slice(lineStart, lineEnd === -1 ? sql.length : lineEnd);
|
|
1060
|
-
console.error(`[pglite-migration] Failing line (truncated to 500 chars):\n${line.slice(0, 500)}`);
|
|
1061
|
-
} else console.error(`[pglite-migration] No position info. First 2000 chars of dump:\n${sql.slice(0, 2e3)}`);
|
|
1062
|
-
}
|
|
1063
|
-
async function rollbackFromBackup(idbName, backupName, originalError) {
|
|
1064
|
-
try {
|
|
1065
|
-
await clearFileData(idbName).catch(() => {});
|
|
1066
|
-
await cloneFileData(backupName, idbName);
|
|
1067
|
-
} catch (rollbackErr) {
|
|
1068
|
-
console.error(`PGlite migration failed AND rollback failed for ${idbName}. Primary error:`, originalError, "Rollback error:", rollbackErr);
|
|
1069
|
-
return;
|
|
1070
|
-
}
|
|
1071
|
-
await deleteIdb(backupName).catch(() => {});
|
|
1072
|
-
forgetBackup(backupName);
|
|
1073
|
-
}
|
|
1074
|
-
async function migrateAllLocked() {
|
|
1075
|
-
for (const idbName of PRIMARY_IDB_NAMES) await migrateIdbIfNeeded(idbName);
|
|
1076
|
-
setMigrationStatus(null);
|
|
1077
|
-
}
|
|
1078
|
-
async function migrateAllIfNeeded() {
|
|
1079
|
-
janitor().catch((err) => console.warn("PGlite backup janitor failed", err));
|
|
1080
|
-
if (!(typeof navigator !== "undefined" && "locks" in navigator && typeof navigator.locks.request === "function")) {
|
|
1081
|
-
await migrateAllLocked();
|
|
1082
|
-
return;
|
|
1083
|
-
}
|
|
1084
|
-
await navigator.locks.request("ph-pglite-migration", { mode: "exclusive" }, async () => {
|
|
1085
|
-
await migrateAllLocked();
|
|
1086
|
-
});
|
|
1087
|
-
}
|
|
1088
|
-
//#endregion
|
|
1089
|
-
//#region src/components/app-skeleton.tsx
|
|
1090
|
-
const LOADER_DELAY = 250;
|
|
1091
|
-
const PHASE_LABEL = {
|
|
1092
|
-
clone: "Backing up local database…",
|
|
1093
|
-
dump: "Exporting data from previous version…",
|
|
1094
|
-
restore: "Restoring data into the new database…"
|
|
1095
|
-
};
|
|
1096
|
-
const MigrationOverlay = () => {
|
|
1097
|
-
const status = useSyncExternalStore(subscribeMigrationStatus, getMigrationStatus, () => null);
|
|
1098
|
-
if (!status) return null;
|
|
1099
|
-
return /* @__PURE__ */ jsx("div", {
|
|
1100
|
-
className: "absolute inset-0 z-20 flex items-center justify-center",
|
|
1101
|
-
children: /* @__PURE__ */ jsxs("div", {
|
|
1102
|
-
className: "rounded-lg bg-white/90 px-6 py-4 text-sm text-gray-900 shadow-lg dark:bg-slate-900/90 dark:text-slate-50",
|
|
1103
|
-
children: [/* @__PURE__ */ jsx("div", {
|
|
1104
|
-
className: "font-medium",
|
|
1105
|
-
children: "Upgrading local database…"
|
|
1106
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
1107
|
-
className: "text-gray-700 dark:text-slate-200",
|
|
1108
|
-
children: PHASE_LABEL[status.phase]
|
|
1109
|
-
})]
|
|
1110
|
-
})
|
|
1111
|
-
});
|
|
1112
|
-
};
|
|
1113
|
-
const Loader = ({ delay = LOADER_DELAY }) => {
|
|
1114
|
-
const isSSR = typeof window === "undefined";
|
|
1115
|
-
const showInitialLoader = typeof document !== "undefined" && document.body.getAttribute("data-show-loader") === "true";
|
|
1116
|
-
if (!isSSR) initTheme();
|
|
1117
|
-
const [showLoading, setShowLoading] = useState(!delay || showInitialLoader);
|
|
1118
|
-
useEffect(() => {
|
|
1119
|
-
const id = setTimeout(() => {
|
|
1120
|
-
setShowLoading(true);
|
|
1121
|
-
}, delay);
|
|
1122
|
-
return () => clearTimeout(id);
|
|
1123
|
-
}, []);
|
|
1124
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
1125
|
-
className: `skeleton-loader absolute inset-0 z-10 flex items-center justify-center ${showLoading ? "" : "hidden"}`,
|
|
1126
|
-
children: [/* @__PURE__ */ jsx("div", {
|
|
1127
|
-
className: "animate-pulse overflow-hidden rounded-full shadow-lg",
|
|
1128
|
-
children: /* @__PURE__ */ jsx(LogoAnimation, {})
|
|
1129
|
-
}), isSSR ? /* @__PURE__ */ jsx("script", { dangerouslySetInnerHTML: { __html: `setTimeout(() => {
|
|
1130
|
-
document.querySelector('.skeleton-loader')?.classList.remove('hidden');
|
|
1131
|
-
document.body.setAttribute('data-show-loader', 'true');
|
|
1132
|
-
}, ${delay})` } }) : null]
|
|
1133
|
-
});
|
|
1134
|
-
};
|
|
1135
|
-
const AppSkeleton = (props) => {
|
|
1136
|
-
const isSSR = typeof window === "undefined";
|
|
1137
|
-
const isHomeScreen = !isSSR && window.location.pathname === getBasePath();
|
|
1138
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
1139
|
-
className: "flex h-screen overflow-hidden bg-gray-50 dark:bg-slate-700",
|
|
1140
|
-
children: [
|
|
1141
|
-
!(!isSSR && getIsEmbedded()) && /* @__PURE__ */ jsx(ConnectSidebar, {
|
|
1142
|
-
className: "animate-pulse",
|
|
1143
|
-
onLogin: void 0,
|
|
1144
|
-
onDisconnect: void 0,
|
|
1145
|
-
onClickSettings: void 0,
|
|
1146
|
-
address: void 0
|
|
1147
|
-
}),
|
|
1148
|
-
/* @__PURE__ */ jsx(HomeScreen, {
|
|
1149
|
-
containerClassName: isSSR || !isHomeScreen ? "hidden home-screen" : void 0,
|
|
1150
|
-
children: props.children ?? null
|
|
1151
|
-
}),
|
|
1152
|
-
isSSR ? /* @__PURE__ */ jsx("script", { dangerouslySetInnerHTML: { __html: `
|
|
1153
|
-
const baseEl = document.querySelector('base');
|
|
1154
|
-
const href = baseEl?.getAttribute('href');
|
|
1155
|
-
const basePath = href || '/';
|
|
1156
|
-
if (window.location.pathname === basePath) {
|
|
1157
|
-
document.querySelector('.home-screen')?.classList.remove('hidden')
|
|
1158
|
-
}` } }) : null,
|
|
1159
|
-
!props.children ? /* @__PURE__ */ jsx(Loader, {}) : null,
|
|
1160
|
-
/* @__PURE__ */ jsx(MigrationOverlay, {})
|
|
1161
|
-
]
|
|
1162
|
-
});
|
|
1163
|
-
};
|
|
1164
|
-
//#endregion
|
|
1165
|
-
//#region src/components/migration-banner.tsx
|
|
1166
|
-
let dismissed = false;
|
|
1167
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
1168
|
-
function notify() {
|
|
1169
|
-
for (const l of listeners) l();
|
|
1170
|
-
}
|
|
1171
|
-
function dismiss() {
|
|
1172
|
-
dismissed = true;
|
|
1173
|
-
notify();
|
|
1174
|
-
}
|
|
1175
|
-
function subscribeDismissed(cb) {
|
|
1176
|
-
listeners.add(cb);
|
|
1177
|
-
return () => {
|
|
1178
|
-
listeners.delete(cb);
|
|
1179
|
-
};
|
|
1180
|
-
}
|
|
1181
|
-
function getDismissed() {
|
|
1182
|
-
return dismissed;
|
|
1183
|
-
}
|
|
1184
|
-
const MigrationBanner = () => {
|
|
1185
|
-
const isDismissed = useSyncExternalStore(subscribeDismissed, getDismissed, () => false);
|
|
1186
|
-
const major = useSyncExternalStore(subscribeReactorPgMajor, getCachedReactorPgMajor, () => void 0);
|
|
1187
|
-
const [migrating, setMigrating] = useState(false);
|
|
1188
|
-
const [error, setError] = useState(null);
|
|
1189
|
-
if (major === void 0 || major === null || major === 17) return null;
|
|
1190
|
-
if (isDismissed) return null;
|
|
1191
|
-
const handleMigrate = () => {
|
|
1192
|
-
setError(null);
|
|
1193
|
-
setMigrating(true);
|
|
1194
|
-
migrateAllIfNeeded().then(() => {
|
|
1195
|
-
window.location.reload();
|
|
1196
|
-
}).catch((err) => {
|
|
1197
|
-
setError(err instanceof Error ? err.message : String(err));
|
|
1198
|
-
setMigrating(false);
|
|
1199
|
-
});
|
|
1200
|
-
};
|
|
1201
|
-
return /* @__PURE__ */ jsx("div", {
|
|
1202
|
-
className: "absolute inset-x-0 top-0 z-30 flex justify-center p-3",
|
|
1203
|
-
children: /* @__PURE__ */ jsxs("div", {
|
|
1204
|
-
className: "flex max-w-3xl items-center gap-3 rounded-lg border border-yellow-500 bg-yellow-400 px-4 py-3 text-sm text-gray-900 shadow-lg dark:border-yellow-400 dark:bg-yellow-500 dark:text-slate-50",
|
|
1205
|
-
children: [
|
|
1206
|
-
/* @__PURE__ */ jsxs("div", {
|
|
1207
|
-
className: "flex-1",
|
|
1208
|
-
children: [
|
|
1209
|
-
/* @__PURE__ */ jsxs("div", {
|
|
1210
|
-
className: "font-semibold",
|
|
1211
|
-
children: [
|
|
1212
|
-
"Your local database uses Postgres ",
|
|
1213
|
-
major,
|
|
1214
|
-
"."
|
|
1215
|
-
]
|
|
1216
|
-
}),
|
|
1217
|
-
/* @__PURE__ */ jsxs("div", {
|
|
1218
|
-
className: "text-gray-900 dark:text-slate-100",
|
|
1219
|
-
children: [
|
|
1220
|
-
"Migrate to Postgres ",
|
|
1221
|
-
17,
|
|
1222
|
-
" to pick up the latest features. Your data will be backed up first."
|
|
1223
|
-
]
|
|
1224
|
-
}),
|
|
1225
|
-
error && /* @__PURE__ */ jsxs("div", {
|
|
1226
|
-
className: "mt-2 font-medium text-red-900 dark:text-red-400",
|
|
1227
|
-
children: ["Migration failed: ", error]
|
|
1228
|
-
})
|
|
1229
|
-
]
|
|
1230
|
-
}),
|
|
1231
|
-
/* @__PURE__ */ jsx("button", {
|
|
1232
|
-
type: "button",
|
|
1233
|
-
disabled: migrating,
|
|
1234
|
-
onClick: handleMigrate,
|
|
1235
|
-
className: "rounded-sm bg-gray-900 px-3 py-1 text-sm font-medium text-white hover:bg-gray-800 disabled:opacity-50 dark:bg-slate-50 dark:text-slate-900 dark:hover:bg-slate-100",
|
|
1236
|
-
children: migrating ? "Migrating…" : "Migrate now"
|
|
1237
|
-
}),
|
|
1238
|
-
/* @__PURE__ */ jsx("button", {
|
|
1239
|
-
type: "button",
|
|
1240
|
-
disabled: migrating,
|
|
1241
|
-
onClick: dismiss,
|
|
1242
|
-
className: "rounded-sm border border-gray-900 bg-transparent px-3 py-1 text-sm font-medium text-gray-900 hover:bg-gray-900/10 disabled:opacity-50 dark:border-slate-50 dark:text-slate-50",
|
|
1243
|
-
children: "Dismiss"
|
|
1244
|
-
})
|
|
1245
|
-
]
|
|
1246
|
-
})
|
|
1137
|
+
}, [
|
|
1138
|
+
client,
|
|
1139
|
+
user,
|
|
1140
|
+
enabled
|
|
1141
|
+
]);
|
|
1142
|
+
return null;
|
|
1143
|
+
}
|
|
1144
|
+
//#endregion
|
|
1145
|
+
//#region src/components/document-editor-container.tsx
|
|
1146
|
+
function DocumentEditorContainer() {
|
|
1147
|
+
const [selectedDocument] = useSelectedDocument();
|
|
1148
|
+
const parentFolder = useNodeParentFolderById(selectedDocument.header.id);
|
|
1149
|
+
useMemo(() => {
|
|
1150
|
+
return async () => {};
|
|
1151
|
+
}, []);
|
|
1152
|
+
useCallback(() => {
|
|
1153
|
+
setSelectedNode(parentFolder);
|
|
1154
|
+
}, [parentFolder]);
|
|
1155
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1156
|
+
id: "document-editor-container",
|
|
1157
|
+
className: "flex-1",
|
|
1158
|
+
"data-document-type": selectedDocument.header.documentType,
|
|
1159
|
+
children: /* @__PURE__ */ jsx(DocumentEditor, { document: selectedDocument })
|
|
1247
1160
|
});
|
|
1248
|
-
}
|
|
1161
|
+
}
|
|
1249
1162
|
//#endregion
|
|
1250
|
-
//#region src/components/
|
|
1251
|
-
|
|
1252
|
-
const
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
const
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
const modalComponents = {
|
|
1264
|
-
addDrive: AddDriveModal$1,
|
|
1265
|
-
clearStorage: ClearStorageModal,
|
|
1266
|
-
cookiesPolicy: CookiesPolicyModal,
|
|
1267
|
-
createDocument: CreateDocumentModal$1,
|
|
1268
|
-
debugSettings: DebugSettingsModal,
|
|
1269
|
-
deleteDrive: DeleteDriveModal,
|
|
1270
|
-
deleteItem: DeleteItemModal,
|
|
1271
|
-
disclaimer: DisclaimerModal,
|
|
1272
|
-
driveSettings: DriveSettingsModal$1,
|
|
1273
|
-
downloadDocumentWithErrors: DownloadDocumentWithErrorsModal,
|
|
1274
|
-
inspector: lazy(() => import("./InspectorModal-ywEL5GKA.js").then((m) => ({ default: m.InspectorModal }))),
|
|
1275
|
-
settings: SettingsModal$1,
|
|
1276
|
-
upgradeDrive: UpgradeDriveModal,
|
|
1277
|
-
missingPackage: lazy(() => import("./MissingPackageModal-CUdcDjSO.js").then((m) => ({ default: m.ConnectMissingPackageModal })))
|
|
1278
|
-
};
|
|
1279
|
-
const ModalsContainer = lazy(() => {
|
|
1280
|
-
return Promise.resolve({ default: () => {
|
|
1281
|
-
const phModal = usePHModal();
|
|
1282
|
-
if (!phModal?.type) return null;
|
|
1283
|
-
const ModalComponent = modalComponents[phModal.type];
|
|
1284
|
-
return ModalComponent ? /* @__PURE__ */ jsx(ErrorBoundary$1, {
|
|
1285
|
-
variant: "silent",
|
|
1286
|
-
loggerContext: ["Connect", "Modals"],
|
|
1287
|
-
children: /* @__PURE__ */ jsx(Suspense, {
|
|
1288
|
-
fallback: null,
|
|
1289
|
-
children: /* @__PURE__ */ jsx(ModalComponent, {})
|
|
1290
|
-
})
|
|
1291
|
-
}) : null;
|
|
1292
|
-
} });
|
|
1293
|
-
});
|
|
1163
|
+
//#region src/components/editor-loader.tsx
|
|
1164
|
+
function EditorLoader(props) {
|
|
1165
|
+
const [showLoading, setShowLoading] = useState(false);
|
|
1166
|
+
useEffect(() => {
|
|
1167
|
+
setTimeout(() => {
|
|
1168
|
+
setShowLoading(true);
|
|
1169
|
+
}, props.loadingTimeout ?? 200);
|
|
1170
|
+
}, [props]);
|
|
1171
|
+
if (!showLoading) return null;
|
|
1172
|
+
const { customEditorLoader, ...defaultProps } = props;
|
|
1173
|
+
if (customEditorLoader) return /* @__PURE__ */ jsx(Fragment, { children: customEditorLoader });
|
|
1174
|
+
return /* @__PURE__ */ jsx(DefaultEditorLoader, { ...defaultProps });
|
|
1175
|
+
}
|
|
1294
1176
|
//#endregion
|
|
1295
|
-
//#region src/components/app-
|
|
1296
|
-
|
|
1297
|
-
const
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
children: /* @__PURE__ */ jsx(ModalsContainer, {})
|
|
1318
|
-
}),
|
|
1319
|
-
/* @__PURE__ */ jsx(MigrationBanner, {})
|
|
1320
|
-
]
|
|
1321
|
-
}) });
|
|
1322
|
-
};
|
|
1177
|
+
//#region src/components/app-container.tsx
|
|
1178
|
+
function AppContainer() {
|
|
1179
|
+
const [selectedDrive] = useSelectedDriveSafe();
|
|
1180
|
+
const selectedDocumentId = useSelectedDocumentId();
|
|
1181
|
+
const app = useAppModuleById(selectedDrive?.header.meta?.preferredEditor);
|
|
1182
|
+
const defaultApp = useDefaultAppModule();
|
|
1183
|
+
if (!selectedDrive) return /* @__PURE__ */ jsx(EditorLoader, {});
|
|
1184
|
+
const AppComponent = app?.Component ?? defaultApp?.Component ?? GenericDriveExplorer.Component;
|
|
1185
|
+
if (!AppComponent) throw new Error("No app component found");
|
|
1186
|
+
return /* @__PURE__ */ jsx(ErrorBoundary$1, {
|
|
1187
|
+
variant: "detailed",
|
|
1188
|
+
resetKeys: [selectedDrive.header.id],
|
|
1189
|
+
loggerContext: ["Connect", "App"],
|
|
1190
|
+
children: /* @__PURE__ */ jsx(Suspense, {
|
|
1191
|
+
fallback: /* @__PURE__ */ jsx(EditorLoader, {}),
|
|
1192
|
+
children: /* @__PURE__ */ jsx(DropZoneWrapper, {
|
|
1193
|
+
className: "flex h-full flex-col overflow-auto",
|
|
1194
|
+
children: /* @__PURE__ */ jsx(AppComponent, { children: selectedDocumentId ? /* @__PURE__ */ jsx(DocumentEditorContainer, {}) : null })
|
|
1195
|
+
})
|
|
1196
|
+
})
|
|
1197
|
+
});
|
|
1198
|
+
}
|
|
1323
1199
|
//#endregion
|
|
1324
1200
|
//#region src/context/sentry-provider.tsx
|
|
1325
1201
|
const SentryProvider = ({ children }) => {
|
|
@@ -1775,10 +1651,14 @@ function DriveItem({ drive }) {
|
|
|
1775
1651
|
}
|
|
1776
1652
|
function HomeScreenContainer() {
|
|
1777
1653
|
const drives = useDrives();
|
|
1778
|
-
const
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1654
|
+
const isAddDriveEnabled = useIsAddDriveEnabled();
|
|
1655
|
+
const homeBackground = getRuntimeConfig().connect?.branding?.homeBackground;
|
|
1656
|
+
return /* @__PURE__ */ jsxs(HomeScreen, {
|
|
1657
|
+
homeBackground,
|
|
1658
|
+
children: [drives?.map((drive) => {
|
|
1659
|
+
return /* @__PURE__ */ jsx(DriveItem, { drive }, drive.header.id);
|
|
1660
|
+
}), isAddDriveEnabled && /* @__PURE__ */ jsx(HomeScreenAddDriveItem, {})]
|
|
1661
|
+
});
|
|
1782
1662
|
}
|
|
1783
1663
|
//#endregion
|
|
1784
1664
|
//#region src/components/router.tsx
|
|
@@ -1817,6 +1697,7 @@ const Router = () => {
|
|
|
1817
1697
|
function Sidebar() {
|
|
1818
1698
|
const user = useUser();
|
|
1819
1699
|
const drives = useDrives();
|
|
1700
|
+
const isAddDriveEnabled = useIsAddDriveEnabled();
|
|
1820
1701
|
const [selectedDrive] = useSelectedDriveSafe();
|
|
1821
1702
|
const connectDebug = localStorage.getItem("CONNECT_DEBUG") === "true";
|
|
1822
1703
|
const ensName = user?.ens?.name || user?.profile?.username || void 0;
|
|
@@ -1852,12 +1733,171 @@ function Sidebar() {
|
|
|
1852
1733
|
onClick: () => setSelectedDrive(drive),
|
|
1853
1734
|
active: selectedDrive?.header.id === drive.header.id,
|
|
1854
1735
|
icon: /* @__PURE__ */ jsx(DriveIcon, { drive })
|
|
1855
|
-
}, index)),
|
|
1736
|
+
}, index)), isAddDriveEnabled && /* @__PURE__ */ jsx(SidebarAddDriveItem, { onClick: onAddDriveClick })]
|
|
1856
1737
|
})
|
|
1857
1738
|
}) });
|
|
1858
1739
|
}
|
|
1859
1740
|
//#endregion
|
|
1860
|
-
|
|
1741
|
+
//#region src/components/migration-banner.tsx
|
|
1742
|
+
let dismissed = false;
|
|
1743
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
1744
|
+
function notify() {
|
|
1745
|
+
for (const l of listeners) l();
|
|
1746
|
+
}
|
|
1747
|
+
function dismiss() {
|
|
1748
|
+
dismissed = true;
|
|
1749
|
+
notify();
|
|
1750
|
+
}
|
|
1751
|
+
function subscribeDismissed(cb) {
|
|
1752
|
+
listeners.add(cb);
|
|
1753
|
+
return () => {
|
|
1754
|
+
listeners.delete(cb);
|
|
1755
|
+
};
|
|
1756
|
+
}
|
|
1757
|
+
function getDismissed() {
|
|
1758
|
+
return dismissed;
|
|
1759
|
+
}
|
|
1760
|
+
const MigrationBanner = () => {
|
|
1761
|
+
const isDismissed = useSyncExternalStore(subscribeDismissed, getDismissed, () => false);
|
|
1762
|
+
const major = useSyncExternalStore(subscribeReactorPgMajor, getCachedReactorPgMajor, () => void 0);
|
|
1763
|
+
const [migrating, setMigrating] = useState(false);
|
|
1764
|
+
const [error, setError] = useState(null);
|
|
1765
|
+
if (major === void 0 || major === null || major === 17) return null;
|
|
1766
|
+
if (isDismissed) return null;
|
|
1767
|
+
const handleMigrate = () => {
|
|
1768
|
+
setError(null);
|
|
1769
|
+
setMigrating(true);
|
|
1770
|
+
migrateAllIfNeeded().then(() => {
|
|
1771
|
+
window.location.reload();
|
|
1772
|
+
}).catch((err) => {
|
|
1773
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
1774
|
+
setMigrating(false);
|
|
1775
|
+
});
|
|
1776
|
+
};
|
|
1777
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1778
|
+
className: "absolute inset-x-0 top-0 z-30 flex justify-center p-3",
|
|
1779
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
1780
|
+
className: "flex max-w-3xl items-center gap-3 rounded-lg border border-yellow-500 bg-yellow-400 px-4 py-3 text-sm text-gray-900 shadow-lg dark:border-yellow-400 dark:bg-yellow-500 dark:text-slate-50",
|
|
1781
|
+
children: [
|
|
1782
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1783
|
+
className: "flex-1",
|
|
1784
|
+
children: [
|
|
1785
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1786
|
+
className: "font-semibold",
|
|
1787
|
+
children: [
|
|
1788
|
+
"Your local database uses Postgres ",
|
|
1789
|
+
major,
|
|
1790
|
+
"."
|
|
1791
|
+
]
|
|
1792
|
+
}),
|
|
1793
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1794
|
+
className: "text-gray-900 dark:text-slate-100",
|
|
1795
|
+
children: [
|
|
1796
|
+
"Migrate to Postgres ",
|
|
1797
|
+
17,
|
|
1798
|
+
" to pick up the latest features. Your data will be backed up first."
|
|
1799
|
+
]
|
|
1800
|
+
}),
|
|
1801
|
+
error && /* @__PURE__ */ jsxs("div", {
|
|
1802
|
+
className: "mt-2 font-medium text-red-900 dark:text-red-400",
|
|
1803
|
+
children: ["Migration failed: ", error]
|
|
1804
|
+
})
|
|
1805
|
+
]
|
|
1806
|
+
}),
|
|
1807
|
+
/* @__PURE__ */ jsx("button", {
|
|
1808
|
+
type: "button",
|
|
1809
|
+
disabled: migrating,
|
|
1810
|
+
onClick: handleMigrate,
|
|
1811
|
+
className: "rounded-sm bg-gray-900 px-3 py-1 text-sm font-medium text-white hover:bg-gray-800 disabled:opacity-50 dark:bg-slate-50 dark:text-slate-900 dark:hover:bg-slate-100",
|
|
1812
|
+
children: migrating ? "Migrating…" : "Migrate now"
|
|
1813
|
+
}),
|
|
1814
|
+
/* @__PURE__ */ jsx("button", {
|
|
1815
|
+
type: "button",
|
|
1816
|
+
disabled: migrating,
|
|
1817
|
+
onClick: dismiss,
|
|
1818
|
+
className: "rounded-sm border border-gray-900 bg-transparent px-3 py-1 text-sm font-medium text-gray-900 hover:bg-gray-900/10 disabled:opacity-50 dark:border-slate-50 dark:text-slate-50",
|
|
1819
|
+
children: "Dismiss"
|
|
1820
|
+
})
|
|
1821
|
+
]
|
|
1822
|
+
})
|
|
1823
|
+
});
|
|
1824
|
+
};
|
|
1825
|
+
//#endregion
|
|
1826
|
+
//#region src/components/modal/modals-container.tsx
|
|
1827
|
+
const AddDriveModal$1 = lazy(() => import("./AddDriveModal-kzL41qdg.js").then((m) => ({ default: m.AddDriveModal })));
|
|
1828
|
+
const ClearStorageModal = lazy(() => import("./ClearStorageModal-DIKwqiia.js").then((m) => ({ default: m.ClearStorageModal })));
|
|
1829
|
+
const CookiesPolicyModal = lazy(() => import("./CookiesPolicyModal-1nOh6oN3.js").then((m) => ({ default: m.CookiesPolicyModal })));
|
|
1830
|
+
const CreateDocumentModal$1 = lazy(() => import("./CreateDocumentModal-nQbLVDq3.js").then((m) => ({ default: m.CreateDocumentModal })));
|
|
1831
|
+
const DebugSettingsModal = lazy(() => import("./DebugSettingsModal-CJunSkZB.js").then((m) => ({ default: m.DebugSettingsModal })));
|
|
1832
|
+
const DeleteDriveModal = lazy(() => import("./DeleteDriveModal-6P0T1QgD.js").then((m) => ({ default: m.DeleteDriveModal })));
|
|
1833
|
+
const DeleteItemModal = lazy(() => import("./DeleteItemModal-BmV1aQyZ.js").then((m) => ({ default: m.DeleteItemModal })));
|
|
1834
|
+
const DisclaimerModal = lazy(() => import("./DisclaimerModal-CVTFfN0_.js").then((m) => ({ default: m.DisclaimerModal })));
|
|
1835
|
+
const DriveSettingsModal$1 = lazy(() => import("./DriveSettingsModal-L5sM-rIh.js").then((m) => ({ default: m.DriveSettingsModal })));
|
|
1836
|
+
const DownloadDocumentWithErrorsModal = lazy(() => import("./DownloadDocumentWithErrorsModal-CXeFUtaz.js").then((m) => ({ default: m.DownloadDocumentWithErrorsModal })));
|
|
1837
|
+
const SettingsModal$1 = lazy(() => import("./SettingsModal-Dz3u5m_G.js").then((m) => ({ default: m.SettingsModal })));
|
|
1838
|
+
const UpgradeDriveModal = lazy(() => import("./UpgradeDriveModal-CyAUDCU7.js").then((m) => ({ default: m.UpgradeDriveModal })));
|
|
1839
|
+
const modalComponents = {
|
|
1840
|
+
addDrive: AddDriveModal$1,
|
|
1841
|
+
clearStorage: ClearStorageModal,
|
|
1842
|
+
cookiesPolicy: CookiesPolicyModal,
|
|
1843
|
+
createDocument: CreateDocumentModal$1,
|
|
1844
|
+
debugSettings: DebugSettingsModal,
|
|
1845
|
+
deleteDrive: DeleteDriveModal,
|
|
1846
|
+
deleteItem: DeleteItemModal,
|
|
1847
|
+
disclaimer: DisclaimerModal,
|
|
1848
|
+
driveSettings: DriveSettingsModal$1,
|
|
1849
|
+
downloadDocumentWithErrors: DownloadDocumentWithErrorsModal,
|
|
1850
|
+
inspector: lazy(() => import("./InspectorModal-C7pIf6OO.js").then((m) => ({ default: m.InspectorModal }))),
|
|
1851
|
+
settings: SettingsModal$1,
|
|
1852
|
+
upgradeDrive: UpgradeDriveModal,
|
|
1853
|
+
missingPackage: lazy(() => import("./MissingPackageModal-D_N96M6a.js").then((m) => ({ default: m.ConnectMissingPackageModal })))
|
|
1854
|
+
};
|
|
1855
|
+
const ModalsContainer = lazy(() => {
|
|
1856
|
+
return Promise.resolve({ default: () => {
|
|
1857
|
+
const phModal = usePHModal();
|
|
1858
|
+
if (!phModal?.type) return null;
|
|
1859
|
+
const ModalComponent = modalComponents[phModal.type];
|
|
1860
|
+
return ModalComponent ? /* @__PURE__ */ jsx(ErrorBoundary$1, {
|
|
1861
|
+
variant: "silent",
|
|
1862
|
+
loggerContext: ["Connect", "Modals"],
|
|
1863
|
+
children: /* @__PURE__ */ jsx(Suspense, {
|
|
1864
|
+
fallback: null,
|
|
1865
|
+
children: /* @__PURE__ */ jsx(ModalComponent, {})
|
|
1866
|
+
})
|
|
1867
|
+
}) : null;
|
|
1868
|
+
} });
|
|
1869
|
+
});
|
|
1870
|
+
//#endregion
|
|
1871
|
+
//#region src/components/app-loader.tsx
|
|
1872
|
+
const AppLoader = (props) => {
|
|
1873
|
+
const Load = lazy(() => import("./load-DalJBk1C.js").then((m) => m.loadComponent(props.localPackage)));
|
|
1874
|
+
return /* @__PURE__ */ jsx(StrictMode, { children: /* @__PURE__ */ jsxs(ErrorBoundary$1, {
|
|
1875
|
+
fallbackRender: (props) => /* @__PURE__ */ jsx(AppSkeleton, { children: /* @__PURE__ */ jsx(DetailedFallback, { ...props }) }),
|
|
1876
|
+
resetKeys: [props.localPackage],
|
|
1877
|
+
loggerContext: ["Connect"],
|
|
1878
|
+
children: [
|
|
1879
|
+
/* @__PURE__ */ jsx(Suspense, {
|
|
1880
|
+
fallback: /* @__PURE__ */ jsx(AppSkeleton, {}),
|
|
1881
|
+
name: "AppLoader",
|
|
1882
|
+
children: /* @__PURE__ */ jsx(Load, {
|
|
1883
|
+
...props,
|
|
1884
|
+
children: /* @__PURE__ */ jsx(App, {})
|
|
1885
|
+
})
|
|
1886
|
+
}),
|
|
1887
|
+
/* @__PURE__ */ jsx(Suspense, {
|
|
1888
|
+
name: "CookieBanner",
|
|
1889
|
+
children: /* @__PURE__ */ jsx(CookieBanner$1, {})
|
|
1890
|
+
}),
|
|
1891
|
+
/* @__PURE__ */ jsx(Suspense, {
|
|
1892
|
+
name: "ModalsContainer",
|
|
1893
|
+
children: /* @__PURE__ */ jsx(ModalsContainer, {})
|
|
1894
|
+
}),
|
|
1895
|
+
/* @__PURE__ */ jsx(MigrationBanner, {})
|
|
1896
|
+
]
|
|
1897
|
+
}) });
|
|
1898
|
+
};
|
|
1899
|
+
//#endregion
|
|
1900
|
+
export { DocumentEditor as a, App as c, DocumentEditorContainer as d, OpenPanel as f, Root as i, AppContainer as l, AppSkeleton as m, Sidebar as n, DriveIcon as o, Analytics as p, Router as r, CookieBanner$1 as s, AppLoader as t, EditorLoader as u };
|
|
1861
1901
|
|
|
1862
|
-
//# sourceMappingURL=
|
|
1863
|
-
//# debugId=
|
|
1902
|
+
//# sourceMappingURL=app-loader-CCPp_d0K.js.map
|
|
1903
|
+
//# debugId=ce32a32d-843f-5968-a8c8-3c3a319dc387
|