Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FROM node:24-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
11 changes: 8 additions & 3 deletions src/pages/components/ui/Buttons.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { Play } from 'lucide-react';

export function SubmitButton() {
export function SubmitButton({ onClick, disabled }) {
return (
<button className="btn btn-submit" title="Отправить решение на проверку">
Submit
<button
className="btn btn-submit"
title="Отправить решение на проверку"
onClick={onClick}
disabled={disabled}
>
{disabled ? 'Отправка...' : 'Submit'}
</button>
);
}
Expand Down
144 changes: 93 additions & 51 deletions src/pages/components/workspace/LeftWorkspace.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,29 @@ import { useState, useRef } from 'react';
import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels";
import Editor from "@monaco-editor/react";
import { Code, Terminal, Import, RotateCcw, Expand } from 'lucide-react';

import PanelHeader from '../ui/PanelHeader';
import { RunButton, SubmitButton } from '../ui/Buttons';
import Timer from '../ui/Timer';
import ConfirmModal from '../ui/ConfirmModal';

const DEFAULT_CODE = "# Напишите ваш код здесь...\n";

export default function LeftWorkspace({ isDarkMode, position = 'left' }) {
const LANGUAGE_IDS = {
python: 71,
cpp: 54,
javascript: 63,
};

export default function LeftWorkspace({ isDarkMode, position = 'left', problemId = 6 }) {
const [language, setLanguage] = useState('python');
const [code, setCode] = useState(DEFAULT_CODE);
const [showResetModal, setShowResetModal] = useState(false);

const [testResults, setTestResults] = useState(null);
const [isSubmitting, setIsSubmitting] = useState(false);
const [isCollapsed, setIsCollapsed] = useState(false);
const panelRef = useRef(null);
const fileInputRef = useRef(null);

const codeContainerRef = useRef(null);
const codeContainerRef = useRef(null);

const handleFileImport = (e) => {
const file = e.target.files[0];
Expand All @@ -43,20 +48,55 @@ export default function LeftWorkspace({ isDarkMode, position = 'left' }) {
}
};

const handleSubmit = async () => {
setIsSubmitting(true);
setTestResults('Отправка решения...');
try {
const blob = new Blob([code], { type: 'text/plain' });
const file = new File([blob], 'solution.py');
const formData = new FormData();
formData.append('problemId', problemId);
formData.append('languageId', LANGUAGE_IDS[language]);
formData.append('file', file);

const response = await fetch('/submissions/upload', {
method: 'POST',
body: formData,
});
const submissionId = await response.text();
setTestResults('Решение в очереди, ожидаем результат...');

const wsHost = window.location.hostname;
const ws = new WebSocket(`ws://${wsHost}:8080/ws/results?submissionId=${submissionId}`);
ws.onmessage = (event) => {
setTestResults(event.data);
setIsSubmitting(false);
ws.close();
};
ws.onerror = () => {
setTestResults('Ошибка WebSocket соединения');
setIsSubmitting(false);
};
} catch (err) {
setTestResults('Ошибка: ' + err.message);
setIsSubmitting(false);
}
};

return (
<Panel
<Panel
ref={panelRef}
collapsible={true}
collapsedSize={3}
onCollapse={() => setIsCollapsed(true)}
onExpand={() => setIsCollapsed(false)}
defaultSize={50}
minSize={4}
defaultSize={50}
minSize={4}
className={`column-container ${position}-column`}
>
{isCollapsed ? (
<div
className="collapsed-vertical-bar"
<div
className="collapsed-vertical-bar"
onClick={() => panelRef.current?.expand()}
title="Развернуть"
>
Expand All @@ -67,43 +107,37 @@ export default function LeftWorkspace({ isDarkMode, position = 'left' }) {
) : (
<PanelGroup direction="vertical">
<Panel defaultSize={70} minSize={15} className="panel">
<div
ref={codeContainerRef}
<div
ref={codeContainerRef}
style={{ display: 'flex', flexDirection: 'column', height: '100%', backgroundColor: 'var(--bg-panel)' }}
>
<PanelHeader title="Код" Icon={Code} />

<div className="toolbar-container">
<div className="toolbar">
<RunButton />
<SubmitButton />
<Timer />

<select
className="language-select"
value={language}
onChange={(e) => setLanguage(e.target.value)}
>
<option value="python">Python</option>
<option value="cpp">C++</option>
<option value="javascript">JavaScript</option>
</select>

<button className="btn icon-btn" onClick={() => setShowResetModal(true)} title="Сбросить код">
<RotateCcw size={16} />
</button>

<input type="file" ref={fileInputRef} style={{ display: 'none' }} onChange={handleFileImport} accept=".js,.py,.cpp,.txt" />
<button className="btn icon-btn" onClick={() => fileInputRef.current.click()} title="Загрузить из файла">
<Import size={16} />
</button>

<button className="btn icon-btn" onClick={toggleEditorFullscreen} title="Развернуть/свернуть окно">
<Expand size={16} />
</button>
</div>
<div className="toolbar">
<RunButton />
<SubmitButton onClick={handleSubmit} disabled={isSubmitting} />
<Timer />
<select
className="language-select"
value={language}
onChange={(e) => setLanguage(e.target.value)}
>
<option value="python">Python</option>
<option value="cpp">C++</option>
<option value="javascript">JavaScript</option>
</select>
<button className="btn icon-btn" onClick={() => setShowResetModal(true)} title="Сбросить код">
<RotateCcw size={16} />
</button>
<input type="file" ref={fileInputRef} style={{ display: 'none' }} onChange={handleFileImport} accept=".js,.py,.cpp,.txt" />
<button className="btn icon-btn" onClick={() => fileInputRef.current.click()} title="Загрузить из файла">
<Import size={16} />
</button>
<button className="btn icon-btn" onClick={toggleEditorFullscreen} title="Развернуть/свернуть окно">
<Expand size={16} />
</button>
</div>
</div>

<div className="editor-wrapper">
<Editor
height="100%"
Expand All @@ -116,21 +150,29 @@ export default function LeftWorkspace({ isDarkMode, position = 'left' }) {
</div>
</div>
</Panel>

<PanelResizeHandle className="resizer-horizontal">
<div className="resizer-line-horizontal"></div>
<div className="resizer-line-horizontal"></div>
</PanelResizeHandle>

<Panel defaultSize={30} minSize={15} className="panel">
<PanelHeader title="Результаты тестов" Icon={Terminal} />
<div className="panel-content">
<p style={{color: 'var(--text-secondary)', fontStyle: 'italic'}}>Здесь будут результаты запуска...</p>
</div>
<PanelHeader title="Результаты тестов" Icon={Terminal} />
<div className="panel-content">
{testResults ? (
<pre style={{
color: testResults.includes('Accepted') ? '#4caf50' : 'var(--text-primary)',
whiteSpace: 'pre-wrap'
}}>
{testResults}
</pre>
) : (
<p style={{ color: 'var(--text-secondary)', fontStyle: 'italic' }}>
Здесь будут результаты запуска...
</p>
)}
</div>
</Panel>
</PanelGroup>
)}

<ConfirmModal
<ConfirmModal
isOpen={showResetModal}
onClose={() => setShowResetModal(false)}
onConfirm={confirmReset}
Expand Down
Loading