mirror of
https://github.com/massbug/judge4c.git
synced 2025-05-18 07:16:34 +00:00
feat(dockview): refactor component API and add customization options
This commit is contained in:
parent
67e1445f98
commit
da26648a9b
@ -1,108 +1,96 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import {
|
import type {
|
||||||
BotIcon,
|
AddPanelOptions,
|
||||||
CircleCheckBigIcon,
|
DockviewApi,
|
||||||
FileTextIcon,
|
DockviewReadyEvent,
|
||||||
FlaskConicalIcon,
|
IDockviewPanelHeaderProps,
|
||||||
SquareCheckIcon,
|
IDockviewPanelProps,
|
||||||
SquarePenIcon,
|
} from "dockview";
|
||||||
TerminalIcon,
|
|
||||||
} from "lucide-react";
|
|
||||||
import "@/styles/dockview.css";
|
import "@/styles/dockview.css";
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import type { LucideIcon } from "lucide-react";
|
||||||
import { DockviewReact, themeAbyssSpaced } from "dockview";
|
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 {
|
||||||
options: (AddPanelOptions & {
|
|
||||||
node: React.ReactNode;
|
|
||||||
icon: IconKey;
|
|
||||||
})[];
|
|
||||||
storageKey: string;
|
storageKey: string;
|
||||||
|
options: AddPanelOptions[];
|
||||||
|
components: Record<string, React.FunctionComponent<IDockviewPanelProps>>;
|
||||||
|
tabComponents?: Record<string, React.FunctionComponent<IDockviewPanelHeaderProps>>;
|
||||||
|
onApiReady?: (api: DockviewApi) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DockView = ({ options, storageKey }: DockviewProps) => {
|
const DefaultTab = (
|
||||||
const [api, setApi] = useState<DockviewApi>();
|
props: IDockviewPanelHeaderProps<{ icon?: LucideIcon }>
|
||||||
|
) => {
|
||||||
|
const { icon: Icon } = props.params;
|
||||||
|
|
||||||
const { components, tabComponents } = useMemo(() => {
|
return (
|
||||||
const comps: Record<string, () => React.ReactNode> = {};
|
|
||||||
const tabs: Record<string, () => React.ReactNode> = {};
|
|
||||||
|
|
||||||
options.forEach((option) => {
|
|
||||||
const { id, icon, node, title } = option;
|
|
||||||
|
|
||||||
comps[id] = () => <>{node}</>;
|
|
||||||
|
|
||||||
const Icon = iconMap[icon];
|
|
||||||
tabs[id] = () => (
|
|
||||||
<div className="flex items-center px-1 text-sm font-medium">
|
<div className="flex items-center px-1 text-sm font-medium">
|
||||||
|
{Icon && (
|
||||||
<Icon
|
<Icon
|
||||||
className="-ms-0.5 me-1.5 opacity-60"
|
className="-ms-0.5 me-1.5 opacity-60"
|
||||||
size={16}
|
size={16}
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
<span>{title}</span>
|
)}
|
||||||
|
{props.api.title}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
};
|
||||||
|
|
||||||
return { components: comps, tabComponents: tabs };
|
export default function DockView({
|
||||||
}, [options]);
|
storageKey,
|
||||||
|
options,
|
||||||
|
components,
|
||||||
|
tabComponents,
|
||||||
|
onApiReady,
|
||||||
|
}: DockviewProps) {
|
||||||
|
const [api, setApi] = useState<DockviewApi>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!api) return;
|
if (!api) return;
|
||||||
|
|
||||||
const disposable = api.onDidLayoutChange(() => {
|
const disposable = api.onDidLayoutChange(() => {
|
||||||
localStorage.setItem(storageKey, JSON.stringify(api.toJSON()));
|
const layout = api.toJSON();
|
||||||
|
localStorage.setItem(storageKey, JSON.stringify(layout));
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => disposable.dispose();
|
return () => disposable.dispose();
|
||||||
}, [api, storageKey]);
|
}, [api, storageKey]);
|
||||||
|
|
||||||
const handleReady = (event: DockviewReadyEvent) => {
|
const onReady = (event: DockviewReadyEvent) => {
|
||||||
setApi(event.api);
|
setApi(event.api);
|
||||||
const serializedLayout = localStorage.getItem(storageKey);
|
onApiReady?.(event.api);
|
||||||
|
|
||||||
const addDefaultPanels = () => {
|
let success = false;
|
||||||
options.forEach((option) => {
|
const serializedLayout = localStorage.getItem(storageKey);
|
||||||
event.api.addPanel({ ...option });
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
if (serializedLayout) {
|
if (serializedLayout) {
|
||||||
try {
|
try {
|
||||||
event.api.fromJSON(JSON.parse(serializedLayout));
|
const layout = JSON.parse(serializedLayout);
|
||||||
|
event.api.fromJSON(layout);
|
||||||
|
success = true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to parse layout:", error);
|
console.error("Failed to load layout:", error);
|
||||||
localStorage.removeItem(storageKey);
|
localStorage.removeItem(storageKey);
|
||||||
addDefaultPanels();
|
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
addDefaultPanels();
|
|
||||||
|
if (!success) {
|
||||||
|
options.forEach((option) => {
|
||||||
|
event.api.addPanel({ ...option });
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DockviewReact
|
<DockviewReact
|
||||||
theme={themeAbyssSpaced}
|
theme={themeAbyssSpaced}
|
||||||
onReady={handleReady}
|
onReady={onReady}
|
||||||
components={components}
|
components={components}
|
||||||
|
defaultTabComponent={DefaultTab}
|
||||||
tabComponents={tabComponents}
|
tabComponents={tabComponents}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default DockView;
|
|
||||||
|
Loading…
Reference in New Issue
Block a user