mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2026-09-03 05:10:16 +03:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bce4d2bd04 | ||
|
|
384e200663 |
@@ -1,4 +1,5 @@
|
||||
import React, {
|
||||
Suspense,
|
||||
useMemo,
|
||||
useState,
|
||||
useEffect,
|
||||
@@ -270,6 +271,7 @@ const AppConfigModalInner: React.FC<AppConfigModalProps> = ({
|
||||
withCloseButton={false}
|
||||
zIndex={Z_INDEX_CONFIG_MODAL}
|
||||
overlayProps={{ opacity: 0.35, blur: 2 }}
|
||||
transitionProps={{ duration: 0 }}
|
||||
padding={0}
|
||||
fullScreen={isMobile}
|
||||
styles={{ content: { overflowY: "hidden", overscrollBehavior: "none" } }}
|
||||
@@ -429,7 +431,11 @@ const AppConfigModalInner: React.FC<AppConfigModalProps> = ({
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
</div>
|
||||
<div className="modal-body">{activeComponent}</div>
|
||||
<div className="modal-body">
|
||||
<Suspense fallback={<div className="modal-section-loading" />}>
|
||||
{activeComponent}
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Loader } from "@mantine/core";
|
||||
import { Suspense, lazy, useEffect, useState } from "react";
|
||||
import type {
|
||||
ConfigNavSection,
|
||||
@@ -5,11 +6,32 @@ import type {
|
||||
} from "@app/components/shared/config/types";
|
||||
|
||||
// AppConfigModal pulls in the entire settings UI tree (admin sections,
|
||||
// account, supabase auth flows, etc.). We defer loading until the user first
|
||||
// opens the modal, then keep it mounted so the close animation runs.
|
||||
const AppConfigModal = lazy(
|
||||
() => import("@app/components/shared/AppConfigModal"),
|
||||
);
|
||||
// account, supabase auth flows, etc.). Keep the import deferred, but share the
|
||||
// promise so callers can preload it before the modal is opened.
|
||||
let appConfigModalPromise: Promise<
|
||||
typeof import("@app/components/shared/AppConfigModal")
|
||||
> | null = null;
|
||||
let generalSectionPromise: Promise<
|
||||
typeof import("@app/components/shared/config/configSections/GeneralSection")
|
||||
> | null = null;
|
||||
|
||||
const loadAppConfigModal = () => {
|
||||
appConfigModalPromise ??= import("@app/components/shared/AppConfigModal");
|
||||
return appConfigModalPromise;
|
||||
};
|
||||
|
||||
const loadGeneralSection = () => {
|
||||
generalSectionPromise ??=
|
||||
import("@app/components/shared/config/configSections/GeneralSection");
|
||||
return generalSectionPromise;
|
||||
};
|
||||
|
||||
const AppConfigModal = lazy(loadAppConfigModal);
|
||||
|
||||
export function preloadAppConfigModal() {
|
||||
void loadAppConfigModal();
|
||||
void loadGeneralSection();
|
||||
}
|
||||
|
||||
interface AppConfigModalLazyProps {
|
||||
opened: boolean;
|
||||
@@ -34,12 +56,42 @@ export default function AppConfigModalLazy({
|
||||
}: AppConfigModalLazyProps) {
|
||||
const [shouldMount, setShouldMount] = useState(false);
|
||||
|
||||
// Settings is opened frequently from the persistent sidebar. Warm the two
|
||||
// chunks needed for the initial screen after the main page has painted, so
|
||||
// the first click does not have to wait for module evaluation.
|
||||
useEffect(() => {
|
||||
if (opened) setShouldMount(true);
|
||||
const timer = window.setTimeout(preloadAppConfigModal, 500);
|
||||
return () => window.clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// Unmount the settings tree immediately on close. Keeping the hidden tree
|
||||
// alive makes Mantine's overlay transition re-layout the entire settings
|
||||
// page and blocks the close click on large admin builds.
|
||||
setShouldMount(opened);
|
||||
}, [opened]);
|
||||
|
||||
return (
|
||||
<Suspense fallback={null}>
|
||||
<Suspense
|
||||
fallback={
|
||||
opened ? (
|
||||
<div
|
||||
role="status"
|
||||
aria-label="Loading settings"
|
||||
style={{
|
||||
position: "fixed",
|
||||
inset: 0,
|
||||
display: "grid",
|
||||
placeItems: "center",
|
||||
zIndex: 1300,
|
||||
background: "var(--c-overlay, rgba(0, 0, 0, 0.35))",
|
||||
}}
|
||||
>
|
||||
<Loader size="sm" />
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
>
|
||||
{shouldMount && (
|
||||
<AppConfigModal
|
||||
opened={opened}
|
||||
|
||||
@@ -1,18 +1,39 @@
|
||||
import React from "react";
|
||||
import React, { lazy } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import HotkeysSection from "@app/components/shared/config/configSections/HotkeysSection";
|
||||
import GeneralSection from "@app/components/shared/config/configSections/GeneralSection";
|
||||
import HelpSection from "@app/components/shared/config/configSections/HelpSection";
|
||||
import LegalSection from "@app/components/shared/config/configSections/LegalSection";
|
||||
import {
|
||||
BackendThirdPartyLicensesSection,
|
||||
FrontendThirdPartyLicensesSection,
|
||||
} from "@app/components/shared/config/configSections/ThirdPartyLicensesSection";
|
||||
import type {
|
||||
ConfigNavItem,
|
||||
ConfigNavSection,
|
||||
} from "@app/components/shared/config/types";
|
||||
|
||||
// Keep the modal shell light. Settings sections are independent screens and
|
||||
// do not need to be evaluated just to build the navigation list.
|
||||
const HotkeysSection = lazy(
|
||||
() => import("@app/components/shared/config/configSections/HotkeysSection"),
|
||||
);
|
||||
const GeneralSection = lazy(
|
||||
() => import("@app/components/shared/config/configSections/GeneralSection"),
|
||||
);
|
||||
const HelpSection = lazy(
|
||||
() => import("@app/components/shared/config/configSections/HelpSection"),
|
||||
);
|
||||
const LegalSection = lazy(
|
||||
() => import("@app/components/shared/config/configSections/LegalSection"),
|
||||
);
|
||||
const BackendThirdPartyLicensesSection = lazy(() =>
|
||||
import("@app/components/shared/config/configSections/ThirdPartyLicensesSection").then(
|
||||
({ BackendThirdPartyLicensesSection: Component }) => ({
|
||||
default: Component,
|
||||
}),
|
||||
),
|
||||
);
|
||||
const FrontendThirdPartyLicensesSection = lazy(() =>
|
||||
import("@app/components/shared/config/configSections/ThirdPartyLicensesSection").then(
|
||||
({ FrontendThirdPartyLicensesSection: Component }) => ({
|
||||
default: Component,
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
// Re-exported for the many existing importers; the definitions live in
|
||||
// config/types so type-only consumers don't pull the section tree in.
|
||||
export type { ConfigNavItem, ConfigNavSection };
|
||||
|
||||
@@ -26,7 +26,9 @@ import Workbench from "@app/components/layout/Workbench";
|
||||
import FileSidebar from "@app/components/shared/FileSidebar";
|
||||
import FileManager from "@app/components/FileManager";
|
||||
import LocalIcon from "@app/components/shared/LocalIcon";
|
||||
import AppConfigModal from "@app/components/shared/AppConfigModalLazy";
|
||||
import AppConfigModal, {
|
||||
preloadAppConfigModal,
|
||||
} from "@app/components/shared/AppConfigModalLazy";
|
||||
import { getStartupNavigationAction } from "@app/utils/homePageNavigation";
|
||||
import { EDITOR_BASENAME } from "@app/routes/editorBasename";
|
||||
import { HomePageExtensions } from "@app/components/home/HomePageExtensions";
|
||||
@@ -91,7 +93,10 @@ export default function HomePage() {
|
||||
const sliderRef = useRef<HTMLDivElement | null>(null);
|
||||
const [activeMobileView, setActiveMobileView] = useState<MobileView>("tools");
|
||||
const isProgrammaticScroll = useRef(false);
|
||||
const [configModalOpen, setConfigModalOpen] = useState(false);
|
||||
const openConfigModal = useCallback(() => {
|
||||
preloadAppConfigModal();
|
||||
window.dispatchEvent(new Event("appConfig:open"));
|
||||
}, []);
|
||||
const location = useLocation();
|
||||
// Persisted user preference for the FileSidebar collapsed state. Auto-
|
||||
// collapse on /files is layered on top in the transition effect below and
|
||||
@@ -476,7 +481,7 @@ export default function HomePage() {
|
||||
variant="tertiary"
|
||||
className="mobile-bottom-button"
|
||||
aria-label={t("quickAccess.config", "Config")}
|
||||
onClick={() => setConfigModalOpen(true)}
|
||||
onClick={openConfigModal}
|
||||
>
|
||||
<LocalIcon
|
||||
icon="settings-rounded"
|
||||
@@ -489,10 +494,7 @@ export default function HomePage() {
|
||||
</Button>
|
||||
</div>
|
||||
<FileManager selectedTool={selectedTool} />
|
||||
<AppConfigModal
|
||||
opened={configModalOpen}
|
||||
onClose={handleCloseConfig}
|
||||
/>
|
||||
<AppConfigModalHost />
|
||||
</div>
|
||||
) : (
|
||||
<Group
|
||||
@@ -533,16 +535,13 @@ export default function HomePage() {
|
||||
return next;
|
||||
});
|
||||
}}
|
||||
onOpenSettings={() => setConfigModalOpen(true)}
|
||||
onOpenSettings={openConfigModal}
|
||||
/>
|
||||
<FolderTreePanel active={navigationState.workbench === "myFiles"} />
|
||||
<Workbench />
|
||||
{!hideToolPanel && <RightSidebar />}
|
||||
<FileManager selectedTool={selectedTool} />
|
||||
<AppConfigModal
|
||||
opened={configModalOpen}
|
||||
onClose={handleCloseConfig}
|
||||
/>
|
||||
<AppConfigModalHost />
|
||||
</Group>
|
||||
)}
|
||||
</FilesPageProvider>
|
||||
@@ -550,6 +549,37 @@ export default function HomePage() {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Keeps settings visibility state out of HomePage. The editor page is a large
|
||||
* tree, so toggling the modal must not cause the whole workbench to render.
|
||||
*/
|
||||
function AppConfigModalHost() {
|
||||
const [opened, setOpened] = useState(false);
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
const handleOpen = () => setOpened(true);
|
||||
window.addEventListener("appConfig:open", handleOpen);
|
||||
return () => window.removeEventListener("appConfig:open", handleOpen);
|
||||
}, []);
|
||||
|
||||
// Open the config modal whenever the URL is /settings/* (e.g. from the
|
||||
// admin tour's openConfigModal action which navigates to /settings/overview).
|
||||
useEffect(() => {
|
||||
if (location.pathname.startsWith("/settings")) setOpened(true);
|
||||
}, [location.pathname]);
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
setOpened(false);
|
||||
if (location.pathname.startsWith("/settings")) {
|
||||
navigate("/", { replace: true });
|
||||
}
|
||||
}, [location.pathname, navigate]);
|
||||
|
||||
return <AppConfigModal opened={opened} onClose={handleClose} />;
|
||||
}
|
||||
|
||||
interface MyFilesAwareFileSidebarProps extends FileSidebarProps {
|
||||
active: boolean;
|
||||
}
|
||||
|
||||
+5
-2
@@ -1,8 +1,11 @@
|
||||
import type { ComponentProps } from "react";
|
||||
import { lazy, type ComponentProps } from "react";
|
||||
import { Stack } from "@mantine/core";
|
||||
import GeneralSection from "@app/components/shared/config/configSections/GeneralSection";
|
||||
import { LoginLandingSetting } from "@app/components/shared/config/LoginLandingSetting";
|
||||
|
||||
const GeneralSection = lazy(
|
||||
() => import("@app/components/shared/config/configSections/GeneralSection"),
|
||||
);
|
||||
|
||||
type GeneralSectionProps = ComponentProps<typeof GeneralSection>;
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,34 +1,100 @@
|
||||
import React from "react";
|
||||
import React, { lazy } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
useConfigNavSections as useCoreConfigNavSections,
|
||||
ConfigNavSection,
|
||||
} from "@core/components/shared/config/configNavSections";
|
||||
import PeopleSection from "@app/components/shared/config/configSections/PeopleSection";
|
||||
import TeamsSection from "@app/components/shared/config/configSections/TeamsSection";
|
||||
import AdminGeneralSection from "@app/components/shared/config/configSections/AdminGeneralSection";
|
||||
import AdminSecuritySection from "@app/components/shared/config/configSections/AdminSecuritySection";
|
||||
import AdminConnectionsSection from "@app/components/shared/config/configSections/AdminConnectionsSection";
|
||||
import AdminPrivacySection from "@app/components/shared/config/configSections/AdminPrivacySection";
|
||||
import AdminDatabaseSection from "@app/components/shared/config/configSections/AdminDatabaseSection";
|
||||
import AdminAdvancedSection from "@app/components/shared/config/configSections/AdminAdvancedSection";
|
||||
import AdminLegalSection from "@app/components/shared/config/configSections/AdminLegalSection";
|
||||
import AdminPlanSection from "@app/components/shared/config/configSections/AdminPlanSection";
|
||||
import AdminFeaturesSection from "@app/components/shared/config/configSections/AdminFeaturesSection";
|
||||
import AdminEndpointsSection from "@app/components/shared/config/configSections/AdminEndpointsSection";
|
||||
import AdminMcpSection from "@app/components/shared/config/configSections/AdminMcpSection";
|
||||
import AdminAiGeneralSection from "@app/components/shared/config/configSections/AdminAiGeneralSection";
|
||||
import AdminAiModelsSection from "@app/components/shared/config/configSections/AdminAiModelsSection";
|
||||
import AdminAiDocumentsSection from "@app/components/shared/config/configSections/AdminAiDocumentsSection";
|
||||
import AdminAiLimitsSection from "@app/components/shared/config/configSections/AdminAiLimitsSection";
|
||||
import AdminAuditSection from "@app/components/shared/config/configSections/AdminAuditSection";
|
||||
import AdminUsageSection from "@app/components/shared/config/configSections/AdminUsageSection";
|
||||
import AdminStorageSharingSection from "@app/components/shared/config/configSections/AdminStorageSharingSection";
|
||||
import AdminFolderAccessSection from "@app/components/shared/config/configSections/AdminFolderAccessSection";
|
||||
import ApiKeys from "@app/components/shared/config/configSections/ApiKeys";
|
||||
import AccountSection from "@app/components/shared/config/configSections/AccountSection";
|
||||
import GeneralWithLoginLanding from "@app/components/shared/config/GeneralWithLoginLanding";
|
||||
|
||||
// These screens are only needed after selecting their navigation item. Keeping
|
||||
// them out of the initial settings render avoids evaluating the whole admin UI.
|
||||
const PeopleSection = lazy(
|
||||
() => import("@app/components/shared/config/configSections/PeopleSection"),
|
||||
);
|
||||
const TeamsSection = lazy(
|
||||
() => import("@app/components/shared/config/configSections/TeamsSection"),
|
||||
);
|
||||
const AdminGeneralSection = lazy(
|
||||
() =>
|
||||
import("@app/components/shared/config/configSections/AdminGeneralSection"),
|
||||
);
|
||||
const AdminSecuritySection = lazy(
|
||||
() =>
|
||||
import("@app/components/shared/config/configSections/AdminSecuritySection"),
|
||||
);
|
||||
const AdminConnectionsSection = lazy(
|
||||
() =>
|
||||
import("@app/components/shared/config/configSections/AdminConnectionsSection"),
|
||||
);
|
||||
const AdminPrivacySection = lazy(
|
||||
() =>
|
||||
import("@app/components/shared/config/configSections/AdminPrivacySection"),
|
||||
);
|
||||
const AdminDatabaseSection = lazy(
|
||||
() =>
|
||||
import("@app/components/shared/config/configSections/AdminDatabaseSection"),
|
||||
);
|
||||
const AdminAdvancedSection = lazy(
|
||||
() =>
|
||||
import("@app/components/shared/config/configSections/AdminAdvancedSection"),
|
||||
);
|
||||
const AdminLegalSection = lazy(
|
||||
() =>
|
||||
import("@app/components/shared/config/configSections/AdminLegalSection"),
|
||||
);
|
||||
const AdminPlanSection = lazy(
|
||||
() => import("@app/components/shared/config/configSections/AdminPlanSection"),
|
||||
);
|
||||
const AdminFeaturesSection = lazy(
|
||||
() =>
|
||||
import("@app/components/shared/config/configSections/AdminFeaturesSection"),
|
||||
);
|
||||
const AdminEndpointsSection = lazy(
|
||||
() =>
|
||||
import("@app/components/shared/config/configSections/AdminEndpointsSection"),
|
||||
);
|
||||
const AdminMcpSection = lazy(
|
||||
() => import("@app/components/shared/config/configSections/AdminMcpSection"),
|
||||
);
|
||||
const AdminAiGeneralSection = lazy(
|
||||
() =>
|
||||
import("@app/components/shared/config/configSections/AdminAiGeneralSection"),
|
||||
);
|
||||
const AdminAiModelsSection = lazy(
|
||||
() =>
|
||||
import("@app/components/shared/config/configSections/AdminAiModelsSection"),
|
||||
);
|
||||
const AdminAiDocumentsSection = lazy(
|
||||
() =>
|
||||
import("@app/components/shared/config/configSections/AdminAiDocumentsSection"),
|
||||
);
|
||||
const AdminAiLimitsSection = lazy(
|
||||
() =>
|
||||
import("@app/components/shared/config/configSections/AdminAiLimitsSection"),
|
||||
);
|
||||
const AdminAuditSection = lazy(
|
||||
() =>
|
||||
import("@app/components/shared/config/configSections/AdminAuditSection"),
|
||||
);
|
||||
const AdminUsageSection = lazy(
|
||||
() =>
|
||||
import("@app/components/shared/config/configSections/AdminUsageSection"),
|
||||
);
|
||||
const AdminStorageSharingSection = lazy(
|
||||
() =>
|
||||
import("@app/components/shared/config/configSections/AdminStorageSharingSection"),
|
||||
);
|
||||
const AdminFolderAccessSection = lazy(
|
||||
() =>
|
||||
import("@app/components/shared/config/configSections/AdminFolderAccessSection"),
|
||||
);
|
||||
const ApiKeys = lazy(
|
||||
() => import("@app/components/shared/config/configSections/ApiKeys"),
|
||||
);
|
||||
const AccountSection = lazy(
|
||||
() => import("@app/components/shared/config/configSections/AccountSection"),
|
||||
);
|
||||
|
||||
/**
|
||||
* Hook version of proprietary config nav sections with proper i18n support
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user