дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 19:25:54 +02:00
fix(workbench): show splash-style progress when opening projects
Цей коміт міститься в:
1 джерело
770178966f
коміт
4d086c44c9
11 files changed
+320
-27
No files matched your search
@@ -1,4 +1,5 @@
|
||||
.workbench {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -6,6 +7,90 @@
|
||||
background: var(--bg-app);
|
||||
color: var(--fg);
|
||||
}
|
||||
|
||||
.content {
|
||||
display: contents;
|
||||
}
|
||||
.content[inert] {
|
||||
visibility: hidden;
|
||||
}
|
||||
.opening {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: var(--z-panel);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--bg-frame);
|
||||
}
|
||||
.openingBody {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--splash-gap);
|
||||
padding: var(--space-4) var(--space-4) var(--splash-offset);
|
||||
color: var(--fg-muted);
|
||||
text-align: center;
|
||||
animation: opening-in var(--dur-slow) ease-out both;
|
||||
}
|
||||
.openingBrand {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: var(--splash-gap);
|
||||
font-size: var(--fs-input-large);
|
||||
font-weight: 500;
|
||||
line-height: 1.21;
|
||||
}
|
||||
.openingLogo {
|
||||
width: var(--splash-logo-size);
|
||||
height: var(--splash-logo-size);
|
||||
}
|
||||
.openingProgress {
|
||||
width: var(--splash-bar-width);
|
||||
height: var(--splash-bar-height);
|
||||
flex-shrink: 0;
|
||||
border-radius: calc(var(--splash-bar-height) / 2);
|
||||
background: color-mix(in srgb, var(--fg-muted) 24%, transparent);
|
||||
}
|
||||
.opening .openingProgress > div {
|
||||
left: 0;
|
||||
width: 100%;
|
||||
opacity: 1;
|
||||
background: var(--accent);
|
||||
transform-origin: left;
|
||||
animation: opening-progress var(--splash-progress-duration) var(--splash-progress-easing) both;
|
||||
}
|
||||
.openingTitle {
|
||||
max-width: 100%;
|
||||
overflow-wrap: anywhere;
|
||||
font-size: var(--fs-small);
|
||||
}
|
||||
.openingPath {
|
||||
max-width: 100%;
|
||||
overflow-wrap: anywhere;
|
||||
font-size: var(--fs-tiny);
|
||||
}
|
||||
@keyframes opening-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@keyframes opening-progress {
|
||||
from {
|
||||
transform: scaleX(0.04);
|
||||
}
|
||||
to {
|
||||
transform: scaleX(0.92);
|
||||
}
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.openingBody {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
/* Islands frame; the project colour washes in from the top-left corner (IDEA project gradient). */
|
||||
.framed {
|
||||
background:
|
||||
|
||||
@@ -325,6 +325,41 @@ describe("tool window split areas", () => {
|
||||
});
|
||||
|
||||
describe("workbench shell", () => {
|
||||
it("covers the old project with the opening target and keeps its views mounted and inert", async () => {
|
||||
const { useProject } = await import("@/platform/project");
|
||||
const { Workbench } = await import("./Workbench");
|
||||
const dispose = registerToolWindow({
|
||||
id: "project",
|
||||
title: "Project",
|
||||
icon: "folder",
|
||||
anchor: "left",
|
||||
component: () => <input aria-label="old project input" />,
|
||||
});
|
||||
useProject.setState({
|
||||
project: { root: "/old", name: "old", trusted: true, isGit: false, settings: {} },
|
||||
opening: false,
|
||||
openingPath: null,
|
||||
});
|
||||
loadToolWindowLayout("/old");
|
||||
const { unmount } = render(<Workbench />);
|
||||
const input = screen.getByRole("textbox", { name: "old project input" });
|
||||
try {
|
||||
act(() => useProject.setState({ opening: true, openingPath: "/work/new-project" }));
|
||||
expect(screen.getByRole("status", { name: "Opening new-project…" })).toBeVisible();
|
||||
expect(screen.getByText("/work/new-project")).toBeVisible();
|
||||
expect(input.closest("[inert]")).not.toBeNull();
|
||||
expect(input).toBeInTheDocument();
|
||||
act(() => useProject.setState({ opening: false, openingPath: null }));
|
||||
expect(screen.queryByText("Opening new-project…")).toBeNull();
|
||||
expect(input.closest("[inert]")).toBeNull();
|
||||
expect(screen.getByRole("textbox", { name: "old project input" })).toBe(input);
|
||||
} finally {
|
||||
unmount();
|
||||
dispose();
|
||||
useProject.setState({ project: null, opening: false, openingPath: null });
|
||||
}
|
||||
});
|
||||
|
||||
it("renders the welcome view without a project and the frame with one", async () => {
|
||||
const { useProject } = await import("@/platform/project");
|
||||
const { Workbench } = await import("./Workbench");
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
*/
|
||||
import { useEditorRegistry } from "@/editor/api";
|
||||
import { basename, useProject } from "@/platform/project";
|
||||
import { useT } from "@/platform/i18n";
|
||||
import { ProgressBar } from "@/ui";
|
||||
import projectMark from "../../src-tauri/icons/source/openidea-mark.svg";
|
||||
import { useDistractionFree } from "./distractionFree";
|
||||
import { EditorArea } from "./EditorArea";
|
||||
import { OverlayHost } from "./OverlayHost";
|
||||
@@ -37,11 +40,33 @@ function LightEditFrame() {
|
||||
);
|
||||
}
|
||||
|
||||
function ProjectOpening({ path }: { path: string }) {
|
||||
const t = useT();
|
||||
const label = t("app.project.opening", { name: basename(path) });
|
||||
return (
|
||||
<div className={styles.opening} data-project-opening>
|
||||
<TitleBar bare />
|
||||
<div className={styles.openingBody} role="status" aria-label={label}>
|
||||
<div className={styles.openingBrand} aria-hidden="true">
|
||||
<img className={styles.openingLogo} src={projectMark} alt="" draggable={false} />
|
||||
<span>OpenIDEA</span>
|
||||
</div>
|
||||
<ProgressBar className={styles.openingProgress} aria-label={label} />
|
||||
<div className={styles.openingTitle}>{label}</div>
|
||||
<div className={styles.openingPath} title={path}>
|
||||
{path}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function Workbench() {
|
||||
useApplyTheme();
|
||||
const banners = useContributions((s) => s.workbenchBanners);
|
||||
const project = useProject((s) => s.project);
|
||||
const lightEdit = useProject((s) => s.lightEdit);
|
||||
const openingPath = useProject((s) => s.openingPath);
|
||||
const root = project?.root ?? null;
|
||||
const distractionFree = useDistractionFree((s) => s.active);
|
||||
const frame = project ? styles.framed : lightEdit ? styles.lightEditFrame : undefined;
|
||||
@@ -51,22 +76,25 @@ export function Workbench() {
|
||||
className={frame ? `${styles.workbench} ${frame}` : styles.workbench}
|
||||
style={project ? projectColorVars(project.name) : undefined}
|
||||
>
|
||||
{project ? (
|
||||
<>
|
||||
{/* Distraction Free Mode keeps the bar for the traffic lights and window dragging only. */}
|
||||
<TitleBar bare={distractionFree} />
|
||||
{!distractionFree && <WindowGroupTabs />}
|
||||
{[...banners.values()].map(({ id, component: Banner }) => (
|
||||
<Banner key={id} />
|
||||
))}
|
||||
<ToolWindowLayout key={root} editor={<EditorArea />} />
|
||||
{!distractionFree && <StatusBar />}
|
||||
</>
|
||||
) : lightEdit ? (
|
||||
<LightEditFrame />
|
||||
) : (
|
||||
<Welcome />
|
||||
)}
|
||||
<div className={styles.content} inert={openingPath !== null} aria-hidden={openingPath ? true : undefined}>
|
||||
{project ? (
|
||||
<>
|
||||
{/* Distraction Free Mode keeps the bar for the traffic lights and window dragging only. */}
|
||||
<TitleBar bare={distractionFree} />
|
||||
{!distractionFree && <WindowGroupTabs />}
|
||||
{[...banners.values()].map(({ id, component: Banner }) => (
|
||||
<Banner key={id} />
|
||||
))}
|
||||
<ToolWindowLayout key={root} editor={<EditorArea />} />
|
||||
{!distractionFree && <StatusBar />}
|
||||
</>
|
||||
) : lightEdit ? (
|
||||
<LightEditFrame />
|
||||
) : (
|
||||
<Welcome />
|
||||
)}
|
||||
</div>
|
||||
{openingPath && <ProjectOpening path={openingPath} />}
|
||||
<OverlayHost />
|
||||
<WindowResizeHandles />
|
||||
</div>
|
||||
|
||||
Reference in new issue
Заблокувати користувача