mirror of
https://github.com/cfngc4594/monaco-editor-lsp-next.git
synced 2025-05-18 15:26:36 +00:00
refactor(editor): consolidate editor toolbar actions into unified structure
- Moved all editor action buttons (copy, format, undo, redo, reset) from `src/components/features/playground/workspace/editor/components/` to new location `src/features/problems/code/components/toolbar/actions/` - Introduced shared `TooltipButton` component to reduce duplication - Created centralized `useProblemEditorActions` hook for common editor operations - Updated imports and exports through new index file - Maintained all existing functionality while improving code organization
This commit is contained in:
parent
6c9351ccd2
commit
3417d2ee49
@ -1,75 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useState } from "react";
|
||||
import { Check, Copy } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useProblem } from "@/hooks/use-problem";
|
||||
|
||||
export function CopyButton() {
|
||||
const { editor } = useProblem();
|
||||
const [copied, setCopied] = useState(false);
|
||||
const t = useTranslations("WorkspaceEditorHeader.CopyButton");
|
||||
|
||||
const handleCopy = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(editor?.getValue() || "");
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 1500);
|
||||
} catch (err) {
|
||||
console.error("Failed to copy text: ", err);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className={cn(
|
||||
"h-6 w-6 border-none shadow-none px-1.5 py-0.5 hover:bg-muted",
|
||||
copied ? "disabled:opacity-100" : ""
|
||||
)}
|
||||
onClick={handleCopy}
|
||||
aria-label={copied ? "Copied" : "Copy to clipboard"}
|
||||
disabled={!editor || copied}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"transition-all",
|
||||
copied ? "scale-100 opacity-100" : "scale-0 opacity-0"
|
||||
)}
|
||||
>
|
||||
<Check
|
||||
className="stroke-emerald-500"
|
||||
size={16}
|
||||
strokeWidth={2}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
"absolute transition-all",
|
||||
copied ? "scale-0 opacity-0" : "scale-100 opacity-100"
|
||||
)}
|
||||
>
|
||||
<Copy size={16} strokeWidth={2} aria-hidden="true" />
|
||||
</div>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="px-2 py-1 text-xs">
|
||||
{t("TooltipContent")}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { Paintbrush } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useProblem } from "@/hooks/use-problem";
|
||||
|
||||
export function FormatButton() {
|
||||
const { editor } = useProblem();
|
||||
const t = useTranslations("WorkspaceEditorHeader.FormatButton");
|
||||
|
||||
return (
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
aria-label={t("TooltipContent")}
|
||||
onClick={() => {
|
||||
editor?.trigger("format", "editor.action.formatDocument", null);
|
||||
}}
|
||||
className="h-6 w-6 px-1.5 py-0.5 border-none shadow-none hover:bg-muted"
|
||||
disabled={!editor}
|
||||
>
|
||||
<Paintbrush size={16} strokeWidth={2} aria-hidden="true" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="px-2 py-1 text-xs">
|
||||
{t("TooltipContent")}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { Redo2 } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useProblem } from "@/hooks/use-problem";
|
||||
|
||||
export function RedoButton() {
|
||||
const { editor } = useProblem();
|
||||
const t = useTranslations("WorkspaceEditorHeader.RedoButton");
|
||||
|
||||
return (
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
aria-label={t("TooltipContent")}
|
||||
onClick={() => {
|
||||
editor?.trigger("redo", "redo", null);
|
||||
}}
|
||||
disabled={!editor}
|
||||
className="h-6 w-6 px-1.5 py-0.5 border-none shadow-none hover:bg-muted"
|
||||
>
|
||||
<Redo2 size={16} strokeWidth={2} aria-hidden="true" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="px-2 py-1 text-xs">
|
||||
{t("TooltipContent")}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { RotateCcw } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useProblem } from "@/hooks/use-problem";
|
||||
|
||||
export function ResetButton() {
|
||||
const { editor, currentTemplate } = useProblem();
|
||||
const t = useTranslations("WorkspaceEditorHeader.ResetButton");
|
||||
|
||||
const handleReset = () => {
|
||||
if (editor) {
|
||||
const model = editor.getModel();
|
||||
if (model) {
|
||||
const fullRange = model.getFullModelRange();
|
||||
editor.pushUndoStop();
|
||||
editor.executeEdits("reset-code", [
|
||||
{
|
||||
range: fullRange,
|
||||
text: currentTemplate,
|
||||
forceMoveMarkers: true,
|
||||
},
|
||||
]);
|
||||
editor.pushUndoStop();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
aria-label={t("TooltipContent")}
|
||||
onClick={handleReset}
|
||||
disabled={!editor}
|
||||
className="h-6 w-6 px-1.5 py-0.5 border-none shadow-none hover:bg-muted"
|
||||
>
|
||||
<RotateCcw size={16} strokeWidth={2} aria-hidden="true" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="px-2 py-1 text-xs">
|
||||
{t("TooltipContent")}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { Undo2 } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useProblem } from "@/hooks/use-problem";
|
||||
|
||||
export function UndoButton() {
|
||||
const { editor } = useProblem();
|
||||
const t = useTranslations("WorkspaceEditorHeader.UndoButton");
|
||||
|
||||
return (
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
aria-label={t("TooltipContent")}
|
||||
onClick={() => {
|
||||
editor?.trigger("undo", "undo", null);
|
||||
}}
|
||||
disabled={!editor}
|
||||
className="h-6 w-6 px-1.5 py-0.5 border-none shadow-none hover:bg-muted"
|
||||
>
|
||||
<Undo2 size={16} strokeWidth={2} aria-hidden="true" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="px-2 py-1 text-xs">
|
||||
{t("TooltipContent")}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
"use client";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useState } from "react";
|
||||
import { Check, Copy } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { TooltipButton } from "@/components/tooltip-button";
|
||||
import { useProblemEditorActions } from "@/features/problems/code/hooks/use-problem-editor-actions";
|
||||
|
||||
const CopyButton = () => {
|
||||
const t = useTranslations("WorkspaceEditorHeader.CopyButton");
|
||||
const [copied, setCopied] = useState(false);
|
||||
const { canExecute, handleCopy } = useProblemEditorActions();
|
||||
|
||||
const handleClick = async () => {
|
||||
const success = await handleCopy();
|
||||
if (success) {
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 1500);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<TooltipButton
|
||||
tooltipContent={t("TooltipContent")}
|
||||
onClick={handleClick}
|
||||
aria-label={copied ? "Copied" : "Copy to clipboard"}
|
||||
disabled={!canExecute || copied}
|
||||
className={copied ? "disabled:opacity-100" : undefined}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"transition-all",
|
||||
copied ? "scale-100 opacity-100" : "scale-0 opacity-0"
|
||||
)}
|
||||
>
|
||||
<Check
|
||||
className="stroke-emerald-500"
|
||||
size={16}
|
||||
strokeWidth={2}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
"absolute transition-all",
|
||||
copied ? "scale-0 opacity-0" : "scale-100 opacity-100"
|
||||
)}
|
||||
>
|
||||
<Copy size={16} strokeWidth={2} aria-hidden="true" />
|
||||
</div>
|
||||
</TooltipButton>
|
||||
);
|
||||
};
|
||||
|
||||
export { CopyButton };
|
@ -0,0 +1,24 @@
|
||||
"use client";
|
||||
|
||||
import { Paintbrush } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { TooltipButton } from "@/components/tooltip-button";
|
||||
import { useProblemEditorActions } from "@/features/problems/code/hooks/use-problem-editor-actions";
|
||||
|
||||
const FormatButton = () => {
|
||||
const t = useTranslations("WorkspaceEditorHeader.FormatButton");
|
||||
const { canExecute, handleFormat } = useProblemEditorActions();
|
||||
|
||||
return (
|
||||
<TooltipButton
|
||||
onClick={handleFormat}
|
||||
aria-label={t("TooltipContent")}
|
||||
disabled={!canExecute}
|
||||
tooltipContent={t("TooltipContent")}
|
||||
>
|
||||
<Paintbrush size={16} strokeWidth={2} aria-hidden="true" />
|
||||
</TooltipButton>
|
||||
);
|
||||
};
|
||||
|
||||
export { FormatButton };
|
@ -0,0 +1,24 @@
|
||||
"use client";
|
||||
|
||||
import { Redo2 } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { TooltipButton } from "@/components/tooltip-button";
|
||||
import { useProblemEditorActions } from "@/features/problems/code/hooks/use-problem-editor-actions";
|
||||
|
||||
const RedoButton = () => {
|
||||
const t = useTranslations("WorkspaceEditorHeader.RedoButton");
|
||||
const { canExecute, handleRedo } = useProblemEditorActions();
|
||||
|
||||
return (
|
||||
<TooltipButton
|
||||
tooltipContent={t("TooltipContent")}
|
||||
onClick={handleRedo}
|
||||
aria-label={t("TooltipContent")}
|
||||
disabled={!canExecute}
|
||||
>
|
||||
<Redo2 size={16} strokeWidth={2} aria-hidden="true" />
|
||||
</TooltipButton>
|
||||
);
|
||||
};
|
||||
|
||||
export { RedoButton };
|
@ -0,0 +1,43 @@
|
||||
"use client";
|
||||
|
||||
import { useMemo } from "react";
|
||||
import { RotateCcw } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import type { Template } from "@/generated/client";
|
||||
import { TooltipButton } from "@/components/tooltip-button";
|
||||
import { useProblemEditorStore } from "@/stores/problem-editor-store";
|
||||
import { useProblemEditorActions } from "@/features/problems/code/hooks/use-problem-editor-actions";
|
||||
|
||||
interface ResetButtonProps {
|
||||
templates: Template[];
|
||||
}
|
||||
|
||||
const ResetButton = ({ templates }: ResetButtonProps) => {
|
||||
const t = useTranslations("WorkspaceEditorHeader.ResetButton");
|
||||
const { language } = useProblemEditorStore();
|
||||
const { canExecute, handleReset } = useProblemEditorActions();
|
||||
|
||||
const currentTemplate = useMemo(() => {
|
||||
return (
|
||||
templates.find((template) => template.language === language)?.template ??
|
||||
""
|
||||
);
|
||||
}, [language, templates]);
|
||||
|
||||
const handleClick = () => {
|
||||
handleReset(currentTemplate);
|
||||
};
|
||||
|
||||
return (
|
||||
<TooltipButton
|
||||
tooltipContent={t("TooltipContent")}
|
||||
onClick={handleClick}
|
||||
aria-label={t("TooltipContent")}
|
||||
disabled={!canExecute}
|
||||
>
|
||||
<RotateCcw size={16} strokeWidth={2} aria-hidden="true" />
|
||||
</TooltipButton>
|
||||
);
|
||||
};
|
||||
|
||||
export { ResetButton };
|
@ -0,0 +1,24 @@
|
||||
"use client";
|
||||
|
||||
import { Undo2 } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { TooltipButton } from "@/components/tooltip-button";
|
||||
import { useProblemEditorActions } from "@/features/problems/code/hooks/use-problem-editor-actions";
|
||||
|
||||
const UndoButton = () => {
|
||||
const t = useTranslations("WorkspaceEditorHeader.UndoButton");
|
||||
const { canExecute, handleUndo } = useProblemEditorActions();
|
||||
|
||||
return (
|
||||
<TooltipButton
|
||||
tooltipContent={t("TooltipContent")}
|
||||
onClick={handleUndo}
|
||||
aria-label={t("TooltipContent")}
|
||||
disabled={!canExecute}
|
||||
>
|
||||
<Undo2 size={16} strokeWidth={2} aria-hidden="true" />
|
||||
</TooltipButton>
|
||||
);
|
||||
};
|
||||
|
||||
export { UndoButton };
|
5
src/features/problems/code/components/toolbar/index.ts
Normal file
5
src/features/problems/code/components/toolbar/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export * from "./actions/copy-button";
|
||||
export * from "./actions/format-button";
|
||||
export * from "./actions/redo-button";
|
||||
export * from "./actions/reset-button";
|
||||
export * from "./actions/undo-button";
|
@ -0,0 +1,55 @@
|
||||
import { useCallback } from "react";
|
||||
import { useProblemEditorStore } from "@/stores/problem-editor-store";
|
||||
|
||||
export const useProblemEditorActions = () => {
|
||||
const { editor } = useProblemEditorStore();
|
||||
|
||||
const handleCopy = useCallback(async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(editor?.getValue() || "");
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("Failed to copy text: ", error);
|
||||
return false;
|
||||
}
|
||||
}, [editor]);
|
||||
|
||||
const handleFormat = useCallback(() => {
|
||||
editor?.trigger("format", "editor.action.formatDocument", null);
|
||||
}, [editor]);
|
||||
|
||||
const handleUndo = useCallback(() => {
|
||||
editor?.trigger("undo", "undo", null);
|
||||
}, [editor]);
|
||||
|
||||
const handleRedo = useCallback(() => {
|
||||
editor?.trigger("redo", "redo", null);
|
||||
}, [editor]);
|
||||
|
||||
const handleReset = useCallback((template: string) => {
|
||||
if (!editor) return;
|
||||
|
||||
const model = editor.getModel();
|
||||
if (!model) return;
|
||||
|
||||
const fullRange = model.getFullModelRange();
|
||||
editor.pushUndoStop();
|
||||
editor.executeEdits("reset-code", [
|
||||
{
|
||||
range: fullRange,
|
||||
text: template,
|
||||
forceMoveMarkers: true,
|
||||
},
|
||||
]);
|
||||
editor.pushUndoStop();
|
||||
}, [editor]);
|
||||
|
||||
return {
|
||||
handleCopy,
|
||||
handleFormat,
|
||||
handleUndo,
|
||||
handleRedo,
|
||||
handleReset,
|
||||
canExecute: !!editor,
|
||||
};
|
||||
};
|
Loading…
Reference in New Issue
Block a user