Files
HellreigN/frontend/src/modules/ide/IDE.tsx
T
2026-04-05 03:28:31 +03:00

339 lines
9.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useEffect } from "react";
import { MdAdd, MdArrowBack } from "react-icons/md";
import { GoTrash } from "react-icons/go";
import {
useIDEStore,
initialFiles as defaultInitialFiles,
} from "./store/useIDEStore";
import type { FileNode } from "./types";
import {
FileExplorer,
TabBar,
CodeEditor,
TitleBar,
StatusBar,
} from "./components";
import { useThemeStore } from "@/modules/theme-bw/stores/theme.store";
interface IDEProps {
initialFiles?: FileNode;
onBack?: () => void;
}
const darkColors = {
bg: "#1e1e1e",
bgSecondary: "#252526",
bgTertiary: "#2d2d30",
border: "#3e3e42",
textPrimary: "#cccccc",
textSecondary: "#858585",
accent: "#0e639c",
accentHover: "#1177bb",
statusBar: "#007acc",
};
const lightColors = {
bg: "#ffffff",
bgSecondary: "#f3f3f3",
bgTertiary: "#e8e8e8",
border: "#e0e0e0",
textPrimary: "#333333",
textSecondary: "#616161",
accent: "#0e639c",
accentHover: "#1177bb",
statusBar: "#007acc",
};
export const IDE: React.FC<IDEProps> = ({
initialFiles: externalFiles,
onBack,
}: IDEProps = {}) => {
const theme = useThemeStore((s) => s.theme);
const isDark = theme === "dark";
const c = isDark ? darkColors : lightColors;
const files = useIDEStore((state) => state.files);
const openFiles = useIDEStore((state) => state.openFiles);
const activeFile = useIDEStore((state) => state.activeFile);
const createNewProject = useIDEStore((state) => state.createNewProject);
const selectFile = useIDEStore((state) => state.selectFile);
const updateFileContent = useIDEStore((state) => state.updateFileContent);
const saveActiveFile = useIDEStore((state) => state.saveActiveFile);
const closeFile = useIDEStore((state) => state.closeFile);
const closeAllFiles = useIDEStore((state) => state.closeAllFiles);
const closeOtherFiles = useIDEStore((state) => state.closeOtherFiles);
const initialize = useIDEStore((state) => state.initialize);
const isInitialized = useIDEStore((state) => state.isInitialized);
const fetchTree = useIDEStore((state) => state.fetchTree);
const fetchInterpreters = useIDEStore((state) => state.fetchInterpreters);
// Загружаем интерпретаторы при инициализации
useEffect(() => {
fetchInterpreters();
}, []);
// Обработка Ctrl+S
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if ((e.ctrlKey || e.metaKey) && e.key === "s") {
e.preventDefault();
saveActiveFile();
}
};
window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, [saveActiveFile]);
// При загрузке пробуем загрузить дерево с сервера
useEffect(() => {
if (!isInitialized) {
fetchTree().catch(() => {
// Только при ошибке — используем моковые данные
const state = useIDEStore.getState();
if (!state.files) {
const filesToInit = externalFiles || defaultInitialFiles;
initialize(filesToInit);
}
});
}
}, [isInitialized]);
// Если проект не открыт
if (!files) {
return (
<div
style={{
height: "100vh",
display: "flex",
flexDirection: "column",
backgroundColor: c.bg,
fontFamily:
"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
position: "relative",
}}
>
<TitleBar />
{onBack && (
<button
onClick={onBack}
style={{
position: "absolute",
top: "40px",
left: "12px",
background: "transparent",
border: `1px solid ${c.border}`,
color: c.textPrimary,
cursor: "pointer",
display: "flex",
alignItems: "center",
gap: "6px",
padding: "6px 12px",
borderRadius: "6px",
fontSize: "12px",
transition: "all 0.1s",
zIndex: 10,
}}
onMouseEnter={(e) => {
e.currentTarget.style.backgroundColor = c.border;
e.currentTarget.style.color = "#fff";
e.currentTarget.style.borderColor = "#555";
}}
onMouseLeave={(e) => {
e.currentTarget.style.backgroundColor = "transparent";
e.currentTarget.style.color = c.textPrimary;
e.currentTarget.style.borderColor = c.border;
}}
title="Go back"
>
<MdArrowBack size={16} />
<span>Back</span>
</button>
)}
<div
style={{
flex: 1,
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<div style={{ textAlign: "center" }}>
<div
style={{
marginBottom: "24px",
display: "flex",
justifyContent: "center",
opacity: 0.3,
}}
>
<GoTrash size={72} />
</div>
<div
style={{
fontSize: "22px",
marginBottom: "12px",
color: c.textPrimary,
fontWeight: 300,
}}
>
No project open
</div>
<div
style={{
fontSize: "13px",
marginBottom: "32px",
color: c.textSecondary,
}}
>
Create a new project to get started
</div>
<button
onClick={createNewProject}
style={{
padding: "10px 24px",
backgroundColor: c.accent,
border: "none",
borderRadius: "4px",
color: "#fff",
cursor: "pointer",
fontSize: "13px",
fontWeight: 500,
transition: "background-color 0.1s",
}}
onMouseEnter={(e) => {
e.currentTarget.style.backgroundColor = c.accentHover;
}}
onMouseLeave={(e) => {
e.currentTarget.style.backgroundColor = c.accent;
}}
>
<MdAdd size={14} /> New Project
</button>
</div>
</div>
<StatusBar activeFile={null} />
</div>
);
}
return (
<div
style={{
height: "100vh",
display: "flex",
flexDirection: "column",
overflow: "hidden",
backgroundColor: c.bg,
fontFamily:
"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
}}
>
<div
style={{
height: "30px",
backgroundColor: c.bgTertiary,
display: "flex",
alignItems: "center",
justifyContent: "space-between",
padding: "0 8px",
borderBottom: `1px solid ${c.bg}`,
fontSize: "12px",
color: c.textPrimary,
userSelect: "none",
flexShrink: 0,
}}
>
{onBack && (
<button
onClick={onBack}
style={{
background: "transparent",
border: "none",
color: c.textPrimary,
cursor: "pointer",
display: "flex",
alignItems: "center",
gap: "4px",
padding: "4px 8px",
borderRadius: "4px",
fontSize: "11px",
transition: "all 0.1s",
}}
onMouseEnter={(e) => {
e.currentTarget.style.backgroundColor = c.border;
e.currentTarget.style.color = "#fff";
}}
onMouseLeave={(e) => {
e.currentTarget.style.backgroundColor = "transparent";
e.currentTarget.style.color = c.textPrimary;
}}
title="Go back"
>
<MdArrowBack size={14} />
<span>Back</span>
</button>
)}
{!onBack && <div />}
<span style={{ fontWeight: 400 }}>
{activeFile
? `${activeFile.name}${activeFile.dirty ? " •" : ""} - `
: ""}
{files.name}
</span>
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
{activeFile?.dirty && (
<button
onClick={saveActiveFile}
style={{
background: "transparent",
border: "none",
color: c.textPrimary,
cursor: "pointer",
fontSize: "11px",
padding: "4px 8px",
borderRadius: "4px",
}}
title="Сохранить (Ctrl+S)"
>
Сохранить
</button>
)}
</div>
</div>
<div style={{ display: "flex", flex: 1, overflow: "hidden" }}>
<div style={{ width: "260px", flexShrink: 0 }}>
<FileExplorer
files={files}
onDeleteRoot={useIDEStore.getState().deleteRoot}
/>
</div>
<div
style={{
flex: 1,
display: "flex",
flexDirection: "column",
overflow: "hidden",
}}
>
<TabBar
openFiles={openFiles}
activeFile={activeFile}
onSelectFile={selectFile}
onCloseFile={closeFile}
onCloseAll={closeAllFiles}
onCloseOthers={closeOtherFiles}
/>
<CodeEditor
filePath={activeFile?.path || ""}
content={activeFile?.content || ""}
onChange={updateFileContent}
/>
</div>
</div>
<StatusBar activeFile={activeFile} />
</div>
);
};
export default IDE;