feat: remove folders & create folder
This commit is contained in:
@@ -65,6 +65,12 @@ export const IDE: React.FC<IDEProps> = ({
|
|||||||
const initialize = useIDEStore((state) => state.initialize);
|
const initialize = useIDEStore((state) => state.initialize);
|
||||||
const isInitialized = useIDEStore((state) => state.isInitialized);
|
const isInitialized = useIDEStore((state) => state.isInitialized);
|
||||||
const fetchTree = useIDEStore((state) => state.fetchTree);
|
const fetchTree = useIDEStore((state) => state.fetchTree);
|
||||||
|
const fetchInterpreters = useIDEStore((state) => state.fetchInterpreters);
|
||||||
|
|
||||||
|
// Загружаем интерпретаторы при инициализации
|
||||||
|
useEffect(() => {
|
||||||
|
fetchInterpreters();
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Обработка Ctrl+S
|
// Обработка Ctrl+S
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { apiClient } from "@/shared/api/axios.instance";
|
import { apiClient } from "@/shared/api/axios.instance";
|
||||||
|
import type { Interpreter } from "../types";
|
||||||
|
|
||||||
export interface ScriptNodeDto {
|
export interface ScriptNodeDto {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -32,6 +33,11 @@ export interface UpdateScriptPayload {
|
|||||||
|
|
||||||
// apiClient уже имеет интерсептор для Authorization header
|
// apiClient уже имеет интерсептор для Authorization header
|
||||||
export const scriptsApi = {
|
export const scriptsApi = {
|
||||||
|
getInterpreters: async (): Promise<Interpreter[]> => {
|
||||||
|
const res = await apiClient.get<Interpreter[]>("/scripts/interpreters");
|
||||||
|
return res.data;
|
||||||
|
},
|
||||||
|
|
||||||
getTree: async (): Promise<ScriptNodeDto[]> => {
|
getTree: async (): Promise<ScriptNodeDto[]> => {
|
||||||
const res = await apiClient.get<ScriptNodeDto[]>("/scripts/tree");
|
const res = await apiClient.get<ScriptNodeDto[]>("/scripts/tree");
|
||||||
return res.data;
|
return res.data;
|
||||||
@@ -55,4 +61,15 @@ export const scriptsApi = {
|
|||||||
deleteScript: async (id: number): Promise<void> => {
|
deleteScript: async (id: number): Promise<void> => {
|
||||||
await apiClient.delete(`/scripts/${id}`);
|
await apiClient.delete(`/scripts/${id}`);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
createFolder: async (path: string): Promise<{ path: string }> => {
|
||||||
|
const res = await apiClient.post<{ path: string }>("/scripts/folder", {
|
||||||
|
path,
|
||||||
|
});
|
||||||
|
return res.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteFolder: async (path: string): Promise<void> => {
|
||||||
|
await apiClient.delete(`/scripts/folder`, { data: { path } });
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -46,6 +46,10 @@ export const FileExplorer: React.FC<FileExplorerProps> = ({
|
|||||||
const handleEmptyContextMenu = (e: React.MouseEvent) => {
|
const handleEmptyContextMenu = (e: React.MouseEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
// Загружаем интерпретаторы перед открытием меню
|
||||||
|
if (store.interpreters.length === 0) {
|
||||||
|
store.fetchInterpreters();
|
||||||
|
}
|
||||||
store.setContextMenu({ x: e.clientX, y: e.clientY, node: null });
|
store.setContextMenu({ x: e.clientX, y: e.clientY, node: null });
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -55,6 +59,13 @@ export const FileExplorer: React.FC<FileExplorerProps> = ({
|
|||||||
store.setContextMenu({ x: e.clientX, y: e.clientY, node });
|
store.setContextMenu({ x: e.clientX, y: e.clientY, node });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Загружаем интерпретаторы при монтировании компонента
|
||||||
|
useEffect(() => {
|
||||||
|
if (store.interpreters.length === 0) {
|
||||||
|
store.fetchInterpreters();
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
const filteredFiles = store.searchQuery
|
const filteredFiles = store.searchQuery
|
||||||
? (files.children || [])
|
? (files.children || [])
|
||||||
.map((child) => filterTree(child, store.searchQuery))
|
.map((child) => filterTree(child, store.searchQuery))
|
||||||
@@ -320,8 +331,13 @@ export const FileExplorer: React.FC<FileExplorerProps> = ({
|
|||||||
? store.dialog.node.name
|
? store.dialog.node.name
|
||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
onConfirm={store.handleDialogConfirm}
|
onConfirm={(value, interpreterId) => {
|
||||||
|
store.handleDialogConfirm(value, interpreterId);
|
||||||
|
}}
|
||||||
onCancel={() => store.setDialog(null)}
|
onCancel={() => store.setDialog(null)}
|
||||||
|
interpreters={
|
||||||
|
store.dialog.type === "newFile" ? store.interpreters : undefined
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import React, { useState, useRef, useEffect } from "react";
|
import React, { useState, useRef, useEffect } from "react";
|
||||||
|
import type { Interpreter } from "../types";
|
||||||
|
|
||||||
interface InputDialogProps {
|
interface InputDialogProps {
|
||||||
title: string;
|
title: string;
|
||||||
initialValue?: string;
|
initialValue?: string;
|
||||||
onConfirm: (value: string) => void;
|
onConfirm: (value: string, interpreterId?: number) => void;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
|
interpreters?: Interpreter[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const InputDialog: React.FC<InputDialogProps> = ({
|
export const InputDialog: React.FC<InputDialogProps> = ({
|
||||||
@@ -12,8 +14,12 @@ export const InputDialog: React.FC<InputDialogProps> = ({
|
|||||||
initialValue = "",
|
initialValue = "",
|
||||||
onConfirm,
|
onConfirm,
|
||||||
onCancel,
|
onCancel,
|
||||||
|
interpreters,
|
||||||
}) => {
|
}) => {
|
||||||
const [value, setValue] = useState(initialValue);
|
const [value, setValue] = useState(initialValue);
|
||||||
|
const [interpreterId, setInterpreterId] = useState<number | undefined>(
|
||||||
|
interpreters?.[0]?.id,
|
||||||
|
);
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -21,6 +27,8 @@ export const InputDialog: React.FC<InputDialogProps> = ({
|
|||||||
inputRef.current?.select();
|
inputRef.current?.select();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const showInterpreterDropdown = interpreters && interpreters.length > 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@@ -59,7 +67,7 @@ export const InputDialog: React.FC<InputDialogProps> = ({
|
|||||||
{title}
|
{title}
|
||||||
</h3>
|
</h3>
|
||||||
<p style={{ margin: "0 0 16px 0", color: "#858585", fontSize: "12px" }}>
|
<p style={{ margin: "0 0 16px 0", color: "#858585", fontSize: "12px" }}>
|
||||||
Enter a new name
|
Enter a name
|
||||||
</p>
|
</p>
|
||||||
<input
|
<input
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
@@ -67,7 +75,9 @@ export const InputDialog: React.FC<InputDialogProps> = ({
|
|||||||
value={value}
|
value={value}
|
||||||
onChange={(e) => setValue(e.target.value)}
|
onChange={(e) => setValue(e.target.value)}
|
||||||
onKeyDown={(e) =>
|
onKeyDown={(e) =>
|
||||||
e.key === "Enter" && value.trim() && onConfirm(value.trim())
|
e.key === "Enter" &&
|
||||||
|
value.trim() &&
|
||||||
|
onConfirm(value.trim(), interpreterId)
|
||||||
}
|
}
|
||||||
style={{
|
style={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
@@ -77,10 +87,48 @@ export const InputDialog: React.FC<InputDialogProps> = ({
|
|||||||
borderRadius: "6px",
|
borderRadius: "6px",
|
||||||
color: "#ccc",
|
color: "#ccc",
|
||||||
fontSize: "14px",
|
fontSize: "14px",
|
||||||
marginBottom: "20px",
|
marginBottom: showInterpreterDropdown ? "12px" : "20px",
|
||||||
outline: "none",
|
outline: "none",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Interpreter dropdown */}
|
||||||
|
{showInterpreterDropdown && (
|
||||||
|
<div style={{ marginBottom: "20px" }}>
|
||||||
|
<label
|
||||||
|
style={{
|
||||||
|
display: "block",
|
||||||
|
fontSize: "12px",
|
||||||
|
color: "#858585",
|
||||||
|
marginBottom: "6px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Interpreter
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
value={interpreterId}
|
||||||
|
onChange={(e) => setInterpreterId(Number(e.target.value))}
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
padding: "10px",
|
||||||
|
backgroundColor: "#3c3c3c",
|
||||||
|
border: "1px solid #3e3e42",
|
||||||
|
borderRadius: "6px",
|
||||||
|
color: "#ccc",
|
||||||
|
fontSize: "14px",
|
||||||
|
outline: "none",
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{interpreters.map((interp) => (
|
||||||
|
<option key={interp.id} value={interp.id}>
|
||||||
|
{interp.label}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
style={{ display: "flex", gap: "12px", justifyContent: "flex-end" }}
|
style={{ display: "flex", gap: "12px", justifyContent: "flex-end" }}
|
||||||
>
|
>
|
||||||
@@ -99,7 +147,9 @@ export const InputDialog: React.FC<InputDialogProps> = ({
|
|||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => value.trim() && onConfirm(value.trim())}
|
onClick={() =>
|
||||||
|
value.trim() && onConfirm(value.trim(), interpreterId)
|
||||||
|
}
|
||||||
style={{
|
style={{
|
||||||
padding: "6px 16px",
|
padding: "6px 16px",
|
||||||
backgroundColor: "#0e639c",
|
backgroundColor: "#0e639c",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import type { FileNode } from "../types";
|
import type { FileNode, Interpreter, DialogState } from "../types";
|
||||||
import {
|
import {
|
||||||
addPaths,
|
addPaths,
|
||||||
getAllFolderPaths,
|
getAllFolderPaths,
|
||||||
@@ -52,13 +52,11 @@ interface IDEState {
|
|||||||
searchQuery: string;
|
searchQuery: string;
|
||||||
showSearch: boolean;
|
showSearch: boolean;
|
||||||
isInitialized: boolean;
|
isInitialized: boolean;
|
||||||
|
interpreters: Interpreter[];
|
||||||
|
|
||||||
// Диалоги и контекстные меню
|
// Диалоги и контекстные меню
|
||||||
contextMenu: { x: number; y: number; node: FileNode | null } | null;
|
contextMenu: { x: number; y: number; node: FileNode | null } | null;
|
||||||
dialog: {
|
dialog: DialogState | null;
|
||||||
type: "newFile" | "newFolder" | "rename";
|
|
||||||
node: FileNode | null;
|
|
||||||
} | null;
|
|
||||||
tabContextMenu: { x: number; y: number; file: FileNode } | null;
|
tabContextMenu: { x: number; y: number; file: FileNode } | null;
|
||||||
|
|
||||||
// Действия с файлами
|
// Действия с файлами
|
||||||
@@ -78,6 +76,9 @@ interface IDEState {
|
|||||||
deleteRoot: () => void;
|
deleteRoot: () => void;
|
||||||
createNewProject: () => void;
|
createNewProject: () => void;
|
||||||
|
|
||||||
|
// Интерпретаторы
|
||||||
|
fetchInterpreters: () => Promise<void>;
|
||||||
|
|
||||||
// API методы
|
// API методы
|
||||||
fetchTree: () => Promise<void>;
|
fetchTree: () => Promise<void>;
|
||||||
createScript: (payload: {
|
createScript: (payload: {
|
||||||
@@ -85,11 +86,13 @@ interface IDEState {
|
|||||||
interpreter_id: number;
|
interpreter_id: number;
|
||||||
path: string;
|
path: string;
|
||||||
}) => Promise<void>;
|
}) => Promise<void>;
|
||||||
|
createFolder: (path: string) => Promise<void>;
|
||||||
updateScript: (
|
updateScript: (
|
||||||
id: number,
|
id: number,
|
||||||
payload: { content: string; interpreter_id: number; path: string },
|
payload: { content: string; interpreter_id: number; path: string },
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
deleteScript: (id: number) => Promise<void>;
|
deleteScript: (id: number) => Promise<void>;
|
||||||
|
deleteFolder: (payload: { path: string }) => Promise<void>;
|
||||||
saveActiveFile: () => Promise<void>;
|
saveActiveFile: () => Promise<void>;
|
||||||
|
|
||||||
// Поиск
|
// Поиск
|
||||||
@@ -114,7 +117,7 @@ interface IDEState {
|
|||||||
initialize: (initialFiles: FileNode) => void;
|
initialize: (initialFiles: FileNode) => void;
|
||||||
|
|
||||||
// Диалог подтверждения
|
// Диалог подтверждения
|
||||||
handleDialogConfirm: (value: string) => Promise<void>;
|
handleDialogConfirm: (value: string, interpreterId?: number) => Promise<void>;
|
||||||
handleDeleteNode: (node: FileNode) => Promise<void>;
|
handleDeleteNode: (node: FileNode) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,6 +134,7 @@ export const useIDEStore = create<IDEState>((set, get) => ({
|
|||||||
contextMenu: null,
|
contextMenu: null,
|
||||||
dialog: null,
|
dialog: null,
|
||||||
tabContextMenu: null,
|
tabContextMenu: null,
|
||||||
|
interpreters: [],
|
||||||
|
|
||||||
// Инициализация
|
// Инициализация
|
||||||
initialize: (initialFiles: FileNode) => {
|
initialize: (initialFiles: FileNode) => {
|
||||||
@@ -295,10 +299,21 @@ export const useIDEStore = create<IDEState>((set, get) => ({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Интерпретаторы
|
||||||
|
fetchInterpreters: async () => {
|
||||||
|
try {
|
||||||
|
const interpreters = await scriptsApi.getInterpreters();
|
||||||
|
set({ interpreters });
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to fetch interpreters:", e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// API: загрузка дерева с сервера
|
// API: загрузка дерева с сервера
|
||||||
fetchTree: async () => {
|
fetchTree: async () => {
|
||||||
try {
|
try {
|
||||||
const data = await scriptsApi.getTree();
|
const data = await scriptsApi.getTree();
|
||||||
|
const { expandedFolders } = get();
|
||||||
|
|
||||||
const convertItem = (item: any): FileNode => {
|
const convertItem = (item: any): FileNode => {
|
||||||
const node: FileNode = {
|
const node: FileNode = {
|
||||||
@@ -332,7 +347,7 @@ export const useIDEStore = create<IDEState>((set, get) => ({
|
|||||||
type: "folder",
|
type: "folder",
|
||||||
children: roots,
|
children: roots,
|
||||||
},
|
},
|
||||||
expandedFolders: new Set(),
|
expandedFolders,
|
||||||
isInitialized: true,
|
isInitialized: true,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -352,6 +367,37 @@ export const useIDEStore = create<IDEState>((set, get) => ({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// API: создание папки
|
||||||
|
createFolder: async (path: string) => {
|
||||||
|
try {
|
||||||
|
await scriptsApi.createFolder(path);
|
||||||
|
await get().fetchTree();
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to create folder:", e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// API: удаление папки
|
||||||
|
deleteFolder: async ({ path }: { path: string }) => {
|
||||||
|
try {
|
||||||
|
const { openFiles } = get();
|
||||||
|
|
||||||
|
// Закрываем все файлы, которые находятся в удаляемой папке
|
||||||
|
const folderPathPrefix = path.endsWith("/") ? path : `${path}/`;
|
||||||
|
const filesToClose = openFiles.filter(
|
||||||
|
(f) => f.path === path || f.path?.startsWith(folderPathPrefix),
|
||||||
|
);
|
||||||
|
filesToClose.forEach((f) => get().closeFile(f));
|
||||||
|
|
||||||
|
await scriptsApi.deleteFolder(path);
|
||||||
|
await get().fetchTree();
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to delete folder:", e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// API: обновление скрипта
|
// API: обновление скрипта
|
||||||
updateScript: async (id, payload) => {
|
updateScript: async (id, payload) => {
|
||||||
try {
|
try {
|
||||||
@@ -412,7 +458,7 @@ export const useIDEStore = create<IDEState>((set, get) => ({
|
|||||||
setTabContextMenu: (menu) => set({ tabContextMenu: menu }),
|
setTabContextMenu: (menu) => set({ tabContextMenu: menu }),
|
||||||
|
|
||||||
// Подтверждение диалога
|
// Подтверждение диалога
|
||||||
handleDialogConfirm: async (value: string) => {
|
handleDialogConfirm: async (value: string, interpreterId?: number) => {
|
||||||
const { dialog, files, toggleFolder, autoExpandPaths } = get();
|
const { dialog, files, toggleFolder, autoExpandPaths } = get();
|
||||||
if (!dialog) return;
|
if (!dialog) return;
|
||||||
|
|
||||||
@@ -480,31 +526,52 @@ export const useIDEStore = create<IDEState>((set, get) => ({
|
|||||||
const savedExpandedFolders = new Set(get().expandedFolders);
|
const savedExpandedFolders = new Set(get().expandedFolders);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await scriptsApi.createScript({
|
// Создание папки
|
||||||
content: "",
|
if (dialog.type === "newFolder" && !isFile) {
|
||||||
interpreter_id: 1,
|
await scriptsApi.createFolder(fullPath);
|
||||||
path: fullPath,
|
await get().fetchTree();
|
||||||
});
|
|
||||||
|
|
||||||
await get().fetchTree();
|
// Восстанавливаем раскрытые папки
|
||||||
|
set({ expandedFolders: savedExpandedFolders });
|
||||||
|
|
||||||
// Восстанавливаем раскрытые папки
|
// Собираем все пути от корня до родительской папки
|
||||||
set({ expandedFolders: savedExpandedFolders });
|
const allParentPaths: string[] = [];
|
||||||
|
let current = parentPath;
|
||||||
|
while (current) {
|
||||||
|
allParentPaths.push(current);
|
||||||
|
const parts = current.split("/");
|
||||||
|
parts.pop();
|
||||||
|
current = parts.join("/");
|
||||||
|
}
|
||||||
|
|
||||||
// Собираем все пути от корня до родительской папки
|
// Раскрываем родительскую цепочку
|
||||||
const allParentPaths: string[] = [];
|
autoExpandPaths(new Set(allParentPaths));
|
||||||
let current = parentPath;
|
} else {
|
||||||
while (current) {
|
// Создание файла
|
||||||
allParentPaths.push(current);
|
const result = await scriptsApi.createScript({
|
||||||
const parts = current.split("/");
|
content: "",
|
||||||
parts.pop();
|
interpreter_id: interpreterId || 0,
|
||||||
current = parts.join("/");
|
path: fullPath,
|
||||||
}
|
});
|
||||||
|
|
||||||
// Раскрываем родительскую цепочку
|
await get().fetchTree();
|
||||||
autoExpandPaths(new Set(allParentPaths));
|
|
||||||
|
// Восстанавливаем раскрытые папки
|
||||||
|
set({ expandedFolders: savedExpandedFolders });
|
||||||
|
|
||||||
|
// Собираем все пути от корня до родительской папки
|
||||||
|
const allParentPaths: string[] = [];
|
||||||
|
let current = parentPath;
|
||||||
|
while (current) {
|
||||||
|
allParentPaths.push(current);
|
||||||
|
const parts = current.split("/");
|
||||||
|
parts.pop();
|
||||||
|
current = parts.join("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Раскрываем родительскую цепочку
|
||||||
|
autoExpandPaths(new Set(allParentPaths));
|
||||||
|
|
||||||
if (isFile) {
|
|
||||||
const createdNode: FileNode = {
|
const createdNode: FileNode = {
|
||||||
id: result.id,
|
id: result.id,
|
||||||
name: finalName,
|
name: finalName,
|
||||||
@@ -529,12 +596,14 @@ export const useIDEStore = create<IDEState>((set, get) => ({
|
|||||||
if (isRootNode) {
|
if (isRootNode) {
|
||||||
get().deleteRoot();
|
get().deleteRoot();
|
||||||
} else if (window.confirm(`Delete "${node.name}"?`)) {
|
} else if (window.confirm(`Delete "${node.name}"?`)) {
|
||||||
if (node.id) {
|
try {
|
||||||
try {
|
if (node.type === "folder") {
|
||||||
|
await get().deleteFolder({ path: node.path || node.name });
|
||||||
|
} else if (node.id) {
|
||||||
await get().deleteScript(node.id);
|
await get().deleteScript(node.id);
|
||||||
} catch (e) {
|
|
||||||
console.error("Failed to delete:", e);
|
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to delete:", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,6 +6,15 @@ export interface FileNode {
|
|||||||
path?: string;
|
path?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Interpreter {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
label: string;
|
||||||
|
argv: string[];
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ContextMenuState {
|
export interface ContextMenuState {
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
@@ -15,6 +24,7 @@ export interface ContextMenuState {
|
|||||||
export interface DialogState {
|
export interface DialogState {
|
||||||
type: "newFile" | "newFolder" | "rename";
|
type: "newFile" | "newFolder" | "rename";
|
||||||
node: FileNode | null;
|
node: FileNode | null;
|
||||||
|
interpreterId?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TabContextMenuState {
|
export interface TabContextMenuState {
|
||||||
|
|||||||
Reference in New Issue
Block a user