339 lines
9.9 KiB
TypeScript
339 lines
9.9 KiB
TypeScript
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;
|