From b3f67a539869d4733739034789b586ac87b65fd7 Mon Sep 17 00:00:00 2001 From: 3252a8 <3252a8@proton.me> Date: Thu, 21 May 2026 10:43:55 +0300 Subject: [PATCH] fix: admin panel loading --- frontend/src/App.svelte | 100 +++++++++++++++++++++++----------- frontend/src/adminEntry.js | 38 ++++++++++++- frontend/src/styles-admin.css | 4 ++ frontend/vite.config.mjs | 7 ++- 4 files changed, 116 insertions(+), 33 deletions(-) diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte index 9e2bff3..59b5c95 100644 --- a/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -119,9 +119,12 @@ let scrollLockApplied = false; let adminI18nLoaded = false; let adminI18nPromise = null; - let AdminPanelComponent = null; + let adminBundleApi = null; let adminBundlePromise = null; let adminBundleError = ""; + let adminMountTarget = null; + let adminMountHandle = null; + let adminPanelProps = {}; let tg = null; const telegramSdk = createTelegramSdk({ scriptUrl: TELEGRAM_WEBAPP_SCRIPT_URL, @@ -527,6 +530,7 @@ supportStore.closePolling(); clearLanguageClickGuard(); syncBodyScrollLock(false); + destroyAdminMount(); }; }); @@ -647,13 +651,18 @@ }); } + function readAdminBundleApi() { + const bundle = window.SubscriptionWebAppAdmin; + return bundle?.mount ? bundle : null; + } + async function ensureAdminBundle() { - if (AdminPanelComponent) return true; + if (adminBundleApi) return true; if (adminBundlePromise) return adminBundlePromise; - const existing = window.SubscriptionWebAppAdminPanel; + const existing = readAdminBundleApi(); if (existing) { - AdminPanelComponent = existing.default || existing; + adminBundleApi = existing; return true; } @@ -663,9 +672,9 @@ const jsSrc = resolveWebappAssetPath(CFG.adminJsAsset, "subscription_webapp_admin.js"); await appendStylesheetOnce("subscription-webapp-admin-css", cssHref); await appendScriptOnce("subscription-webapp-admin-js", jsSrc); - const loaded = window.SubscriptionWebAppAdminPanel; - if (!loaded) throw new Error("admin_bundle_missing_component"); - AdminPanelComponent = loaded.default || loaded; + const loaded = readAdminBundleApi(); + if (!loaded) throw new Error("admin_bundle_missing_mount"); + adminBundleApi = loaded; return true; })() .catch((error) => { @@ -679,6 +688,57 @@ return adminBundlePromise; } + function destroyAdminMount() { + if (!adminMountHandle) return; + adminMountHandle.destroy?.(); + adminMountHandle = null; + } + + $: adminPanelProps = { + api, + onClose: closeAdminPanel, + onToast: (text) => showToast(text), + initialSection: adminSectionFromPath(window.location.pathname), + initialUserId: adminUserIdFromPath(window.location.pathname), + onSectionChange: handleAdminSectionChange, + onSettingsSaved: handleAdminPersistedSaved, + onTariffsSaved: handleAdminPersistedSaved, + onThemesSaved: handleAdminPersistedSaved, + brandTitle, + brand, + appFaviconUrl: CFG.faviconUrl, + appFaviconUseCustom: CFG.faviconUseCustom, + appVersion: CFG.appVersion, + appRepositoryUrl: CFG.appRepositoryUrl, + currentLang, + languageOptions, + languageBusy, + onLanguageChange: accountStore.updateAccountLanguage, + t, + }; + + $: { + const shouldMountAdmin = screen === "admin" && isAdmin && adminBundleApi && adminMountTarget; + const props = adminPanelProps; + + if (shouldMountAdmin) { + try { + if (adminMountHandle) { + adminMountHandle.update?.(props); + } else { + adminMountTarget.replaceChildren(); + adminMountHandle = adminBundleApi.mount(adminMountTarget, props); + } + } catch (error) { + adminBundleError = error?.message || "admin_bundle_mount_failed"; + adminBundleApi = null; + destroyAdminMount(); + } + } else { + destroyAdminMount(); + } + } + async function boot() { await runWebappBoot({ MOCK, @@ -1221,30 +1281,8 @@ setPasswordLoginMode={(enabled) => setPasswordLoginMode(enabled)} /> {:else if screen === "admin" && isAdmin} - {#if AdminPanelComponent} - showToast(text)} - initialSection={adminSectionFromPath(window.location.pathname)} - initialUserId={adminUserIdFromPath(window.location.pathname)} - onSectionChange={handleAdminSectionChange} - onSettingsSaved={handleAdminPersistedSaved} - onTariffsSaved={handleAdminPersistedSaved} - onThemesSaved={handleAdminPersistedSaved} - {brandTitle} - {brand} - appFaviconUrl={CFG.faviconUrl} - appFaviconUseCustom={CFG.faviconUseCustom} - appVersion={CFG.appVersion} - appRepositoryUrl={CFG.appRepositoryUrl} - {currentLang} - {languageOptions} - {languageBusy} - onLanguageChange={accountStore.updateAccountLanguage} - {t} - /> + {#if adminBundleApi} +
{:else}
diff --git a/frontend/src/adminEntry.js b/frontend/src/adminEntry.js index 22f2a85..1849b2f 100644 --- a/frontend/src/adminEntry.js +++ b/frontend/src/adminEntry.js @@ -1,9 +1,45 @@ +import { mount, unmount } from "svelte"; + import AdminPanel from "./admin/AdminPanel.svelte"; import "./styles-admin.css"; +function mountAdminPanel(target, props = {}) { + if (!target) throw new Error("admin_mount_target_missing"); + + const currentProps = { ...props }; + const instance = mount(AdminPanel, { target, props: currentProps }); + let destroyed = false; + + return { + update(nextProps = {}) { + if (destroyed) return; + Object.assign(currentProps, nextProps); + + if (typeof instance.$set === "function") { + instance.$set(nextProps); + return; + } + + for (const [key, value] of Object.entries(nextProps)) { + if (key in instance) instance[key] = value; + } + }, + destroy() { + if (destroyed) return; + destroyed = true; + void unmount(instance); + target.replaceChildren(); + }, + }; +} + +window.SubscriptionWebAppAdmin = { + AdminPanel, + mount: mountAdminPanel, +}; window.SubscriptionWebAppAdminPanel = AdminPanel; window.dispatchEvent( new CustomEvent("subscription-webapp-admin-ready", { - detail: { AdminPanel }, + detail: window.SubscriptionWebAppAdmin, }) ); diff --git a/frontend/src/styles-admin.css b/frontend/src/styles-admin.css index b8232ce..04c672d 100644 --- a/frontend/src/styles-admin.css +++ b/frontend/src/styles-admin.css @@ -1,3 +1,7 @@ @import "./styles/admin.css"; @import "./styles/admin-controls.css"; @import "./styles/admin-dialogs.css"; + +.admin-mount { + display: contents; +} diff --git a/frontend/vite.config.mjs b/frontend/vite.config.mjs index bcd316b..c7324b3 100644 --- a/frontend/vite.config.mjs +++ b/frontend/vite.config.mjs @@ -19,7 +19,12 @@ export default defineConfig(({ mode }) => { $components: path.resolve(__dirname, "src/lib/components"), }, }, - plugins: [tailwindcss(), svelte()], + plugins: [ + tailwindcss(), + svelte({ + compilerOptions: isAdminBuild ? { compatibility: { componentApi: 4 } } : {}, + }), + ], build: { outDir: templateDir, emptyOutDir: false,