online-compiler-widget 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/FileStorage/obj/FileStorage.csproj.EntityFrameworkCore.targets +28 -0
- package/README.md +1 -0
- package/eslint.config.js +26 -0
- package/index.html +13 -0
- package/openapitools.json +7 -0
- package/package.json +36 -0
- package/pnpm-workspace.yaml +2 -0
- package/public/vite.svg +1 -0
- package/src/App.css +49 -0
- package/src/App.tsx +84 -0
- package/src/api/.openapi-generator/FILES +25 -0
- package/src/api/.openapi-generator/VERSION +1 -0
- package/src/api/.openapi-generator-ignore +23 -0
- package/src/api/api.ts +1312 -0
- package/src/api/base.ts +62 -0
- package/src/api/common.ts +113 -0
- package/src/api/configuration.ts +121 -0
- package/src/api/docs/CompilationError.md +26 -0
- package/src/api/docs/CompileRequest.md +22 -0
- package/src/api/docs/CompileResult.md +28 -0
- package/src/api/docs/CompilerApi.md +263 -0
- package/src/api/docs/CreateFileDto.md +22 -0
- package/src/api/docs/CreateProjectRequest.md +20 -0
- package/src/api/docs/FileApi.md +274 -0
- package/src/api/docs/ProcessStatus.md +28 -0
- package/src/api/docs/ProjectApi.md +362 -0
- package/src/api/docs/ProjectInfo.md +24 -0
- package/src/api/docs/ProjectStats.md +28 -0
- package/src/api/docs/RenameFileDto.md +20 -0
- package/src/api/docs/RenameProjectRequest.md +20 -0
- package/src/api/docs/RunRequest.md +24 -0
- package/src/api/docs/RunResult.md +30 -0
- package/src/api/docs/RunningProjectInfo.md +26 -0
- package/src/api/docs/UpdateFileDto.md +20 -0
- package/src/api/git_push.sh +57 -0
- package/src/api/index.ts +18 -0
- package/src/assets/Badge.svg +17 -0
- package/src/assets/closeIcon.svg +20 -0
- package/src/assets/documentIcon.svg +11 -0
- package/src/assets/history.svg +11 -0
- package/src/assets/output.svg +12 -0
- package/src/assets/plus.svg +20 -0
- package/src/assets/react.svg +1 -0
- package/src/assets/save-icon.svg +11 -0
- package/src/assets/shield.svg +10 -0
- package/src/assets/start.svg +11 -0
- package/src/assets/stop.svg +11 -0
- package/src/components/CompilerWidget.module.scss +169 -0
- package/src/components/CompilerWidget.tsx +279 -0
- package/src/components/FileExplorer.module.scss +372 -0
- package/src/components/FileExplorer.tsx +285 -0
- package/src/components/MonacoEditorWrapper.module.scss +29 -0
- package/src/components/MonacoEditorWrapper.tsx +74 -0
- package/src/components/OutputPanel.module.scss +123 -0
- package/src/components/OutputPanel.tsx +53 -0
- package/src/components/RunContainer.module.scss +150 -0
- package/src/components/RunContainer.tsx +34 -0
- package/src/hooks/useCompiler.ts +228 -0
- package/src/hooks/useInitialNodes.ts +0 -0
- package/src/index.css +78 -0
- package/src/main.tsx +7 -0
- package/src/types/EditorDocument.ts +8 -0
- package/swagger.json +1020 -0
- package/tsconfig.app.json +29 -0
- package/tsconfig.json +7 -0
- package/tsconfig.node.json +26 -0
- package/vite.config.ts +8 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
// src/components/CompilerWidget.tsx
|
|
2
|
+
import React, { memo, useState, useEffect, useRef, useLayoutEffect } from 'react';
|
|
3
|
+
import { Handle, Position, NodeResizer, useReactFlow } from '@xyflow/react';
|
|
4
|
+
import { FileExplorer } from './FileExplorer';
|
|
5
|
+
import { MonacoEditorWrapper } from './MonacoEditorWrapper';
|
|
6
|
+
import { OutputPanel } from './OutputPanel';
|
|
7
|
+
import cls from './CompilerWidget.module.scss';
|
|
8
|
+
import { useCompiler } from '../hooks/useCompiler';
|
|
9
|
+
|
|
10
|
+
import ShieldIcon from "../assets/shield.svg?react";
|
|
11
|
+
import BadgeIcon from "../assets/Badge.svg?react";
|
|
12
|
+
import CloseIcon from "../assets/closeIcon.svg?react";
|
|
13
|
+
|
|
14
|
+
import type { EditorDocument } from '../types/EditorDocument';
|
|
15
|
+
import { RunContainer } from "./RunContainer.tsx";
|
|
16
|
+
|
|
17
|
+
interface GetInfoModel {
|
|
18
|
+
"widgetId": number,
|
|
19
|
+
"userId": number,
|
|
20
|
+
"role": string,
|
|
21
|
+
"config": string, // json
|
|
22
|
+
"board": {
|
|
23
|
+
"id": number,
|
|
24
|
+
"name": string,
|
|
25
|
+
"parentId": number
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface ConfigModel {
|
|
30
|
+
// ... то, как мы будем общаться с потребителем
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface CompilerWidgetProps {
|
|
34
|
+
id: string;
|
|
35
|
+
data?: {
|
|
36
|
+
initialFiles?: Record<string, string>;
|
|
37
|
+
language?: 'csharp' | 'js';
|
|
38
|
+
};
|
|
39
|
+
setNodeHeight?: (id: string, height: number) => void;
|
|
40
|
+
getInfo?: (info: GetInfoModel) => void;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const CompilerWidget: React.FC<CompilerWidgetProps> = ({ id, data, setNodeHeight }) => {
|
|
44
|
+
const {
|
|
45
|
+
documents,
|
|
46
|
+
selectedDocument,
|
|
47
|
+
selectedId,
|
|
48
|
+
setSelectedId,
|
|
49
|
+
setDocumentContent,
|
|
50
|
+
addDocument,
|
|
51
|
+
deleteDocument,
|
|
52
|
+
updateDocument,
|
|
53
|
+
output,
|
|
54
|
+
history,
|
|
55
|
+
run,
|
|
56
|
+
stop,
|
|
57
|
+
saveAll
|
|
58
|
+
} = useCompiler(
|
|
59
|
+
data?.initialFiles || {
|
|
60
|
+
'Program.cs': '// Write your code here\nConsole.WriteLine("Hello, World!");',
|
|
61
|
+
}
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
// useReactFlow мы оставляем, но НЕ полагаемся на updateNodeDimensions — вызываем опционально
|
|
65
|
+
const rf = useReactFlow();
|
|
66
|
+
const maybeUpdateNodeDimensions = (nodeId: string) => {
|
|
67
|
+
if (rf && typeof (rf as any).updateNodeDimensions === 'function') {
|
|
68
|
+
try {
|
|
69
|
+
(rf as any).updateNodeDimensions(nodeId);
|
|
70
|
+
} catch {
|
|
71
|
+
// игнорируем ошибки — основной механизм через setNodeHeight
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const currentDocument: EditorDocument | null = selectedDocument ?? (documents[0] ?? null);
|
|
77
|
+
const currentCode = currentDocument?.content ?? '';
|
|
78
|
+
const currentLanguage =
|
|
79
|
+
currentDocument?.language ??
|
|
80
|
+
(data?.language === 'js' ? 'javascript' : 'csharp');
|
|
81
|
+
|
|
82
|
+
// panel widths
|
|
83
|
+
const [leftWidth, setLeftWidth] = useState(180);
|
|
84
|
+
const [rightWidth, setRightWidth] = useState(220);
|
|
85
|
+
|
|
86
|
+
const [collapsed, setCollapsed] = useState(false);
|
|
87
|
+
|
|
88
|
+
const handleCodeChange = (newCode: string) => {
|
|
89
|
+
if (currentDocument) {
|
|
90
|
+
setDocumentContent(currentDocument.id, newCode);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// Обработчик переименования файла
|
|
95
|
+
const handleRename = (id: string, newName: string) => {
|
|
96
|
+
const doc = documents.find(d => d.id === id);
|
|
97
|
+
if (!doc || newName === doc.name) return;
|
|
98
|
+
|
|
99
|
+
updateDocument(id, {
|
|
100
|
+
name: newName
|
|
101
|
+
});
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
105
|
+
const resizeObserverRef = useRef<ResizeObserver | null>(null);
|
|
106
|
+
|
|
107
|
+
// Синхронизировать высоту ноды при изменении контейнера
|
|
108
|
+
useLayoutEffect(() => {
|
|
109
|
+
if (!containerRef.current || !setNodeHeight) return;
|
|
110
|
+
|
|
111
|
+
// initial set
|
|
112
|
+
const rect = containerRef.current.getBoundingClientRect();
|
|
113
|
+
const initialHeight = collapsed ? 42 : Math.round(rect.height);
|
|
114
|
+
setNodeHeight(id, initialHeight);
|
|
115
|
+
maybeUpdateNodeDimensions(id);
|
|
116
|
+
|
|
117
|
+
// Создаём ResizeObserver, если доступен
|
|
118
|
+
const ro = new ResizeObserver((entries) => {
|
|
119
|
+
for (const entry of entries) {
|
|
120
|
+
if (entry.target === containerRef.current) {
|
|
121
|
+
const h = collapsed ? 42 : Math.round(entry.contentRect.height);
|
|
122
|
+
setNodeHeight(id, h);
|
|
123
|
+
// опционально форсируем перерисовку XYFlow (если API доступен)
|
|
124
|
+
maybeUpdateNodeDimensions(id);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
resizeObserverRef.current = ro;
|
|
130
|
+
ro.observe(containerRef.current);
|
|
131
|
+
|
|
132
|
+
return () => {
|
|
133
|
+
ro.disconnect();
|
|
134
|
+
resizeObserverRef.current = null;
|
|
135
|
+
};
|
|
136
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
137
|
+
}, [id, setNodeHeight, collapsed]);
|
|
138
|
+
|
|
139
|
+
useEffect(() => {
|
|
140
|
+
// при смене collapsed форсируем одноразовый пересчёт (на случай, если ResizeObserver не сработал моментально)
|
|
141
|
+
if (!containerRef.current || !setNodeHeight) return;
|
|
142
|
+
const h = collapsed ? 42 : Math.round(containerRef.current.getBoundingClientRect().height);
|
|
143
|
+
setNodeHeight(id, h);
|
|
144
|
+
maybeUpdateNodeDimensions(id);
|
|
145
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
146
|
+
}, [collapsed]);
|
|
147
|
+
|
|
148
|
+
const toggleCollapsed = () => setCollapsed(prev => !prev);
|
|
149
|
+
|
|
150
|
+
// Manual panel resizing: убираем прямые вызовы updateNodeDimensions, теперь ResizeObserver всё подхватит
|
|
151
|
+
const startResizing = (
|
|
152
|
+
e: React.MouseEvent,
|
|
153
|
+
setter: (v: number) => void,
|
|
154
|
+
startWidth: number,
|
|
155
|
+
direction: "left" | "right",
|
|
156
|
+
min = 120,
|
|
157
|
+
max = 1000
|
|
158
|
+
) => {
|
|
159
|
+
e.preventDefault();
|
|
160
|
+
e.stopPropagation();
|
|
161
|
+
|
|
162
|
+
const startX = e.clientX;
|
|
163
|
+
|
|
164
|
+
const onMove = (ev: MouseEvent) => {
|
|
165
|
+
const dx = ev.clientX - startX;
|
|
166
|
+
const newWidth = direction === "left" ? startWidth + dx : startWidth - dx;
|
|
167
|
+
setter(Math.min(Math.max(newWidth, min), max));
|
|
168
|
+
// НЕ вызываем updateNodeDimensions здесь — ResizeObserver увидит изменение размеров DOM и обновит ноду
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const onUp = () => {
|
|
172
|
+
window.removeEventListener('mousemove', onMove);
|
|
173
|
+
window.removeEventListener('mouseup', onUp);
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
window.addEventListener('mousemove', onMove);
|
|
177
|
+
window.addEventListener('mouseup', onUp);
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
return (
|
|
181
|
+
<div
|
|
182
|
+
ref={containerRef}
|
|
183
|
+
className={`${cls.widget} ${collapsed ? cls.collapsed : ''}`}
|
|
184
|
+
>
|
|
185
|
+
<Handle type="target" position={Position.Top} />
|
|
186
|
+
|
|
187
|
+
{/* GLOBAL NodeResizer (xyflow) — виден только если не collapsed */}
|
|
188
|
+
{!collapsed && (
|
|
189
|
+
<NodeResizer
|
|
190
|
+
minWidth={600}
|
|
191
|
+
minHeight={300}
|
|
192
|
+
// Некоторые версии NodeResizer поддержуют onResize/onResizeEnd, некоторые — нет.
|
|
193
|
+
// Мы полагаемся на ResizeObserver для синхронизации высоты, так что не обязаны использовать коллбэки.
|
|
194
|
+
/>
|
|
195
|
+
)}
|
|
196
|
+
|
|
197
|
+
{/* HEADER */}
|
|
198
|
+
<div className="drag-handle__custom">
|
|
199
|
+
<div className={cls.header}>
|
|
200
|
+
<h4>Code Block</h4>
|
|
201
|
+
|
|
202
|
+
<div className={cls.shieldContainer}>
|
|
203
|
+
<ShieldIcon className={cls.shield} />
|
|
204
|
+
<BadgeIcon className={cls.badge} />
|
|
205
|
+
</div>
|
|
206
|
+
|
|
207
|
+
<button className={cls.closeButton} onClick={toggleCollapsed}>
|
|
208
|
+
{collapsed ? (
|
|
209
|
+
<svg width="16" height="16" viewBox="0 0 16 16">
|
|
210
|
+
<path d="M4 8h8M8 4v8" stroke="currentColor" strokeWidth="2" />
|
|
211
|
+
</svg>
|
|
212
|
+
) : (
|
|
213
|
+
<CloseIcon className={cls.close} />
|
|
214
|
+
)}
|
|
215
|
+
</button>
|
|
216
|
+
</div>
|
|
217
|
+
</div>
|
|
218
|
+
|
|
219
|
+
{/* BODY */}
|
|
220
|
+
{!collapsed && (
|
|
221
|
+
<div className={cls.body}>
|
|
222
|
+
{/* LEFT PANEL */}
|
|
223
|
+
<div className={cls.panel} style={{ width: leftWidth }}>
|
|
224
|
+
<FileExplorer
|
|
225
|
+
documents={documents}
|
|
226
|
+
selectedId={selectedId}
|
|
227
|
+
onSelect={setSelectedId}
|
|
228
|
+
onAdd={addDocument}
|
|
229
|
+
onRename={handleRename} // Используем inline-редактирование
|
|
230
|
+
onDelete={deleteDocument}
|
|
231
|
+
/>
|
|
232
|
+
|
|
233
|
+
<div
|
|
234
|
+
className={cls.resizer}
|
|
235
|
+
onMouseDown={(e) =>
|
|
236
|
+
startResizing(e, setLeftWidth, leftWidth, "left")
|
|
237
|
+
}
|
|
238
|
+
/>
|
|
239
|
+
</div>
|
|
240
|
+
|
|
241
|
+
{/* CENTER */}
|
|
242
|
+
<div className={cls.centerPanel}>
|
|
243
|
+
{currentDocument ? (
|
|
244
|
+
<div className={cls.editCont}>
|
|
245
|
+
<MonacoEditorWrapper
|
|
246
|
+
code={currentCode}
|
|
247
|
+
language={currentLanguage}
|
|
248
|
+
onChange={handleCodeChange}
|
|
249
|
+
theme="vs-light"
|
|
250
|
+
/>
|
|
251
|
+
<RunContainer run={run} stop={stop} save={saveAll}/>
|
|
252
|
+
</div>
|
|
253
|
+
) : (
|
|
254
|
+
<div style={{ padding: 16, color: '#666' }}>
|
|
255
|
+
Нет открытого документа
|
|
256
|
+
</div>
|
|
257
|
+
)}
|
|
258
|
+
</div>
|
|
259
|
+
|
|
260
|
+
{/* RIGHT */}
|
|
261
|
+
<div className={cls.panel} style={{ width: rightWidth }}>
|
|
262
|
+
<OutputPanel output={output} history={history} />
|
|
263
|
+
|
|
264
|
+
<div
|
|
265
|
+
className={cls.resizer}
|
|
266
|
+
onMouseDown={(e) =>
|
|
267
|
+
startResizing(e, setRightWidth, rightWidth, "right")
|
|
268
|
+
}
|
|
269
|
+
/>
|
|
270
|
+
</div>
|
|
271
|
+
</div>
|
|
272
|
+
)}
|
|
273
|
+
|
|
274
|
+
<Handle type="source" position={Position.Bottom} />
|
|
275
|
+
</div>
|
|
276
|
+
);
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
export default memo(CompilerWidget);
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
$file-bg: #f5f5f5;
|
|
2
|
+
$file-border: #ddd;
|
|
3
|
+
$file-selected: #d0eaff;
|
|
4
|
+
|
|
5
|
+
.fileExplorer {
|
|
6
|
+
width: auto;
|
|
7
|
+
background: #F8FAFC;
|
|
8
|
+
border-right: 1px solid $file-border;
|
|
9
|
+
display: flex;
|
|
10
|
+
flex-direction: column;
|
|
11
|
+
height: 100%;
|
|
12
|
+
width: auto;
|
|
13
|
+
|
|
14
|
+
.header {
|
|
15
|
+
padding: 6px 7px;
|
|
16
|
+
display: flex;
|
|
17
|
+
justify-content: start;
|
|
18
|
+
align-items: center;
|
|
19
|
+
background: #F1F5F9;
|
|
20
|
+
border-bottom: 1px solid #E2E8F0;
|
|
21
|
+
min-height: 21px;
|
|
22
|
+
|
|
23
|
+
span {
|
|
24
|
+
font-family: Arial;
|
|
25
|
+
font-weight: 400;
|
|
26
|
+
font-size: 6.18px;
|
|
27
|
+
margin-right: 1.7px;
|
|
28
|
+
align-items: center;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.documentIcon{
|
|
32
|
+
height: 12px;
|
|
33
|
+
margin-right: 1.5px;
|
|
34
|
+
|
|
35
|
+
svg {
|
|
36
|
+
height: 100%;
|
|
37
|
+
width: auto;
|
|
38
|
+
display: block;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
button {
|
|
43
|
+
all: unset;
|
|
44
|
+
cursor: pointer;
|
|
45
|
+
display: inline-flex;
|
|
46
|
+
align-items: center;
|
|
47
|
+
justify-content: center;
|
|
48
|
+
padding: 0;
|
|
49
|
+
margin: 0;
|
|
50
|
+
outline: none;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.addButton {
|
|
54
|
+
height: auto;
|
|
55
|
+
margin-left: auto;
|
|
56
|
+
.plusIcon {
|
|
57
|
+
height: 13px;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
&:hover {
|
|
61
|
+
opacity: 0.8;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.fileList {
|
|
67
|
+
list-style: none;
|
|
68
|
+
margin: 0;
|
|
69
|
+
padding: 0;
|
|
70
|
+
overflow-y: auto;
|
|
71
|
+
flex-grow: 1;
|
|
72
|
+
padding: 4.12px;
|
|
73
|
+
|
|
74
|
+
.fileItem {
|
|
75
|
+
padding: 3.74px 4.12px;
|
|
76
|
+
cursor: pointer;
|
|
77
|
+
display: flex;
|
|
78
|
+
align-items: center;
|
|
79
|
+
gap: 4.12px;
|
|
80
|
+
transition: background 0.1s;
|
|
81
|
+
position: relative;
|
|
82
|
+
border-radius: 2px;
|
|
83
|
+
|
|
84
|
+
&:hover {
|
|
85
|
+
background: #e9e9e9;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
&.selected {
|
|
89
|
+
background: #DBEAFE;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
&.modified {
|
|
93
|
+
.modifiedDot {
|
|
94
|
+
display: inline-block;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.fileIcon {
|
|
99
|
+
font-size: 7px;
|
|
100
|
+
margin-right: 3px;
|
|
101
|
+
flex-shrink: 0;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.nameContainer {
|
|
105
|
+
display: flex;
|
|
106
|
+
align-items: center;
|
|
107
|
+
gap: 4px;
|
|
108
|
+
flex: 1;
|
|
109
|
+
min-width: 0;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.itemText {
|
|
113
|
+
font-size: 7px;
|
|
114
|
+
font-family: sans-serif;
|
|
115
|
+
color: #1C398E;
|
|
116
|
+
align-items: center;
|
|
117
|
+
white-space: nowrap;
|
|
118
|
+
overflow: hidden;
|
|
119
|
+
text-overflow: ellipsis;
|
|
120
|
+
flex: 1;
|
|
121
|
+
line-height: 14px;
|
|
122
|
+
padding: 0;
|
|
123
|
+
margin: 0;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.modifiedDot {
|
|
127
|
+
display: none;
|
|
128
|
+
color: #ff6b6b;
|
|
129
|
+
font-size: 12px;
|
|
130
|
+
flex-shrink: 0;
|
|
131
|
+
animation: pulse 2s infinite;
|
|
132
|
+
|
|
133
|
+
&:hover {
|
|
134
|
+
color: #ff5252;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.editContainer {
|
|
139
|
+
flex: 1;
|
|
140
|
+
min-width: 0;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
@keyframes pulse {
|
|
147
|
+
0% {
|
|
148
|
+
opacity: 1;
|
|
149
|
+
}
|
|
150
|
+
50% {
|
|
151
|
+
opacity: 0.5;
|
|
152
|
+
}
|
|
153
|
+
100% {
|
|
154
|
+
opacity: 1;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.moreBtn {
|
|
159
|
+
margin-left: auto;
|
|
160
|
+
background: none;
|
|
161
|
+
border: none;
|
|
162
|
+
font-size: 12px;
|
|
163
|
+
padding: 2px 6px;
|
|
164
|
+
cursor: pointer;
|
|
165
|
+
opacity: 0.6;
|
|
166
|
+
outline: none;
|
|
167
|
+
border-radius: 3px;
|
|
168
|
+
flex-shrink: 0;
|
|
169
|
+
|
|
170
|
+
&:hover {
|
|
171
|
+
opacity: 1;
|
|
172
|
+
background: rgba(0, 0, 0, 0.05);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.clickZone {
|
|
177
|
+
display: flex;
|
|
178
|
+
align-items: center;
|
|
179
|
+
flex-grow: 1;
|
|
180
|
+
gap: 4px;
|
|
181
|
+
min-width: 0;
|
|
182
|
+
padding: 0;
|
|
183
|
+
margin: 0;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.contextMenu {
|
|
187
|
+
position: absolute;
|
|
188
|
+
right: 4px;
|
|
189
|
+
top: 28px;
|
|
190
|
+
background: #F1F5F9;
|
|
191
|
+
border: 1px solid #444;
|
|
192
|
+
border-radius: 4px;
|
|
193
|
+
padding: 4px 0;
|
|
194
|
+
min-width: 140px;
|
|
195
|
+
z-index: 100;
|
|
196
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.menuItem {
|
|
200
|
+
padding: 6px 10px;
|
|
201
|
+
cursor: pointer;
|
|
202
|
+
white-space: nowrap;
|
|
203
|
+
font-size: 14px;
|
|
204
|
+
|
|
205
|
+
&:hover {
|
|
206
|
+
background: #DBEAFE;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// СТИЛИ ДЛЯ INPUT ПРИ РЕДАКТИРОВАНИИ
|
|
211
|
+
.editInput {
|
|
212
|
+
flex: 1;
|
|
213
|
+
min-width: 0;
|
|
214
|
+
background: #ffffff;
|
|
215
|
+
border: 1px solid #0078d4;
|
|
216
|
+
border-radius: 2px;
|
|
217
|
+
padding: 0 2px;
|
|
218
|
+
margin: 0;
|
|
219
|
+
font-size: 7px;
|
|
220
|
+
font-family: sans-serif;
|
|
221
|
+
color: #1C398E;
|
|
222
|
+
outline: none;
|
|
223
|
+
height: 14px;
|
|
224
|
+
line-height: 12px;
|
|
225
|
+
box-sizing: border-box;
|
|
226
|
+
width: 100%;
|
|
227
|
+
max-width: 100%;
|
|
228
|
+
|
|
229
|
+
&:focus {
|
|
230
|
+
border-color: #0078d4;
|
|
231
|
+
box-shadow: 0 0 0 1px rgba(0, 120, 212, 0.3);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Состояние редактирования - скрываем кнопку меню
|
|
236
|
+
.fileItem:has(.editInput) {
|
|
237
|
+
.moreBtn {
|
|
238
|
+
display: none;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
&:hover {
|
|
242
|
+
background: transparent;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.clickZone {
|
|
246
|
+
cursor: default;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// СТИЛИ ДЛЯ МОДАЛЬНОГО ОКНА
|
|
251
|
+
.modalOverlay {
|
|
252
|
+
position: fixed;
|
|
253
|
+
top: 0;
|
|
254
|
+
left: 0;
|
|
255
|
+
right: 0;
|
|
256
|
+
bottom: 0;
|
|
257
|
+
background: rgba(0, 0, 0, 0.5);
|
|
258
|
+
display: flex;
|
|
259
|
+
align-items: center;
|
|
260
|
+
justify-content: center;
|
|
261
|
+
z-index: 1000;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.modalContent {
|
|
265
|
+
background: white;
|
|
266
|
+
padding: 24px;
|
|
267
|
+
border-radius: 8px;
|
|
268
|
+
min-width: 320px;
|
|
269
|
+
max-width: 400px;
|
|
270
|
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
|
271
|
+
|
|
272
|
+
h3 {
|
|
273
|
+
margin: 0 0 20px 0;
|
|
274
|
+
font-size: 16px;
|
|
275
|
+
font-weight: 600;
|
|
276
|
+
color: #333;
|
|
277
|
+
text-align: center;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.formGroup {
|
|
282
|
+
margin-bottom: 16px;
|
|
283
|
+
|
|
284
|
+
label {
|
|
285
|
+
display: block;
|
|
286
|
+
margin-bottom: 6px;
|
|
287
|
+
font-size: 13px;
|
|
288
|
+
font-weight: 500;
|
|
289
|
+
color: #555;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
input {
|
|
293
|
+
width: 100%;
|
|
294
|
+
padding: 10px 12px;
|
|
295
|
+
border: 1px solid #ddd;
|
|
296
|
+
border-radius: 4px;
|
|
297
|
+
font-size: 14px;
|
|
298
|
+
box-sizing: border-box;
|
|
299
|
+
transition: border-color 0.2s, box-shadow 0.2s;
|
|
300
|
+
|
|
301
|
+
&:focus {
|
|
302
|
+
outline: none;
|
|
303
|
+
border-color: #0078d4;
|
|
304
|
+
box-shadow: 0 0 0 2px rgba(0, 120, 212, 0.1);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
&.error {
|
|
308
|
+
border-color: #d32f2f;
|
|
309
|
+
|
|
310
|
+
&:focus {
|
|
311
|
+
border-color: #d32f2f;
|
|
312
|
+
box-shadow: 0 0 0 2px rgba(211, 47, 47, 0.1);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.helpText {
|
|
318
|
+
font-size: 12px;
|
|
319
|
+
color: #666;
|
|
320
|
+
margin-top: 4px;
|
|
321
|
+
margin-bottom: 8px;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.errorMessage {
|
|
325
|
+
color: #d32f2f;
|
|
326
|
+
font-size: 12px;
|
|
327
|
+
margin-top: 4px;
|
|
328
|
+
font-weight: 500;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.modalActions {
|
|
333
|
+
display: flex;
|
|
334
|
+
justify-content: flex-end;
|
|
335
|
+
gap: 12px;
|
|
336
|
+
margin-top: 24px;
|
|
337
|
+
|
|
338
|
+
button {
|
|
339
|
+
padding: 10px 20px;
|
|
340
|
+
border: none;
|
|
341
|
+
border-radius: 4px;
|
|
342
|
+
cursor: pointer;
|
|
343
|
+
font-size: 14px;
|
|
344
|
+
font-weight: 500;
|
|
345
|
+
transition: all 0.2s;
|
|
346
|
+
min-width: 80px;
|
|
347
|
+
|
|
348
|
+
&.cancelBtn {
|
|
349
|
+
background: #f5f5f5;
|
|
350
|
+
color: #333;
|
|
351
|
+
|
|
352
|
+
&:hover {
|
|
353
|
+
background: #e9e9e9;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
&.confirmBtn {
|
|
358
|
+
background: #0078d4;
|
|
359
|
+
color: white;
|
|
360
|
+
|
|
361
|
+
&:hover:not(:disabled) {
|
|
362
|
+
background: #106ebe;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
&:disabled {
|
|
366
|
+
background: #ccc;
|
|
367
|
+
cursor: not-allowed;
|
|
368
|
+
opacity: 0.6;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|