mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-16 11:23:10 +02:00
Restructure/frontend editor (#6404)
## Move editor under `frontend/editor/`
Pure restructure: `frontend/` becomes the workspace, `frontend/editor/`
holds
the PDF editor. 1775 file renames + 40 wiring edits. No logic changes.
### Why
`frontend/` is currently the editor — its `src/`, `public/`,
`src-tauri/`,
config files all sit at the root. Promoting `frontend/` to a
workspace and putting the editor in a sibling folder leaves room for
future
apps to drop in alongside it, sharing one `package.json` /
`node_modules` /
lint config / Storybook.
### What moves
frontend/
├── editor/ ← NEW: everything editor-specific
│ ├── src/ ← was frontend/src/
│ ├── public/ ← was frontend/public/
│ ├── src-tauri/ ← was frontend/src-tauri/
│ ├── index.html, vite.config.ts, vitest.config.ts, playwright.config.ts
│ ├── tsconfig*.json, tailwind.config.js, postcss.config.js
│ ├── scripts/
│ ├── .env, .env.desktop, .env.saas
│ └── DeveloperGuide.md
├── package.json, package-lock.json, node_modules/ ← workspace install
├── eslint.config.mjs, .prettierrc, .prettierignore ← shared tooling
├── .gitignore
└── README.md
### Wiring edits (40 files)
- `.taskfiles/frontend.yml`, `desktop.yml`, `e2e.yml`
- `build.gradle`, `app/core/build.gradle`
- `eslint.config.mjs`, `frontend/package.json`, `.gitignore`,
`.prettierignore`
- `docker/frontend/Dockerfile`
- 8 `.github/workflows/*.yml`, plus `.github/dependabot.yml`,
`.github/config/.files.yaml`, `.github/labeler-config-srvaroa.yml`
- `scripts/translations/**`
- Docs: `AGENTS.md`, `CLAUDE.md`, `ADDING_TOOLS.md`,
`DeveloperGuide.md`,
`WINDOWS_SIGNING.md`, `devGuide/HowToAddNewLanguage.md`,
`frontend/README.md`,
`frontend/editor/DeveloperGuide.md`
Plus 3 renamed + edited: `editor/vite.config.ts` (env path +
node_modules
walk-up), `editor/scripts/setup-env.mts` (renamed from `.ts` for
`import.meta.url`), `editor/scripts/build-provisioner.mjs` (resolve
src-tauri
relative to script).
### Verification
| Check | Result |
|---|---|
| `task frontend:typecheck:all` (6 variants) | exit 0 |
| `task frontend:lint` (eslint + dpdm) | exit 0 |
| `task frontend:format:check` | exit 0 |
| `task frontend:test` | 657 tests pass, 50 files |
| `task frontend:build:{core,proprietary,saas,desktop,prototypes}` | all
green |
| `task desktop:build` | full Tauri pipeline →
`Stirling-PDF_2.11.0_x64_en-US.msi` |
| `playwright test --list --project=stubbed` | 172 tests discovered |
`task desktop:build` exercises the heaviest path — Rust + WiX + MSI
bundle
against the moved `editor/src-tauri/`. If anything in the restructure
was
wrong it wouldn't have built.
### Test plan
- [ ] `frontend-validation.yml` green
- [ ] `e2e-stubbed.yml` green
- [ ] `tauri-build.yml` green on at least one platform
- [ ] `check_toml.yml` runs on a translation-touching PR
---------
Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
48027ee9d6
commit
0a50e765b7
@@ -0,0 +1,170 @@
|
||||
.mobile-layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: var(--bg-background);
|
||||
}
|
||||
|
||||
.mobile-toggle {
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
background: var(--bg-toolbar);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
|
||||
.mobile-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.mobile-brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.mobile-brand-icon {
|
||||
height: 1.5rem;
|
||||
width: auto;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.mobile-brand-text {
|
||||
height: 1.5rem;
|
||||
width: auto;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.mobile-toggle-buttons {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 0.25rem;
|
||||
padding: 0.2rem;
|
||||
border-radius: 9999px;
|
||||
background: var(--bg-background);
|
||||
border: 1px solid var(--border-subtle);
|
||||
}
|
||||
|
||||
.mobile-toggle-button {
|
||||
border: none;
|
||||
border-radius: 9999px;
|
||||
padding: 0.4rem 0.9rem;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-muted);
|
||||
background: transparent;
|
||||
transition:
|
||||
background 0.2s ease,
|
||||
color 0.2s ease;
|
||||
}
|
||||
|
||||
.mobile-toggle-button:focus-visible {
|
||||
outline: 2px solid var(--primary-color, #228be6);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.mobile-toggle-button.active {
|
||||
background: var(--primary-surface, rgba(34, 139, 230, 0.12));
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.mobile-toggle-hint {
|
||||
font-size: 0.7rem;
|
||||
color: var(--text-muted);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.mobile-slider {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
scroll-snap-type: x mandatory;
|
||||
scroll-behavior: smooth;
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
touch-action: pan-x pinch-zoom;
|
||||
}
|
||||
|
||||
.mobile-slider::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mobile-slide {
|
||||
flex: 0 0 100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
scroll-snap-align: start;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.mobile-slide-content {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.mobile-slide-content > * {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.mobile-bottom-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
padding: 0.5rem;
|
||||
border-top: 1px solid var(--border-subtle);
|
||||
background: var(--bg-toolbar);
|
||||
gap: 0.5rem;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
.mobile-bottom-button {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.25rem;
|
||||
padding: 0.5rem;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--text-primary);
|
||||
cursor: pointer;
|
||||
border-radius: 0.5rem;
|
||||
transition: background 0.2s ease;
|
||||
touch-action: manipulation;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
min-height: 44px;
|
||||
}
|
||||
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
.mobile-bottom-button:hover {
|
||||
background: var(--bg-hover, rgba(0, 0, 0, 0.05));
|
||||
}
|
||||
}
|
||||
|
||||
.mobile-bottom-button:active {
|
||||
background: var(--bg-active, rgba(0, 0, 0, 0.1));
|
||||
}
|
||||
|
||||
.mobile-bottom-button-label {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
@@ -0,0 +1,394 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useToolWorkflow } from "@app/contexts/ToolWorkflowContext";
|
||||
import { Group } from "@mantine/core";
|
||||
import { useSidebarContext } from "@app/contexts/SidebarContext";
|
||||
import { useDocumentMeta } from "@app/hooks/useDocumentMeta";
|
||||
import { useBaseUrl } from "@app/hooks/useBaseUrl";
|
||||
import { useIsMobile } from "@app/hooks/useIsMobile";
|
||||
import { useAppConfig } from "@app/contexts/AppConfigContext";
|
||||
import { LogoIcon } from "@app/components/shared/LogoIcon";
|
||||
import { Wordmark } from "@app/components/shared/Wordmark";
|
||||
import { useFileContext } from "@app/contexts/file/fileHooks";
|
||||
import {
|
||||
useNavigationState,
|
||||
useNavigationActions,
|
||||
} from "@app/contexts/NavigationContext";
|
||||
import { useViewer } from "@app/contexts/ViewerContext";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import AppsIcon from "@mui/icons-material/AppsRounded";
|
||||
|
||||
import ToolPanel from "@app/components/tools/ToolPanel";
|
||||
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 { useFilesModalContext } from "@app/contexts/FilesModalContext";
|
||||
import AppConfigModal from "@app/components/shared/AppConfigModalLazy";
|
||||
import { getStartupNavigationAction } from "@app/utils/homePageNavigation";
|
||||
import { HomePageExtensions } from "@app/components/home/HomePageExtensions";
|
||||
|
||||
import "@app/pages/HomePage.css";
|
||||
|
||||
type MobileView = "tools" | "workbench";
|
||||
|
||||
export default function HomePage() {
|
||||
const { t } = useTranslation();
|
||||
const { sidebarRefs } = useSidebarContext();
|
||||
|
||||
const { quickAccessRef } = sidebarRefs;
|
||||
|
||||
const {
|
||||
selectedTool,
|
||||
selectedToolKey,
|
||||
handleToolSelect,
|
||||
handleBackToTools,
|
||||
readerMode,
|
||||
setLeftPanelView,
|
||||
toolAvailability,
|
||||
customWorkbenchViews,
|
||||
} = useToolWorkflow();
|
||||
|
||||
const { openFilesModal } = useFilesModalContext();
|
||||
const { config } = useAppConfig();
|
||||
const isMobile = useIsMobile();
|
||||
const sliderRef = useRef<HTMLDivElement | null>(null);
|
||||
const [activeMobileView, setActiveMobileView] = useState<MobileView>("tools");
|
||||
const isProgrammaticScroll = useRef(false);
|
||||
const [configModalOpen, setConfigModalOpen] = useState(false);
|
||||
const [fileSidebarCollapsed, setFileSidebarCollapsed] = useState(false);
|
||||
const location = useLocation();
|
||||
|
||||
// Open the config modal whenever the URL is /settings/* (e.g. from the admin
|
||||
// tour's openConfigModal action which navigates to /settings/overview).
|
||||
useEffect(() => {
|
||||
const isSettings = location.pathname.startsWith("/settings");
|
||||
setConfigModalOpen(isSettings);
|
||||
}, [location.pathname]);
|
||||
|
||||
const { activeFiles } = useFileContext();
|
||||
const navigationState = useNavigationState();
|
||||
const { actions } = useNavigationActions();
|
||||
const { setActiveFileIndex } = useViewer();
|
||||
const prevFileCountRef = useRef(activeFiles.length);
|
||||
|
||||
// Startup/open transition behavior:
|
||||
// - opening exactly 1 file from empty -> viewer (unless already in fileEditor)
|
||||
// - opening 2+ files from empty -> fileEditor
|
||||
useEffect(() => {
|
||||
const prevCount = prevFileCountRef.current;
|
||||
const currentCount = activeFiles.length;
|
||||
|
||||
console.log("[HomePage] Navigation effect triggered:", {
|
||||
prevCount,
|
||||
currentCount,
|
||||
currentWorkbench: navigationState.workbench,
|
||||
selectedToolKey,
|
||||
});
|
||||
|
||||
const action = getStartupNavigationAction(
|
||||
prevCount,
|
||||
currentCount,
|
||||
selectedToolKey,
|
||||
navigationState.workbench,
|
||||
);
|
||||
|
||||
console.log("[HomePage] Navigation action returned:", action);
|
||||
|
||||
if (action) {
|
||||
console.log("[HomePage] Applying navigation:", action);
|
||||
actions.setWorkbench(action.workbench);
|
||||
if (typeof action.activeFileIndex === "number") {
|
||||
setActiveFileIndex(action.activeFileIndex);
|
||||
}
|
||||
} else {
|
||||
console.log("[HomePage] No navigation - staying in current workbench");
|
||||
}
|
||||
|
||||
prevFileCountRef.current = currentCount;
|
||||
}, [
|
||||
activeFiles.length,
|
||||
actions,
|
||||
setActiveFileIndex,
|
||||
selectedToolKey,
|
||||
navigationState.workbench,
|
||||
]);
|
||||
|
||||
const hideToolPanel =
|
||||
customWorkbenchViews.find(
|
||||
(v) => v.workbenchId === navigationState.workbench,
|
||||
)?.hideToolPanel ?? false;
|
||||
|
||||
const brandAltText = t("home.mobile.brandAlt", "Stirling PDF logo");
|
||||
|
||||
const handleSelectMobileView = useCallback((view: MobileView) => {
|
||||
setActiveMobileView(view);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (isMobile) {
|
||||
const container = sliderRef.current;
|
||||
if (container) {
|
||||
isProgrammaticScroll.current = true;
|
||||
const offset = activeMobileView === "tools" ? 0 : container.offsetWidth;
|
||||
container.scrollTo({ left: offset, behavior: "smooth" });
|
||||
|
||||
// Re-enable scroll listener after animation completes
|
||||
setTimeout(() => {
|
||||
isProgrammaticScroll.current = false;
|
||||
}, 500);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
setActiveMobileView("tools");
|
||||
const container = sliderRef.current;
|
||||
if (container) {
|
||||
container.scrollTo({ left: 0, behavior: "auto" });
|
||||
}
|
||||
}, [activeMobileView, isMobile]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isMobile) return;
|
||||
|
||||
const container = sliderRef.current;
|
||||
if (!container) return;
|
||||
|
||||
let animationFrame = 0;
|
||||
|
||||
const handleScroll = () => {
|
||||
if (isProgrammaticScroll.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (animationFrame) {
|
||||
cancelAnimationFrame(animationFrame);
|
||||
}
|
||||
|
||||
animationFrame = window.requestAnimationFrame(() => {
|
||||
const { scrollLeft, offsetWidth } = container;
|
||||
const threshold = offsetWidth / 2;
|
||||
const nextView: MobileView =
|
||||
scrollLeft >= threshold ? "workbench" : "tools";
|
||||
setActiveMobileView((current) =>
|
||||
current === nextView ? current : nextView,
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
container.addEventListener("scroll", handleScroll, { passive: true });
|
||||
|
||||
return () => {
|
||||
container.removeEventListener("scroll", handleScroll);
|
||||
if (animationFrame) {
|
||||
cancelAnimationFrame(animationFrame);
|
||||
}
|
||||
};
|
||||
}, [isMobile]);
|
||||
|
||||
// Automatically switch to workbench when read mode or multiTool is activated in mobile
|
||||
useEffect(() => {
|
||||
if (isMobile && (readerMode || selectedToolKey === "multiTool")) {
|
||||
setActiveMobileView("workbench");
|
||||
}
|
||||
}, [isMobile, readerMode, selectedToolKey]);
|
||||
|
||||
// Automatically switch to workbench slide when a custom workbench (e.g. signing) is active on mobile.
|
||||
// hideToolPanel is true for all custom workbenches that take over the full screen.
|
||||
useEffect(() => {
|
||||
if (isMobile && hideToolPanel) {
|
||||
setActiveMobileView("workbench");
|
||||
}
|
||||
}, [isMobile, hideToolPanel]);
|
||||
|
||||
// When navigating back to tools view in mobile with a workbench-only tool, show tool picker
|
||||
useEffect(() => {
|
||||
if (isMobile && activeMobileView === "tools" && selectedTool) {
|
||||
// Check if this is a workbench-only tool (has workbench but no component)
|
||||
if (selectedTool.workbench && !selectedTool.component) {
|
||||
setLeftPanelView("toolPicker");
|
||||
}
|
||||
}
|
||||
}, [isMobile, activeMobileView, selectedTool, setLeftPanelView]);
|
||||
|
||||
const baseUrl = useBaseUrl();
|
||||
|
||||
// Update document meta when tool changes
|
||||
const appName = config?.appNameNavbar || "Stirling PDF";
|
||||
useDocumentMeta({
|
||||
title: selectedTool ? `${selectedTool.name} - ${appName}` : appName,
|
||||
description:
|
||||
selectedTool?.description ||
|
||||
t(
|
||||
"app.description",
|
||||
"The Free Adobe Acrobat alternative (10M+ Downloads)",
|
||||
),
|
||||
ogTitle: selectedTool ? `${selectedTool.name} - ${appName}` : appName,
|
||||
ogDescription:
|
||||
selectedTool?.description ||
|
||||
t(
|
||||
"app.description",
|
||||
"The Free Adobe Acrobat alternative (10M+ Downloads)",
|
||||
),
|
||||
ogImage: selectedToolKey
|
||||
? `${baseUrl}/og_images/${selectedToolKey}.png`
|
||||
: `${baseUrl}/og_images/home.png`,
|
||||
ogUrl: selectedTool ? `${baseUrl}${window.location.pathname}` : baseUrl,
|
||||
});
|
||||
|
||||
// Note: File selection limits are now handled directly by individual tools
|
||||
|
||||
return (
|
||||
<div className="h-screen overflow-hidden">
|
||||
<HomePageExtensions />
|
||||
{isMobile ? (
|
||||
<div className="mobile-layout">
|
||||
<div className="mobile-toggle">
|
||||
<div className="mobile-header">
|
||||
<div className="mobile-brand">
|
||||
<LogoIcon className="mobile-brand-icon" />
|
||||
<Wordmark alt={brandAltText} className="mobile-brand-text" />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="mobile-toggle-buttons"
|
||||
role="tablist"
|
||||
aria-label={t(
|
||||
"home.mobile.viewSwitcher",
|
||||
"Switch workspace view",
|
||||
)}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={activeMobileView === "tools"}
|
||||
className={`mobile-toggle-button ${activeMobileView === "tools" ? "active" : ""}`}
|
||||
onClick={() => handleSelectMobileView("tools")}
|
||||
>
|
||||
{t("home.mobile.tools", "Tools")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={activeMobileView === "workbench"}
|
||||
className={`mobile-toggle-button ${activeMobileView === "workbench" ? "active" : ""}`}
|
||||
onClick={() => handleSelectMobileView("workbench")}
|
||||
>
|
||||
{t("home.mobile.workspace", "Workspace")}
|
||||
</button>
|
||||
</div>
|
||||
<span className="mobile-toggle-hint">
|
||||
{t(
|
||||
"home.mobile.swipeHint",
|
||||
"Swipe left or right to switch views",
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div ref={sliderRef} className="mobile-slider">
|
||||
<div
|
||||
className="mobile-slide"
|
||||
aria-label={t("home.mobile.toolsSlide", "Tool selection panel")}
|
||||
>
|
||||
<div className="mobile-slide-content">
|
||||
<ToolPanel />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="mobile-slide"
|
||||
aria-label={t("home.mobile.workbenchSlide", "Workspace panel")}
|
||||
>
|
||||
<div className="mobile-slide-content">
|
||||
<div className="flex-1 min-h-0 flex">
|
||||
<Workbench />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mobile-bottom-bar">
|
||||
<button
|
||||
className="mobile-bottom-button"
|
||||
aria-label={t("quickAccess.allTools", "Tools")}
|
||||
onClick={() => {
|
||||
handleBackToTools();
|
||||
if (isMobile) {
|
||||
setActiveMobileView("tools");
|
||||
}
|
||||
}}
|
||||
>
|
||||
<AppsIcon sx={{ fontSize: "1.5rem" }} />
|
||||
<span className="mobile-bottom-button-label">
|
||||
{t("quickAccess.allTools", "Tools")}
|
||||
</span>
|
||||
</button>
|
||||
{toolAvailability["automate"]?.available !== false && (
|
||||
<button
|
||||
className="mobile-bottom-button"
|
||||
aria-label={t("quickAccess.automate", "Automate")}
|
||||
onClick={() => {
|
||||
handleToolSelect("automate");
|
||||
if (isMobile) {
|
||||
setActiveMobileView("tools");
|
||||
}
|
||||
}}
|
||||
>
|
||||
<LocalIcon
|
||||
icon="automation-outline"
|
||||
width="1.5rem"
|
||||
height="1.5rem"
|
||||
/>
|
||||
<span className="mobile-bottom-button-label">
|
||||
{t("quickAccess.automate", "Automate")}
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
className="mobile-bottom-button"
|
||||
aria-label={t("home.mobile.openFiles", "Open files")}
|
||||
onClick={() => openFilesModal()}
|
||||
>
|
||||
<LocalIcon icon="folder-rounded" width="1.5rem" height="1.5rem" />
|
||||
<span className="mobile-bottom-button-label">
|
||||
{t("quickAccess.files", "Files")}
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
className="mobile-bottom-button"
|
||||
aria-label={t("quickAccess.config", "Config")}
|
||||
onClick={() => setConfigModalOpen(true)}
|
||||
>
|
||||
<LocalIcon
|
||||
icon="settings-rounded"
|
||||
width="1.5rem"
|
||||
height="1.5rem"
|
||||
/>
|
||||
<span className="mobile-bottom-button-label">
|
||||
{t("quickAccess.config", "Config")}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<FileManager selectedTool={selectedTool as any /* FIX ME */} />
|
||||
<AppConfigModal
|
||||
opened={configModalOpen}
|
||||
onClose={() => setConfigModalOpen(false)}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<Group align="flex-start" gap={0} h="100%" className="flex-nowrap flex">
|
||||
<FileSidebar
|
||||
ref={quickAccessRef}
|
||||
collapsed={fileSidebarCollapsed}
|
||||
onToggleCollapse={() => setFileSidebarCollapsed((c) => !c)}
|
||||
onOpenSettings={() => setConfigModalOpen(true)}
|
||||
/>
|
||||
<Workbench />
|
||||
{!hideToolPanel && <ToolPanel />}
|
||||
<FileManager selectedTool={selectedTool as any /* FIX ME */} />
|
||||
<AppConfigModal
|
||||
opened={configModalOpen}
|
||||
onClose={() => setConfigModalOpen(false)}
|
||||
/>
|
||||
</Group>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user