mirror of
https://github.com/cfngc4594/monaco-editor-lsp-next.git
synced 2025-05-18 23:42:24 +00:00
feat(dockview): refactor to generic configurable dockview component
This commit is contained in:
parent
6c34fa0171
commit
b8576dbf10
@ -1,142 +1,98 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DockviewReact,
|
BotIcon,
|
||||||
themeAbyssSpaced,
|
CircleCheckBigIcon,
|
||||||
type DockviewReadyEvent,
|
FileTextIcon,
|
||||||
type IDockviewPanelHeaderProps,
|
FlaskConicalIcon,
|
||||||
} from "dockview";
|
SquareCheckIcon,
|
||||||
import {
|
SquarePenIcon,
|
||||||
CircleCheckBigIcon,
|
TerminalIcon,
|
||||||
FileTextIcon,
|
|
||||||
FlaskConicalIcon,
|
|
||||||
type LucideProps,
|
|
||||||
SquareCheckIcon,
|
|
||||||
SquarePenIcon,
|
|
||||||
TerminalIcon
|
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import "@/styles/dockview.css";
|
import "@/styles/dockview.css";
|
||||||
import { type ForwardRefExoticComponent, type RefAttributes, useMemo } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
|
import { DockviewReact, themeAbyssSpaced } from "dockview";
|
||||||
|
import type { AddPanelOptions, DockviewReadyEvent, DockviewApi } from "dockview";
|
||||||
|
|
||||||
|
const iconMap = {
|
||||||
|
FileTextIcon,
|
||||||
|
FlaskConicalIcon,
|
||||||
|
CircleCheckBigIcon,
|
||||||
|
SquarePenIcon,
|
||||||
|
SquareCheckIcon,
|
||||||
|
TerminalIcon,
|
||||||
|
BotIcon,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
type IconKey = keyof typeof iconMap;
|
||||||
|
|
||||||
interface DockviewProps {
|
interface DockviewProps {
|
||||||
Description: React.ReactNode;
|
options: (AddPanelOptions & {
|
||||||
Solutions: React.ReactNode;
|
node: React.ReactNode;
|
||||||
Submissions: React.ReactNode;
|
icon: IconKey;
|
||||||
Code: React.ReactNode;
|
})[];
|
||||||
Testcase: React.ReactNode;
|
storageKey: string;
|
||||||
TestResult: React.ReactNode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const PanelIcons: Record<string, ForwardRefExoticComponent<Omit<LucideProps, "ref"> & RefAttributes<SVGSVGElement>>> = {
|
const DockView = ({ options, storageKey }: DockviewProps) => {
|
||||||
Description: FileTextIcon,
|
const [api, setApi] = useState<DockviewApi>();
|
||||||
Solutions: FlaskConicalIcon,
|
|
||||||
Submissions: CircleCheckBigIcon,
|
|
||||||
Code: SquarePenIcon,
|
|
||||||
Testcase: SquareCheckIcon,
|
|
||||||
TestResult: TerminalIcon,
|
|
||||||
};
|
|
||||||
|
|
||||||
const LAYOUT_STORAGE_KEY = "dockview:layout";
|
const { components, tabComponents } = useMemo(() => {
|
||||||
|
const comps: Record<string, () => React.ReactNode> = {};
|
||||||
|
const tabs: Record<string, () => React.ReactNode> = {};
|
||||||
|
|
||||||
const DefaultTab = ({ params }: IDockviewPanelHeaderProps<{ title: string }>) => {
|
options.forEach((option) => {
|
||||||
const { title } = params;
|
const { id, icon, node, title } = option;
|
||||||
const Icon = PanelIcons[title];
|
|
||||||
|
|
||||||
return (
|
comps[id] = () => <>{node}</>;
|
||||||
<div className="flex items-center px-1 text-sm font-medium">
|
|
||||||
{Icon && (
|
|
||||||
<Icon
|
|
||||||
className="-ms-0.5 me-1.5 opacity-60"
|
|
||||||
size={16}
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<span>{title}</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const tabComponents = {
|
const Icon = iconMap[icon];
|
||||||
default: DefaultTab,
|
tabs[id] = () => (
|
||||||
};
|
<div className="flex items-center px-1 text-sm font-medium">
|
||||||
|
<Icon
|
||||||
|
className="-ms-0.5 me-1.5 opacity-60"
|
||||||
|
size={16}
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
<span>{title}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const DEFAULT_PANELS = [
|
return { components: comps, tabComponents: tabs };
|
||||||
{ id: "Description", component: "Description", position: null },
|
}, [options]);
|
||||||
{
|
|
||||||
id: "Solutions",
|
|
||||||
component: "Solutions",
|
|
||||||
position: { referencePanel: "Description", direction: "within" },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Submissions",
|
|
||||||
component: "Submissions",
|
|
||||||
position: { referencePanel: "Solutions", direction: "within" },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Code",
|
|
||||||
component: "Code",
|
|
||||||
position: { referencePanel: "Submissions", direction: "right" },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "Testcase",
|
|
||||||
component: "Testcase",
|
|
||||||
position: { referencePanel: "Code", direction: "below" },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "TestResult",
|
|
||||||
component: "TestResult",
|
|
||||||
position: { referencePanel: "Testcase", direction: "within" },
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function DockView(props: DockviewProps) {
|
useEffect(() => {
|
||||||
const components = useMemo(
|
if (!api) return;
|
||||||
() => ({
|
|
||||||
Description: () => props.Description,
|
const disposable = api.onDidLayoutChange(() => {
|
||||||
Solutions: () => props.Solutions,
|
localStorage.setItem(storageKey, JSON.stringify(api.toJSON()));
|
||||||
Submissions: () => props.Submissions,
|
});
|
||||||
Code: () => props.Code,
|
|
||||||
Testcase: () => props.Testcase,
|
return () => disposable.dispose();
|
||||||
TestResult: () => props.TestResult,
|
}, [api, storageKey]);
|
||||||
}),
|
|
||||||
[props]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleReady = (event: DockviewReadyEvent) => {
|
const handleReady = (event: DockviewReadyEvent) => {
|
||||||
let success = false;
|
setApi(event.api);
|
||||||
|
const serializedLayout = localStorage.getItem(storageKey);
|
||||||
|
|
||||||
try {
|
const addDefaultPanels = () => {
|
||||||
const layout = localStorage.getItem(LAYOUT_STORAGE_KEY);
|
options.forEach((option) => {
|
||||||
if (layout) {
|
event.api.addPanel({ ...option });
|
||||||
event.api.fromJSON(JSON.parse(layout));
|
|
||||||
success = true;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Failed to load layout:", error);
|
|
||||||
localStorage.removeItem(LAYOUT_STORAGE_KEY);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!success) {
|
|
||||||
DEFAULT_PANELS.forEach(({ id, component, position }) => {
|
|
||||||
event.api.addPanel({
|
|
||||||
id,
|
|
||||||
component,
|
|
||||||
tabComponent: "default",
|
|
||||||
params: { title: id },
|
|
||||||
position: position || undefined,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
const saveLayout = () => {
|
|
||||||
localStorage.setItem(
|
|
||||||
LAYOUT_STORAGE_KEY,
|
|
||||||
JSON.stringify(event.api.toJSON())
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const disposable = event.api.onDidLayoutChange(saveLayout);
|
if (serializedLayout) {
|
||||||
return () => disposable.dispose();
|
try {
|
||||||
|
event.api.fromJSON(JSON.parse(serializedLayout));
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to parse layout:", error);
|
||||||
|
localStorage.removeItem(storageKey);
|
||||||
|
addDefaultPanels();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
addDefaultPanels();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -147,4 +103,6 @@ export default function DockView(props: DockviewProps) {
|
|||||||
tabComponents={tabComponents}
|
tabComponents={tabComponents}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default DockView;
|
||||||
|
Loading…
Reference in New Issue
Block a user