feat: update button run scripts
ci-front / build (push) Successful in 2m35s

This commit is contained in:
nikita
2026-04-05 04:57:16 +03:00
parent f14490c076
commit d6512d6c97
5 changed files with 87 additions and 38 deletions
@@ -83,4 +83,12 @@ export const scriptsApi = {
); );
return res.data; return res.data;
}, },
run: async (path: string): Promise<{ id: number; status: string }> => {
const res = await apiClient.post<{ id: number; status: string }>(
"/scripts/run",
{ path },
);
return res.data;
},
}; };
@@ -5,20 +5,21 @@ import { useFilePickerStore } from "../store/useFilePickerStore";
interface FilePickerProps { interface FilePickerProps {
files: FileNode; files: FileNode;
onRun?: (path: string) => void;
} }
const FilePickerTree: React.FC<{ node: FileNode; level: number }> = ({ const FilePickerTree: React.FC<{
node, node: FileNode;
level, level: number;
}) => { onRun?: (path: string) => void;
}> = ({ node, level, onRun }) => {
const expandedFolders = useFilePickerStore((s) => s.expandedFolders); const expandedFolders = useFilePickerStore((s) => s.expandedFolders);
const selectedPaths = useFilePickerStore((s) => s.selectedPaths); const runningScripts = useFilePickerStore((s) => s.runningScripts);
const toggleSelection = useFilePickerStore((s) => s.toggleSelection);
const toggleFolder = useFilePickerStore((s) => s.toggleFolder); const toggleFolder = useFilePickerStore((s) => s.toggleFolder);
const nodePath = node.path || node.name; const nodePath = node.path || node.name;
const isExpanded = expandedFolders.has(nodePath); const isExpanded = expandedFolders.has(nodePath);
const isSelected = node.type === "file" && selectedPaths.has(nodePath); const isRunning = node.type === "file" && runningScripts.has(nodePath);
if (node.type === "file") { if (node.type === "file") {
return ( return (
@@ -26,9 +27,9 @@ const FilePickerTree: React.FC<{ node: FileNode; level: number }> = ({
name={node.name} name={node.name}
type="file" type="file"
path={nodePath} path={nodePath}
isSelected={isSelected} isSelected={isRunning}
level={level} level={level}
onToggleSelect={toggleSelection} onRun={onRun}
/> />
); );
} }
@@ -44,14 +45,19 @@ const FilePickerTree: React.FC<{ node: FileNode; level: number }> = ({
onToggleFolder={toggleFolder} onToggleFolder={toggleFolder}
> >
{node.children?.map((child, idx) => ( {node.children?.map((child, idx) => (
<FilePickerTree key={idx} node={child} level={level + 1} /> <FilePickerTree
key={idx}
node={child}
level={level + 1}
onRun={onRun}
/>
))} ))}
</FilePickerItem> </FilePickerItem>
</> </>
); );
}; };
export const FilePicker: React.FC<FilePickerProps> = ({ files }) => { export const FilePicker: React.FC<FilePickerProps> = ({ files, onRun }) => {
return ( return (
<div <div
style={{ style={{
@@ -61,7 +67,7 @@ export const FilePicker: React.FC<FilePickerProps> = ({ files }) => {
}} }}
> >
{(files.children || []).map((child, idx) => ( {(files.children || []).map((child, idx) => (
<FilePickerTree key={idx} node={child} level={0} /> <FilePickerTree key={idx} node={child} level={0} onRun={onRun} />
))} ))}
</div> </div>
); );
@@ -4,6 +4,7 @@ import {
FiChevronDown, FiChevronDown,
FiFile, FiFile,
FiFolder, FiFolder,
FiPlay,
} from "react-icons/fi"; } from "react-icons/fi";
interface FilePickerItemProps { interface FilePickerItemProps {
@@ -16,6 +17,7 @@ interface FilePickerItemProps {
level: number; level: number;
onToggleSelect?: (path: string) => void; onToggleSelect?: (path: string) => void;
onToggleFolder?: (path: string) => void; onToggleFolder?: (path: string) => void;
onRun?: (path: string) => void;
} }
export const FilePickerItem: React.FC<FilePickerItemProps> = ({ export const FilePickerItem: React.FC<FilePickerItemProps> = ({
@@ -28,6 +30,7 @@ export const FilePickerItem: React.FC<FilePickerItemProps> = ({
level, level,
onToggleSelect, onToggleSelect,
onToggleFolder, onToggleFolder,
onRun,
}) => { }) => {
const isFolder = type === "folder"; const isFolder = type === "folder";
const extension = name.includes(".") const extension = name.includes(".")
@@ -120,40 +123,48 @@ export const FilePickerItem: React.FC<FilePickerItemProps> = ({
</span> </span>
)} )}
{/* Checkbox — только у файлов */} {/* Run button — только у файлов */}
{!isFolder && onToggleSelect && ( {!isFolder && onRun && (
<div <button
style={{ style={{
width: "18px",
height: "18px",
border: isSelected
? "2px solid #0e639c"
: "2px solid var(--border)",
borderRadius: "3px",
backgroundColor: isSelected ? "#0e639c" : "transparent",
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
justifyContent: "center", justifyContent: "center",
padding: "4px",
backgroundColor: isSelected ? "#238636" : "transparent",
border: isSelected
? "1px solid #2ea043"
: "1px solid transparent",
borderRadius: "3px",
color: isSelected ? "#ffffff" : "var(--text-secondary)",
cursor: "pointer",
flexShrink: 0, flexShrink: 0,
transition: "all 0.15s", transition: "all 0.15s",
}} }}
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
onToggleSelect(path); onRun(path);
}} }}
onMouseEnter={(e) => {
e.currentTarget.style.backgroundColor = "#238636";
e.currentTarget.style.color = "#ffffff";
e.currentTarget.style.borderColor = "#2ea043";
}}
onMouseLeave={(e) => {
e.currentTarget.style.backgroundColor = isSelected
? "#238636"
: "transparent";
e.currentTarget.style.color = isSelected
? "#ffffff"
: "var(--text-secondary)";
e.currentTarget.style.borderColor = isSelected
? "#2ea043"
: "transparent";
}}
title="Run script"
> >
{isSelected && ( <FiPlay size={12} />
<svg width="12" height="12" viewBox="0 0 12 12" fill="none"> </button>
<path
d="M2 6L5 9L10 3"
stroke="white"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)}
</div>
)} )}
</div> </div>
@@ -1,19 +1,23 @@
import { create } from "zustand"; import { create } from "zustand";
import { scriptsApi } from "../api/scripts.api";
interface FilePickerState { interface FilePickerState {
selectedPaths: Set<string>; selectedPaths: Set<string>;
expandedFolders: Set<string>; expandedFolders: Set<string>;
runningScripts: Map<string, { id: number; status: string }>;
toggleSelection: (path: string) => void; toggleSelection: (path: string) => void;
selectAll: (paths: string[]) => void; selectAll: (paths: string[]) => void;
clearSelection: () => void; clearSelection: () => void;
toggleFolder: (path: string) => void; toggleFolder: (path: string) => void;
getSelectedPaths: () => string[]; getSelectedPaths: () => string[];
runScript: (path: string) => Promise<void>;
} }
export const useFilePickerStore = create<FilePickerState>((set, get) => ({ export const useFilePickerStore = create<FilePickerState>((set, get) => ({
selectedPaths: new Set(), selectedPaths: new Set(),
expandedFolders: new Set(), expandedFolders: new Set(),
runningScripts: new Map(),
toggleSelection: (path: string) => { toggleSelection: (path: string) => {
set((state) => { set((state) => {
@@ -54,4 +58,17 @@ export const useFilePickerStore = create<FilePickerState>((set, get) => ({
getSelectedPaths: () => { getSelectedPaths: () => {
return Array.from(get().selectedPaths); return Array.from(get().selectedPaths);
}, },
runScript: async (path: string) => {
try {
const result = await scriptsApi.run(path);
set((state) => {
const newMap = new Map(state.runningScripts);
newMap.set(path, result);
return { runningScripts: newMap };
});
} catch (e) {
console.error("Failed to run script:", e);
}
},
})); }));
+10 -3
View File
@@ -41,6 +41,8 @@ const convertTreeToFileNode = (data: any[]): FileNode => {
export const TemplatesPage = () => { export const TemplatesPage = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const selectedPaths = useFilePickerStore((s) => s.selectedPaths); const selectedPaths = useFilePickerStore((s) => s.selectedPaths);
const runningScripts = useFilePickerStore((s) => s.runningScripts);
const runScript = useFilePickerStore((s) => s.runScript);
const [files, setFiles] = useState<FileNode | null>(null); const [files, setFiles] = useState<FileNode | null>(null);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
@@ -57,6 +59,12 @@ export const TemplatesPage = () => {
.finally(() => setLoading(false)); .finally(() => setLoading(false));
}, []); }, []);
const handleRun = async (path: string) => {
await runScript(path);
};
const runningCount = runningScripts.size;
return ( return (
<div <div
style={{ style={{
@@ -91,8 +99,7 @@ export const TemplatesPage = () => {
> >
<FiPlay size={13} color="#61c454" /> <FiPlay size={13} color="#61c454" />
<span style={{ fontSize: "12px", color: "var(--text-secondary)" }}> <span style={{ fontSize: "12px", color: "var(--text-secondary)" }}>
{selectedPaths.size} script{selectedPaths.size !== 1 ? "s" : ""}{" "} {runningCount} script{runningCount !== 1 ? "s" : ""} running
running
</span> </span>
</div> </div>
@@ -145,7 +152,7 @@ export const TemplatesPage = () => {
/> />
</div> </div>
) : files ? ( ) : files ? (
<FilePicker files={files} /> <FilePicker files={files} onRun={handleRun} />
) : null} ) : null}
</div> </div>
</div> </div>