дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-14 21:55:44 +02:00
Порівняти коміти
1
Коміти
| Автор | SHA1 | Дата | |
|---|---|---|---|
|
|
1c00a1125d |
No files matched your search
@@ -440,7 +440,11 @@ export function DependencyAnalyzer(_props: { input: EditorInput }) {
|
||||
|
||||
const treeView = (
|
||||
<div className={styles.body}>
|
||||
<SplitGroup orientation="horizontal" id="maven-dependency-analyzer-split">
|
||||
<SplitGroup
|
||||
orientation="horizontal"
|
||||
id="maven-dependency-analyzer-split"
|
||||
storageKey="openidea.dependencyAnalyzer.split"
|
||||
>
|
||||
<SplitPanel id="dependencies" minSize={200} className={styles.panel}>
|
||||
<Tree<Node>
|
||||
aria-label="Dependencies"
|
||||
|
||||
@@ -339,7 +339,12 @@ export function BuildView() {
|
||||
}
|
||||
return (
|
||||
<div className={styles.view}>
|
||||
<SplitGroup orientation="horizontal" className={styles.split} id="build-view-split">
|
||||
<SplitGroup
|
||||
orientation="horizontal"
|
||||
className={styles.split}
|
||||
id="build-view-split"
|
||||
storageKey="openidea.build.split"
|
||||
>
|
||||
<SplitPanel id="build-tree" defaultSize="38%" minSize="120px" className={styles.pane}>
|
||||
<BuildTree />
|
||||
</SplitPanel>
|
||||
|
||||
@@ -410,7 +410,7 @@ export function ProblemsView() {
|
||||
<div className={styles.view}>
|
||||
<div className={styles.body} onKeyDownCapture={onKeyDownCapture}>
|
||||
{options.preview ? (
|
||||
<SplitGroup orientation="horizontal" id="problems-preview-split">
|
||||
<SplitGroup orientation="horizontal" id="problems-preview-split" storageKey="openidea.problems.previewSplit">
|
||||
<SplitPanel id="problems" minSize={120} className={styles.panel}>
|
||||
{list}
|
||||
</SplitPanel>
|
||||
|
||||
@@ -401,7 +401,7 @@ function UsagesSplit({ preview, children }: { preview: ReactNode; children: Reac
|
||||
const show = useUsagesOptions((s) => s.preview);
|
||||
if (!show) return children;
|
||||
return (
|
||||
<SplitGroup orientation="horizontal" id="usages-preview-split">
|
||||
<SplitGroup orientation="horizontal" id="usages-preview-split" storageKey="openidea.usages.previewSplit">
|
||||
<SplitPanel id="usages" minSize={160} className={styles.panel}>
|
||||
{children}
|
||||
</SplitPanel>
|
||||
|
||||
@@ -261,7 +261,7 @@ export function HttpClientView() {
|
||||
void refreshHistory();
|
||||
}, []);
|
||||
return (
|
||||
<SplitGroup orientation="horizontal" className={styles.root}>
|
||||
<SplitGroup orientation="horizontal" className={styles.root} storageKey="openidea.httpClient.split">
|
||||
<SplitPanel id="http-history" defaultSize="32%" minSize="160px">
|
||||
<div className={styles.list} role="list" aria-label={tx("Response history")}>
|
||||
{running && (
|
||||
|
||||
@@ -422,7 +422,12 @@ function FindTabView({ tab }: { tab: FindTab }) {
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<SplitGroup orientation="horizontal" className={styles.split} id="find-results-split">
|
||||
<SplitGroup
|
||||
orientation="horizontal"
|
||||
className={styles.split}
|
||||
id="find-results-split"
|
||||
storageKey={showPreview ? "openidea.find.previewSplit" : undefined}
|
||||
>
|
||||
<SplitPanel id="results" minSize={120} className={styles.splitPanel}>
|
||||
<Tree<Node>
|
||||
className={styles.tree}
|
||||
|
||||
@@ -478,7 +478,11 @@ export function TodoView() {
|
||||
{tab === "scope" && <ScopeBar />}
|
||||
{tab === "changes" && <ChangelistBar />}
|
||||
<div className={styles.body}>
|
||||
<SplitGroup orientation="horizontal" id="todo-preview-split">
|
||||
<SplitGroup
|
||||
orientation="horizontal"
|
||||
id="todo-preview-split"
|
||||
storageKey={showPreview ? "openidea.todo.previewSplit" : undefined}
|
||||
>
|
||||
<SplitPanel id="items" minSize={160} className={styles.panel}>
|
||||
<Tree<Node>
|
||||
handleRef={tree}
|
||||
|
||||
@@ -69,14 +69,18 @@ function CommitDialog({ onClose }: { onClose: () => void }) {
|
||||
]}
|
||||
>
|
||||
<div className={styles.body}>
|
||||
<SplitGroup orientation="vertical" className={styles.split}>
|
||||
<SplitPanel defaultSize="58%" minSize="25%">
|
||||
<SplitGroup orientation="horizontal" className={styles.split}>
|
||||
<SplitPanel defaultSize="64%" minSize="35%">
|
||||
<SplitGroup orientation="vertical" className={styles.split} storageKey="openidea.vcs.commitDialogSplit">
|
||||
<SplitPanel id="top" defaultSize="58%" minSize="25%">
|
||||
<SplitGroup
|
||||
orientation="horizontal"
|
||||
className={styles.split}
|
||||
storageKey="openidea.vcs.commitDialogOptionsSplit"
|
||||
>
|
||||
<SplitPanel id="changes" defaultSize="64%" minSize="35%">
|
||||
<ChangesTree mode="dialog" />
|
||||
</SplitPanel>
|
||||
<SplitHandle />
|
||||
<SplitPanel defaultSize="36%" minSize="200px">
|
||||
<SplitPanel id="options" defaultSize="36%" minSize="200px">
|
||||
<div className={styles.options} role="group" aria-label={tx("Commit Options")}>
|
||||
<CommitOptionsBody />
|
||||
</div>
|
||||
@@ -84,7 +88,7 @@ function CommitDialog({ onClose }: { onClose: () => void }) {
|
||||
</SplitGroup>
|
||||
</SplitPanel>
|
||||
<SplitHandle />
|
||||
<SplitPanel defaultSize="42%" minSize="120px">
|
||||
<SplitPanel id="message" defaultSize="42%" minSize="120px">
|
||||
<div className={viewStyles.commit}>
|
||||
<CommitMessageField included={included} runner={runner} autoFocus />
|
||||
</div>
|
||||
|
||||
@@ -184,12 +184,12 @@ export function StashContent({ tab }: { tab: CommitTab }) {
|
||||
const combine = useWorkflowSettings((s) => s.vcs.git.combineStashAndShelf);
|
||||
if (combine)
|
||||
return (
|
||||
<SplitGroup orientation="vertical" className={styles.split}>
|
||||
<SplitPanel defaultSize="50%" minSize="80px">
|
||||
<SplitGroup orientation="vertical" className={styles.split} storageKey="openidea.vcs.stashShelfSplit">
|
||||
<SplitPanel id="stashes" defaultSize="50%" minSize="80px">
|
||||
<StashesPanel />
|
||||
</SplitPanel>
|
||||
<SplitHandle />
|
||||
<SplitPanel defaultSize="50%" minSize="80px">
|
||||
<SplitPanel id="shelf" defaultSize="50%" minSize="80px">
|
||||
<ShelfPanel />
|
||||
</SplitPanel>
|
||||
</SplitGroup>
|
||||
@@ -486,14 +486,18 @@ function ChangesPanel() {
|
||||
const path = useCommit((s) => (s.selection.length === 1 ? s.selection[0] : null));
|
||||
const hasChanges = useVcs((s) => s.status.length > 0);
|
||||
return (
|
||||
<SplitGroup orientation="horizontal" className={styles.split}>
|
||||
<SplitPanel defaultSize="62%" minSize="20%">
|
||||
<SplitGroup orientation="vertical" className={styles.split}>
|
||||
<SplitPanel defaultSize="62%" minSize="20%">
|
||||
<SplitGroup
|
||||
orientation="horizontal"
|
||||
className={styles.split}
|
||||
storageKey={preview ? "openidea.vcs.commitPreviewSplit" : undefined}
|
||||
>
|
||||
<SplitPanel id="commit" defaultSize="62%" minSize="20%">
|
||||
<SplitGroup orientation="vertical" className={styles.split} storageKey="openidea.vcs.commitMessageSplit">
|
||||
<SplitPanel id="changes" defaultSize="62%" minSize="20%">
|
||||
<ChangesTree mode="commit" />
|
||||
</SplitPanel>
|
||||
<SplitHandle />
|
||||
<SplitPanel defaultSize="38%" minSize="120px">
|
||||
<SplitPanel id="message" defaultSize="38%" minSize="120px">
|
||||
<CommitPanel included={included} />
|
||||
</SplitPanel>
|
||||
</SplitGroup>
|
||||
@@ -501,7 +505,7 @@ function ChangesPanel() {
|
||||
{preview && (
|
||||
<>
|
||||
<SplitHandle />
|
||||
<SplitPanel defaultSize="38%" minSize="120px">
|
||||
<SplitPanel id="preview" defaultSize="38%" minSize="120px">
|
||||
{path ? (
|
||||
<DiffEditor input={diffInput(workingDiffData(path))} />
|
||||
) : (
|
||||
@@ -525,14 +529,20 @@ export function LocalChangesView() {
|
||||
const path = useCommit((s) => (s.selection.length === 1 ? s.selection[0] : null));
|
||||
const anchor = useToolWindowLayout((s) => s.placements.git?.anchor ?? "bottom");
|
||||
return (
|
||||
<SplitGroup orientation={anchor === "bottom" ? "horizontal" : "vertical"} className={styles.split}>
|
||||
<SplitPanel defaultSize="50%" minSize="20%">
|
||||
<SplitGroup
|
||||
orientation={anchor === "bottom" ? "horizontal" : "vertical"}
|
||||
className={styles.split}
|
||||
storageKey={
|
||||
preview ? `openidea.vcs.localChangesSplit.${anchor === "bottom" ? "horizontal" : "vertical"}` : undefined
|
||||
}
|
||||
>
|
||||
<SplitPanel id="changes" defaultSize="50%" minSize="20%">
|
||||
<ChangesTree mode="local" />
|
||||
</SplitPanel>
|
||||
{preview && (
|
||||
<>
|
||||
<SplitHandle />
|
||||
<SplitPanel defaultSize="50%" minSize="120px">
|
||||
<SplitPanel id="preview" defaultSize="50%" minSize="120px">
|
||||
{path ? (
|
||||
<DiffEditor input={diffInput(workingDiffData(path))} />
|
||||
) : (
|
||||
|
||||
@@ -327,6 +327,7 @@ export function LogView() {
|
||||
key={showBranches ? "branches" : "no-branches"}
|
||||
orientation="horizontal"
|
||||
id={showBranches ? "git-log" : "git-log-no-branches"}
|
||||
storageKey={`openidea.vcs.logSplit.${showBranches ? "branches" : "noBranches"}.${showDetails ? "details" : "noDetails"}`}
|
||||
className={styles.split}
|
||||
>
|
||||
{showBranches && (
|
||||
@@ -2681,7 +2682,7 @@ export function CommitDetails({ commits }: { commits: Commit[] }) {
|
||||
};
|
||||
|
||||
const details = (
|
||||
<SplitGroup orientation="vertical" id="git-log-details">
|
||||
<SplitGroup orientation="vertical" id="git-log-details" storageKey="openidea.vcs.logDetailsSplit">
|
||||
<SplitPanel id="files" defaultSize="60%" minSize="60px">
|
||||
<div
|
||||
className={styles.filesPane}
|
||||
@@ -2802,6 +2803,7 @@ export function CommitDetails({ commits }: { commits: Commit[] }) {
|
||||
<SplitGroup
|
||||
orientation={diffLocation === "right" ? "horizontal" : "vertical"}
|
||||
id={`git-log-preview-${diffLocation}`}
|
||||
storageKey={`openidea.vcs.logPreviewSplit.${diffLocation}`}
|
||||
>
|
||||
<SplitPanel id="changes" defaultSize="55%" minSize="20%">
|
||||
{details}
|
||||
|
||||
@@ -146,7 +146,7 @@ function RevisionBrowser({
|
||||
) : !files ? (
|
||||
<Spinner />
|
||||
) : (
|
||||
<SplitGroup orientation="horizontal" id="git-revision-browser">
|
||||
<SplitGroup orientation="horizontal" id="git-revision-browser" storageKey="openidea.vcs.revisionBrowserSplit">
|
||||
<SplitPanel id="files" defaultSize="30%" minSize="20%">
|
||||
<Tree
|
||||
roots={tree}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { afterEach, expect, it } from "vitest";
|
||||
import { createRef } from "react";
|
||||
import { render } from "@testing-library/react";
|
||||
import type { GroupImperativeHandle } from "react-resizable-panels";
|
||||
import { SplitGroup, SplitHandle, SplitPanel } from "./Splitter";
|
||||
|
||||
afterEach(() => localStorage.clear());
|
||||
|
||||
function Split({ storageKey, preview }: { storageKey: string; preview: boolean }) {
|
||||
return (
|
||||
<SplitGroup orientation="horizontal" storageKey={storageKey} groupRef={ref}>
|
||||
<SplitPanel id="tree" defaultSize="60%" />
|
||||
{preview && (
|
||||
<>
|
||||
<SplitHandle />
|
||||
<SplitPanel id="preview" defaultSize="40%" />
|
||||
</>
|
||||
)}
|
||||
</SplitGroup>
|
||||
);
|
||||
}
|
||||
const ref = createRef<GroupImperativeHandle>();
|
||||
|
||||
it("starts from the sizes saved under its key", () => {
|
||||
localStorage.setItem("split.preview", JSON.stringify({ tree: 30, preview: 70 }));
|
||||
render(<Split storageKey="split.preview" preview />);
|
||||
expect(ref.current!.getLayout()).toEqual({ tree: 30, preview: 70 });
|
||||
});
|
||||
|
||||
it("applies the saved sizes of a panel set that appears later", () => {
|
||||
localStorage.setItem("split.preview", JSON.stringify({ tree: 25, preview: 75 }));
|
||||
const view = render(<Split storageKey="split.plain" preview={false} />);
|
||||
view.rerender(<Split storageKey="split.preview" preview />);
|
||||
expect(ref.current!.getLayout()).toEqual({ tree: 25, preview: 75 });
|
||||
});
|
||||
|
||||
it("ignores saved sizes for other panels", () => {
|
||||
localStorage.setItem("split.preview", JSON.stringify({ other: 25, preview: 75 }));
|
||||
const view = render(<Split storageKey="split.plain" preview={false} />);
|
||||
view.rerender(<Split storageKey="split.preview" preview />);
|
||||
expect(Object.keys(ref.current!.getLayout()).sort()).toEqual(["preview", "tree"]);
|
||||
expect(ref.current!.getLayout().tree).not.toBe(25);
|
||||
});
|
||||
+65
-15
@@ -2,36 +2,84 @@
|
||||
* Thin wrappers over react-resizable-panels with IDE styling: 1px hairline
|
||||
* separators with a wider invisible hit area and an accent hover state.
|
||||
*/
|
||||
import { Group, Panel, Separator, type GroupProps, type PanelProps, type SeparatorProps } from "react-resizable-panels";
|
||||
import {
|
||||
Group,
|
||||
Panel,
|
||||
Separator,
|
||||
type GroupImperativeHandle,
|
||||
type GroupProps,
|
||||
type PanelProps,
|
||||
type SeparatorProps,
|
||||
} from "react-resizable-panels";
|
||||
import clsx from "clsx";
|
||||
import { useState } from "react";
|
||||
import { useEffect, useImperativeHandle, useRef, useState } from "react";
|
||||
import styles from "./Splitter.module.css";
|
||||
|
||||
function savedLayout(storageKey: string | undefined): Record<string, number> | undefined {
|
||||
try {
|
||||
const value: unknown = storageKey ? JSON.parse(localStorage.getItem(storageKey) ?? "null") : null;
|
||||
return value &&
|
||||
typeof value === "object" &&
|
||||
!Array.isArray(value) &&
|
||||
Object.values(value).every((n) => typeof n === "number" && Number.isFinite(n) && n >= 0 && n <= 100)
|
||||
? (value as Record<string, number>)
|
||||
: undefined;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `storageKey` remembers the sizes the user drags to. A group whose panels come and go (a
|
||||
* preview pane) passes a key per panel set; when it changes, that set's sizes are applied.
|
||||
*/
|
||||
export function SplitGroup({
|
||||
className,
|
||||
storageKey,
|
||||
defaultLayout,
|
||||
onLayoutChanged,
|
||||
groupRef,
|
||||
...rest
|
||||
}: GroupProps & { storageKey?: string }) {
|
||||
const [saved] = useState(() => {
|
||||
try {
|
||||
const value: unknown = storageKey ? JSON.parse(localStorage.getItem(storageKey) ?? "null") : null;
|
||||
return value &&
|
||||
typeof value === "object" &&
|
||||
!Array.isArray(value) &&
|
||||
Object.values(value).every((n) => typeof n === "number" && Number.isFinite(n) && n >= 0 && n <= 100)
|
||||
? (value as Record<string, number>)
|
||||
: undefined;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
const [saved] = useState(() => savedLayout(storageKey));
|
||||
const group = useRef<GroupImperativeHandle | null>(null);
|
||||
const mountedKey = useRef(storageKey);
|
||||
// Sizes to apply once the group's panels match them (a panel added with the new key registers later).
|
||||
const pending = useRef<Record<string, number> | null>(null);
|
||||
const applyPending = (current: Record<string, number>) => {
|
||||
const layout = pending.current;
|
||||
if (!layout) return;
|
||||
const ids = Object.keys(current);
|
||||
if (ids.length !== Object.keys(layout).length || !ids.every((id) => id in layout)) return;
|
||||
pending.current = null;
|
||||
group.current?.setLayout(layout);
|
||||
};
|
||||
useEffect(() => {
|
||||
if (storageKey === mountedKey.current) return;
|
||||
mountedKey.current = storageKey;
|
||||
pending.current = savedLayout(storageKey) ?? null;
|
||||
const current = group.current?.getLayout();
|
||||
if (current) applyPending(current);
|
||||
}, [storageKey]);
|
||||
useImperativeHandle(
|
||||
groupRef,
|
||||
() => ({
|
||||
getLayout: () => group.current?.getLayout() ?? {},
|
||||
setLayout: (layout) => group.current?.setLayout(layout) ?? layout,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
const onLayoutChange: GroupProps["onLayoutChange"] = (layout) => {
|
||||
applyPending(layout);
|
||||
rest.onLayoutChange?.(layout);
|
||||
};
|
||||
if (!storageKey)
|
||||
return (
|
||||
<Group
|
||||
className={clsx(styles.group, className)}
|
||||
{...rest}
|
||||
groupRef={group}
|
||||
onLayoutChange={onLayoutChange}
|
||||
defaultLayout={defaultLayout}
|
||||
onLayoutChanged={onLayoutChanged}
|
||||
/>
|
||||
@@ -40,6 +88,8 @@ export function SplitGroup({
|
||||
<Group
|
||||
className={clsx(styles.group, className)}
|
||||
{...rest}
|
||||
groupRef={group}
|
||||
onLayoutChange={onLayoutChange}
|
||||
defaultLayout={saved ?? defaultLayout}
|
||||
onLayoutChanged={(layout, meta) => {
|
||||
if (storageKey && meta.isUserInteraction) {
|
||||
|
||||
Reference in new issue
Заблокувати користувача