Compare commits
33 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
1b060c185b | |
|
|
bfa07d0058 | |
|
|
789ebe1133 | |
|
|
55117d01c2 | |
|
|
0a3f3a8357 | |
|
|
453e708e3f | |
|
|
61d46edf69 | |
|
|
f5ddac1b57 | |
|
|
d836164ea5 | |
|
|
3ac4493f4c | |
|
|
d6f9f9cdf7 | |
|
|
88a2841a0b | |
|
|
175c2f5522 | |
|
|
716217c09d | |
|
|
ce72117722 | |
|
|
cb83e9632e | |
|
|
82cdd14bb2 | |
|
|
1c7ded5d6d | |
|
|
d33ca318c9 | |
|
|
21450c0d99 | |
|
|
6c5ae38361 | |
|
|
43e0cea91b | |
|
|
a7a9593873 | |
|
|
00c22ac2be | |
|
|
6665d418b8 | |
|
|
66f8ee790b | |
|
|
8033722fe3 | |
|
|
799385ee52 | |
|
|
be3be22b02 | |
|
|
757f255820 | |
|
|
33e346663b | |
|
|
db176c1b56 | |
|
|
55bc4738c7 |
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"extends": "next/core-web-vitals"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
const BoardSettings = () => {};
|
||||||
|
|
||||||
|
export default BoardSettings;
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
import Editor from "@monaco-editor/react";
|
||||||
|
import example from "./example.cpp";
|
||||||
|
|
||||||
|
const CodeEditor = () => {
|
||||||
|
return (
|
||||||
|
<Editor
|
||||||
|
height="100vh" // тут фигня, у меня кусок белый остается, высоты ему не хватает
|
||||||
|
defaultLanguage="cpp"
|
||||||
|
theme="vs-dark"
|
||||||
|
defaultValue={example.value}
|
||||||
|
options={{
|
||||||
|
minimap: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CodeEditor;
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
const example = {
|
||||||
|
value: `void setup() {
|
||||||
|
pinMode(D0, OUTPUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
digitalWrite(D0, HIGH);
|
||||||
|
|
||||||
|
delay(1000);
|
||||||
|
|
||||||
|
digitalWrite(D0, LOW);
|
||||||
|
|
||||||
|
delay(2000);
|
||||||
|
}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default example;
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
import React from "react";
|
||||||
|
import { useDraggable, UseDraggableArguments } from "@dnd-kit/core";
|
||||||
|
|
||||||
|
const Draggable = (props: any) => {
|
||||||
|
const { attributes, listeners, setNodeRef, transform } = useDraggable({
|
||||||
|
id: `draggable-${props.data.type}`,
|
||||||
|
data: props.data,
|
||||||
|
});
|
||||||
|
|
||||||
|
const style = transform
|
||||||
|
? {
|
||||||
|
transform: `translate3d(${transform.x}px, ${transform.y}px, 0)`,
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button ref={setNodeRef} style={style} {...listeners} {...attributes}>
|
||||||
|
{props.children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Draggable;
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
import React from "react";
|
||||||
|
import { useDroppable } from "@dnd-kit/core";
|
||||||
|
|
||||||
|
function Droppable(props: any) {
|
||||||
|
const { isOver, setNodeRef } = useDroppable({
|
||||||
|
id: "droppable",
|
||||||
|
});
|
||||||
|
const style = {
|
||||||
|
color: isOver ? "green" : undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={setNodeRef} className={props.className}>
|
||||||
|
{props.children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Droppable;
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
import { useDroppable } from "@dnd-kit/core";
|
||||||
|
import { useState } from "react";
|
||||||
|
import ReactFlow, {
|
||||||
|
Background,
|
||||||
|
Controls,
|
||||||
|
ReactFlowInstance,
|
||||||
|
} from "react-flow-renderer";
|
||||||
|
|
||||||
|
import useStore from "../../lib/FlowEditor/FlowEditorStore";
|
||||||
|
|
||||||
|
import LedNode from "./nodes/IO/LedNode";
|
||||||
|
import SensorNode from "./nodes/IO/SensorNode";
|
||||||
|
|
||||||
|
import DelayNode from "./nodes/Logic/DelayNode";
|
||||||
|
import FinishNode from "./nodes/Logic/FinishNode";
|
||||||
|
import MathNode from "./nodes/Logic/MathNode";
|
||||||
|
import StartNode from "./nodes/Logic/StartNode";
|
||||||
|
|
||||||
|
const nodeTypes = {
|
||||||
|
led: LedNode,
|
||||||
|
sensor: SensorNode,
|
||||||
|
delay: DelayNode,
|
||||||
|
math: MathNode,
|
||||||
|
finish: FinishNode,
|
||||||
|
start: StartNode,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Flow = () => {
|
||||||
|
const [reactFlowInstance, setReactFlowInstance] =
|
||||||
|
useState<ReactFlowInstance>();
|
||||||
|
|
||||||
|
const onInit = (rfi: ReactFlowInstance) => {
|
||||||
|
setReactFlowInstance(rfi);
|
||||||
|
};
|
||||||
|
|
||||||
|
const { isOver, setNodeRef } = useDroppable({ id: "flow" });
|
||||||
|
|
||||||
|
const { nodes, edges, onNodesChange, onEdgesChange, onConnect } = useStore();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={setNodeRef} className="flow h-screen w-full">
|
||||||
|
<ReactFlow
|
||||||
|
nodes={nodes}
|
||||||
|
edges={edges}
|
||||||
|
nodeTypes={nodeTypes}
|
||||||
|
onNodesChange={onNodesChange}
|
||||||
|
onEdgesChange={onEdgesChange}
|
||||||
|
onConnect={onConnect}
|
||||||
|
onInit={onInit}
|
||||||
|
fitView
|
||||||
|
snapToGrid={true}
|
||||||
|
>
|
||||||
|
<Controls />
|
||||||
|
<Background />
|
||||||
|
</ReactFlow>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Flow;
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
import {
|
||||||
|
DndContext,
|
||||||
|
MouseSensor,
|
||||||
|
TouchSensor,
|
||||||
|
KeyboardSensor,
|
||||||
|
useSensor,
|
||||||
|
useSensors,
|
||||||
|
} from "@dnd-kit/core";
|
||||||
|
import { ReactFlowProvider, useReactFlow } from "react-flow-renderer";
|
||||||
|
import useStore from "../../lib/FlowEditor/FlowEditorStore";
|
||||||
|
import Flow from "./Flow";
|
||||||
|
import Panel from "./Panel";
|
||||||
|
|
||||||
|
const FlowEditorComponent = () => {
|
||||||
|
const { addNode } = useStore();
|
||||||
|
const reactFlowInstance = useReactFlow();
|
||||||
|
|
||||||
|
const sensors = useSensors(
|
||||||
|
useSensor(MouseSensor),
|
||||||
|
useSensor(TouchSensor),
|
||||||
|
useSensor(KeyboardSensor)
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleDragEnd = (event: any) => {
|
||||||
|
const { active, over } = event;
|
||||||
|
|
||||||
|
if (over && over.id == "flow") {
|
||||||
|
const activeType = active.data.current.type;
|
||||||
|
const eventType = event.activatorEvent.type;
|
||||||
|
|
||||||
|
var position = { x: 0, y: 0 };
|
||||||
|
if (eventType == "touchstart" || eventType == "touchend") {
|
||||||
|
const touch = event.activatorEvent.touches[0];
|
||||||
|
position = reactFlowInstance.project({
|
||||||
|
x: touch.clientX + event.delta.x - 220,
|
||||||
|
y: touch.clientY + event.delta.y - 60,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
position = reactFlowInstance.project({
|
||||||
|
x: event.activatorEvent.clientX + event.delta.x - 220,
|
||||||
|
y: event.activatorEvent.clientY + event.delta.y - 60,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addNode(activeType, position);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DndContext onDragEnd={handleDragEnd} sensors={sensors}>
|
||||||
|
<Panel />
|
||||||
|
<Flow />
|
||||||
|
</DndContext>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const FlowEditor = () => {
|
||||||
|
return (
|
||||||
|
<div className="flow-editor">
|
||||||
|
<ReactFlowProvider>
|
||||||
|
<FlowEditorComponent />
|
||||||
|
</ReactFlowProvider>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FlowEditor;
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
import { VscArrowLeft, VscArrowRight } from "react-icons/vsc";
|
||||||
|
import useStore from "../../lib/FlowEditor/FlowEditorStore";
|
||||||
|
import { LedNodeComponent, SensorNodeComponent } from "./nodes/IO/IO";
|
||||||
|
import { DelayNodeComponent, MathNodeComponent , StartNodeComponent, FinishNodeComponent } from "./nodes/Logic/Logic";
|
||||||
|
|
||||||
|
const Panel = () => {
|
||||||
|
const { panelClosed, togglePanelState } = useStore();
|
||||||
|
|
||||||
|
const PanelButton = () => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`flow-panel-button ${panelClosed ? "rotate-180" : ""}`}
|
||||||
|
onClick={togglePanelState}
|
||||||
|
>
|
||||||
|
<VscArrowLeft size={40} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<aside
|
||||||
|
className={`flow-panel ${panelClosed ? "w-0" : "w-60"} duration-300`}
|
||||||
|
>
|
||||||
|
<PanelButton />
|
||||||
|
{!panelClosed && (
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<SensorNodeComponent />
|
||||||
|
<LedNodeComponent />
|
||||||
|
|
||||||
|
<MathNodeComponent />
|
||||||
|
<DelayNodeComponent />
|
||||||
|
|
||||||
|
<StartNodeComponent />
|
||||||
|
<FinishNodeComponent />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</aside>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Panel;
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
import Draggable from "../../../DragNDrop/Draggable";
|
||||||
|
|
||||||
|
const groupColor = "bg-red-700";
|
||||||
|
|
||||||
|
const SensorNodeComponent = () => {
|
||||||
|
return (
|
||||||
|
<Draggable data={{ type: "sensor" }}>
|
||||||
|
<div className="node-component">
|
||||||
|
<div className={`node-component-handle ${groupColor}`} />
|
||||||
|
Сенсор
|
||||||
|
</div>
|
||||||
|
</Draggable>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const LedNodeComponent = () => {
|
||||||
|
return (
|
||||||
|
<Draggable data={{ type: "led" }}>
|
||||||
|
<div className="node-component">
|
||||||
|
<div className={`node-component-handle ${groupColor}`} />
|
||||||
|
Светодиод
|
||||||
|
</div>
|
||||||
|
</Draggable>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { SensorNodeComponent, LedNodeComponent };
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { Handle, Position } from "react-flow-renderer";
|
||||||
|
import {
|
||||||
|
bottomBigStyle,
|
||||||
|
bottomSmallStyle,
|
||||||
|
topBigStyle,
|
||||||
|
topDoubleRightStyle,
|
||||||
|
} from "../styles";
|
||||||
|
|
||||||
|
type LedNodeProps = {};
|
||||||
|
|
||||||
|
const LedNode = (props: LedNodeProps) => {
|
||||||
|
return (
|
||||||
|
<div className="led-node node-layout">
|
||||||
|
<Handle
|
||||||
|
style={topDoubleRightStyle}
|
||||||
|
type="target"
|
||||||
|
position={Position.Top}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="node-header">
|
||||||
|
<span className="node-title">Светодиод</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="node-content">
|
||||||
|
<select className="node-content-select nodrag">
|
||||||
|
<option>Включить</option>
|
||||||
|
<option>Выключить</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Handle
|
||||||
|
style={bottomSmallStyle}
|
||||||
|
type="source"
|
||||||
|
position={Position.Bottom}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LedNode;
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { Handle, Position } from "react-flow-renderer";
|
||||||
|
import {
|
||||||
|
bottomBigStyle,
|
||||||
|
bottomSmallStyle,
|
||||||
|
topBigStyle,
|
||||||
|
topDoubleRightStyle,
|
||||||
|
} from "../styles";
|
||||||
|
|
||||||
|
type SensorNodeProps = {};
|
||||||
|
|
||||||
|
const SensorNode = (props: SensorNodeProps) => {
|
||||||
|
return (
|
||||||
|
<div className="sensor-node node-layout">
|
||||||
|
<Handle
|
||||||
|
style={topDoubleRightStyle}
|
||||||
|
type="target"
|
||||||
|
position={Position.Top}
|
||||||
|
/>
|
||||||
|
<div className="node-header">
|
||||||
|
<span className="node-title">Сенсор</span>
|
||||||
|
</div>
|
||||||
|
<div className="node-content">
|
||||||
|
<select className="node-content-select nodrag">
|
||||||
|
<option>Температура</option>
|
||||||
|
<option>Освещенность</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Handle
|
||||||
|
style={bottomSmallStyle}
|
||||||
|
type="source"
|
||||||
|
position={Position.Bottom}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SensorNode;
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { Handle, Position } from "react-flow-renderer";
|
||||||
|
import {
|
||||||
|
bottomBigStyle,
|
||||||
|
bottomSmallStyle,
|
||||||
|
topBigStyle,
|
||||||
|
topDoubleRightStyle,
|
||||||
|
} from "../styles";
|
||||||
|
|
||||||
|
type DelayNodeProps = {};
|
||||||
|
|
||||||
|
const DelayNode = (props: DelayNodeProps) => {
|
||||||
|
return (
|
||||||
|
<div className="delay-node node-layout">
|
||||||
|
<Handle
|
||||||
|
style={topDoubleRightStyle}
|
||||||
|
type="target"
|
||||||
|
position={Position.Top}
|
||||||
|
/>
|
||||||
|
<div className="node-header">
|
||||||
|
<span className="node-title">Задержка</span>
|
||||||
|
</div>
|
||||||
|
<div className="node-content">
|
||||||
|
<select className="node-content-select nodrag">
|
||||||
|
<option>1 c</option>
|
||||||
|
<option>2 c</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Handle
|
||||||
|
style={bottomSmallStyle}
|
||||||
|
type="source"
|
||||||
|
position={Position.Bottom}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DelayNode;
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { Handle, Position } from "react-flow-renderer";
|
||||||
|
import {
|
||||||
|
topDoubleRightStyle,
|
||||||
|
} from "../styles";
|
||||||
|
|
||||||
|
type FinishNodeProps = {};
|
||||||
|
|
||||||
|
const FinishNode = (props: FinishNodeProps) => {
|
||||||
|
return (
|
||||||
|
<div className="finish-node node-layout">
|
||||||
|
<Handle
|
||||||
|
style={topDoubleRightStyle}
|
||||||
|
type="target"
|
||||||
|
position={Position.Top}
|
||||||
|
/>
|
||||||
|
<div className="node-content">
|
||||||
|
<span className="node-title">Конец программы</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FinishNode;
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
import Draggable from "../../../DragNDrop/Draggable";
|
||||||
|
|
||||||
|
const groupColor = "bg-blue-800";
|
||||||
|
|
||||||
|
const DelayNodeComponent = () => {
|
||||||
|
return (
|
||||||
|
<Draggable data={{ type: "delay" }}>
|
||||||
|
<div className="node-component">
|
||||||
|
<div className={`node-component-handle ${groupColor}`} />
|
||||||
|
Задержка
|
||||||
|
</div>
|
||||||
|
</Draggable>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const MathNodeComponent = () => {
|
||||||
|
return (
|
||||||
|
<Draggable data={{ type: "math" }}>
|
||||||
|
<div className="node-component">
|
||||||
|
<div className={`node-component-handle ${groupColor}`} />
|
||||||
|
Математика
|
||||||
|
</div>
|
||||||
|
</Draggable>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const FinishNodeComponent = () => {
|
||||||
|
return (
|
||||||
|
<Draggable data={{ type: "finish" }}>
|
||||||
|
<div className="node-component">
|
||||||
|
<div className={`node-component-handle ${groupColor}`} />
|
||||||
|
Конец
|
||||||
|
</div>
|
||||||
|
</Draggable>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const StartNodeComponent = () => {
|
||||||
|
return (
|
||||||
|
<Draggable data={{ type: "start" }}>
|
||||||
|
<div className="node-component">
|
||||||
|
<div className={`node-component-handle ${groupColor}`} />
|
||||||
|
Начало
|
||||||
|
</div>
|
||||||
|
</Draggable>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
StartNodeComponent,
|
||||||
|
FinishNodeComponent,
|
||||||
|
DelayNodeComponent,
|
||||||
|
MathNodeComponent,
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { Handle, Position } from "react-flow-renderer";
|
||||||
|
import {
|
||||||
|
bottomBigStyle,
|
||||||
|
bottomSmallStyle,
|
||||||
|
topDoubleLeftStyle,
|
||||||
|
topDoubleRightStyle,
|
||||||
|
} from "../styles";
|
||||||
|
|
||||||
|
type MathNodeProps = {};
|
||||||
|
|
||||||
|
const MathNode = (props: MathNodeProps) => {
|
||||||
|
return (
|
||||||
|
<div className="math-node node-layout">
|
||||||
|
<Handle
|
||||||
|
style={topDoubleLeftStyle}
|
||||||
|
id="in-a"
|
||||||
|
type="target"
|
||||||
|
position={Position.Top}
|
||||||
|
/>
|
||||||
|
<Handle
|
||||||
|
style={topDoubleRightStyle}
|
||||||
|
id="in-b"
|
||||||
|
type="target"
|
||||||
|
position={Position.Top}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="node-header">
|
||||||
|
<span className="node-title">Математика</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="node-content">
|
||||||
|
<select className="node-content-select nodrag">
|
||||||
|
<option>+</option>
|
||||||
|
<option>-</option>
|
||||||
|
<option>*</option>
|
||||||
|
<option>/</option>
|
||||||
|
<option>√</option>
|
||||||
|
<option>**</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Handle
|
||||||
|
style={bottomSmallStyle}
|
||||||
|
type="source"
|
||||||
|
position={Position.Bottom}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MathNode;
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { Handle, Position } from "react-flow-renderer";
|
||||||
|
import {
|
||||||
|
bottomBigStyle,
|
||||||
|
bottomSmallStyle,
|
||||||
|
} from "../styles";
|
||||||
|
|
||||||
|
type StartNodeProps = {};
|
||||||
|
|
||||||
|
const StartNode = (props: StartNodeProps) => {
|
||||||
|
return (
|
||||||
|
<div className="start-node node-layout">
|
||||||
|
|
||||||
|
<div className="node-content">
|
||||||
|
<span className="node-title">Начало программы</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Handle
|
||||||
|
style={bottomSmallStyle}
|
||||||
|
type="source"
|
||||||
|
position={Position.Bottom}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StartNode;
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
import { Handle, Position } from "react-flow-renderer";
|
||||||
|
import Draggable from "../../DragNDrop/Draggable";
|
||||||
|
|
||||||
|
type GenericNodeProps = {};
|
||||||
|
|
||||||
|
const GenericNode = (props: GenericNodeProps) => {
|
||||||
|
return (
|
||||||
|
// Базовый класс generic-node - прописана ширина и положение, цвет текста и фона
|
||||||
|
|
||||||
|
// Nandle - ручка для коннекта. Можно задать оформление при помощи className, тип (target / source) и позицию
|
||||||
|
|
||||||
|
// Label - подпись ноды. Можно стилизовать
|
||||||
|
|
||||||
|
// Select - DropDown меню, можно стилизовать. Стоит класс nodrag для предотвращения залипания
|
||||||
|
|
||||||
|
<div className="generic-node">
|
||||||
|
<Handle className="" type="target" position={Position.Top} />
|
||||||
|
|
||||||
|
<div className="generic-node-content">
|
||||||
|
<label htmlFor="select">Выбери своего покемона: </label>
|
||||||
|
<select className="nodrag">
|
||||||
|
<option>Гена</option>
|
||||||
|
<option>Чайка</option>
|
||||||
|
<option>Бульбазавр</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Handle className="" type="source" position={Position.Bottom} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const GenericNodeComponent = () => {
|
||||||
|
return (
|
||||||
|
<Draggable data={{ type: "generic" }}>
|
||||||
|
<div className="node-component ">
|
||||||
|
<div className="node-component-handle bg-red-700" />
|
||||||
|
Тест
|
||||||
|
</div>
|
||||||
|
</Draggable>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { GenericNode, GenericNodeComponent };
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
const topDoubleLeftStyle = {
|
||||||
|
width: "20px",
|
||||||
|
height: "15px",
|
||||||
|
border: "none",
|
||||||
|
borderTopLeftRadius: "10px",
|
||||||
|
borderTopRightRadius: "10px",
|
||||||
|
borderBottomLeftRadius: "0px",
|
||||||
|
borderBottomRightRadius: "0px",
|
||||||
|
top: "-15px",
|
||||||
|
left: "20px",
|
||||||
|
background: "#6f6f6f",
|
||||||
|
};
|
||||||
|
|
||||||
|
const topDoubleRightStyle = {
|
||||||
|
width: "20px",
|
||||||
|
height: "15px",
|
||||||
|
border: "none",
|
||||||
|
borderTopLeftRadius: "10px",
|
||||||
|
borderTopRightRadius: "10px",
|
||||||
|
borderBottomLeftRadius: "0px",
|
||||||
|
borderBottomRightRadius: "0px",
|
||||||
|
top: "-15px",
|
||||||
|
background: "#6f6f6f",
|
||||||
|
};
|
||||||
|
|
||||||
|
const topBigStyle = {
|
||||||
|
width: "100%",
|
||||||
|
height: "10px",
|
||||||
|
border: "none",
|
||||||
|
borderTopLeftRadius: "10px",
|
||||||
|
borderTopRightRadius: "10px",
|
||||||
|
borderBottomLeftRadius: "0px",
|
||||||
|
borderBottomRightRadius: "0px",
|
||||||
|
top: "-10px",
|
||||||
|
background: "#6f6f6f",
|
||||||
|
};
|
||||||
|
|
||||||
|
const bottomBigStyle = {
|
||||||
|
width: "100%",
|
||||||
|
height: "10px",
|
||||||
|
border: "none",
|
||||||
|
borderTopLeftRadius: "0px",
|
||||||
|
borderTopRightRadius: "0px",
|
||||||
|
borderBottomLeftRadius: "10px",
|
||||||
|
borderBottomRightRadius: "10px",
|
||||||
|
bottom: "-10px",
|
||||||
|
background: "#6f6f6f",
|
||||||
|
};
|
||||||
|
|
||||||
|
const topSmallStyle = {
|
||||||
|
width: "20px",
|
||||||
|
height: "15px",
|
||||||
|
border: "none",
|
||||||
|
borderTopLeftRadius: "10px",
|
||||||
|
borderTopRightRadius: "10px",
|
||||||
|
borderBottomLeftRadius: "0px",
|
||||||
|
borderBottomRightRadius: "0px",
|
||||||
|
top: "-15px",
|
||||||
|
background: "#6f6f6f",
|
||||||
|
};
|
||||||
|
|
||||||
|
const bottomSmallStyle = {
|
||||||
|
width: "20px",
|
||||||
|
height: "15px",
|
||||||
|
border: "none",
|
||||||
|
borderTopLeftRadius: "0px",
|
||||||
|
borderTopRightRadius: "0px",
|
||||||
|
borderBottomLeftRadius: "10px",
|
||||||
|
borderBottomRightRadius: "10px",
|
||||||
|
bottom: "-15px",
|
||||||
|
background: "#6f6f6f",
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
topBigStyle,
|
||||||
|
bottomBigStyle,
|
||||||
|
topDoubleLeftStyle,
|
||||||
|
topDoubleRightStyle,
|
||||||
|
bottomSmallStyle,
|
||||||
|
};
|
||||||
|
|
@ -1,13 +1,46 @@
|
||||||
import { VscAdd, VscTerminal } from "react-icons/vsc";
|
import {
|
||||||
|
VscCircuitBoard,
|
||||||
|
VscCode,
|
||||||
|
VscSettingsGear,
|
||||||
|
VscSourceControl,
|
||||||
|
VscTerminal,
|
||||||
|
} from "react-icons/vsc";
|
||||||
import { ReactElement, ReactNode } from "react";
|
import { ReactElement, ReactNode } from "react";
|
||||||
|
import ActiveLink from "../Navigation/ActiveLink";
|
||||||
|
|
||||||
const SideBar = () => {
|
const SideBar = () => {
|
||||||
return (
|
return (
|
||||||
<div className="sidebar">
|
<div className="sidebar">
|
||||||
<SideBarIcon
|
<ActiveLink href="/board" activeClassName="sidebar-icon-active" shallow>
|
||||||
tooltipText="Настройка Платы"
|
<SideBarIcon
|
||||||
icon={<VscTerminal size={40} />}
|
tooltipText="Настройка Платы"
|
||||||
/>
|
icon={<VscCircuitBoard size={40} />}
|
||||||
|
/>
|
||||||
|
</ActiveLink>
|
||||||
|
<ActiveLink href="/flow" activeClassName="sidebar-icon-active" shallow>
|
||||||
|
<SideBarIcon
|
||||||
|
tooltipText="Визуальная среда"
|
||||||
|
icon={<VscSourceControl size={40} />}
|
||||||
|
/>
|
||||||
|
</ActiveLink>
|
||||||
|
<ActiveLink href="/editor" activeClassName="sidebar-icon-active" shallow>
|
||||||
|
<SideBarIcon tooltipText="Редактор кода" icon={<VscCode size={40} />} />
|
||||||
|
</ActiveLink>
|
||||||
|
|
||||||
|
<ActiveLink href="/console" activeClassName="sidebar-icon-active" shallow>
|
||||||
|
<SideBarIcon tooltipText="Консоль" icon={<VscTerminal size={40} />} />
|
||||||
|
</ActiveLink>
|
||||||
|
|
||||||
|
<ActiveLink
|
||||||
|
href="/settings"
|
||||||
|
activeClassName="sidebar-icon-active"
|
||||||
|
shallow
|
||||||
|
>
|
||||||
|
<SideBarIcon
|
||||||
|
tooltipText="Настройки"
|
||||||
|
icon={<VscSettingsGear size={40} />}
|
||||||
|
/>
|
||||||
|
</ActiveLink>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
@ -20,7 +53,7 @@ type SideBarIconProps = {
|
||||||
|
|
||||||
const SideBarIcon = ({ icon, tooltipText, className }: SideBarIconProps) => {
|
const SideBarIcon = ({ icon, tooltipText, className }: SideBarIconProps) => {
|
||||||
return (
|
return (
|
||||||
<div className="sidebar-icon group">
|
<div className={`sidebar-icon group ${className}`}>
|
||||||
{icon}
|
{icon}
|
||||||
<span className="sidebar-tooltip group-hover:visible">{tooltipText}</span>
|
<span className="sidebar-tooltip group-hover:visible">{tooltipText}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
import create from "zustand";
|
||||||
|
import {
|
||||||
|
Connection,
|
||||||
|
Edge,
|
||||||
|
EdgeChange,
|
||||||
|
Node,
|
||||||
|
NodeChange,
|
||||||
|
addEdge,
|
||||||
|
OnNodesChange,
|
||||||
|
OnEdgesChange,
|
||||||
|
OnConnect,
|
||||||
|
applyNodeChanges,
|
||||||
|
applyEdgeChanges,
|
||||||
|
XYPosition,
|
||||||
|
} from "react-flow-renderer";
|
||||||
|
import { nanoid } from "nanoid";
|
||||||
|
|
||||||
|
import initialNodes from "./nodes";
|
||||||
|
import initialEdges from "./edges";
|
||||||
|
|
||||||
|
type FlowEditorStore = {
|
||||||
|
nodes: Node[];
|
||||||
|
edges: Edge[];
|
||||||
|
panelClosed: boolean;
|
||||||
|
onNodesChange: OnNodesChange;
|
||||||
|
onEdgesChange: OnEdgesChange;
|
||||||
|
onConnect: OnConnect;
|
||||||
|
addNode: (type: string, position: XYPosition) => void;
|
||||||
|
togglePanelState: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const useStore = create<FlowEditorStore>((set, get) => ({
|
||||||
|
nodes: initialNodes,
|
||||||
|
edges: initialEdges,
|
||||||
|
panelClosed: false,
|
||||||
|
onNodesChange: (changes: NodeChange[]) => {
|
||||||
|
set({
|
||||||
|
nodes: applyNodeChanges(changes, get().nodes),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onEdgesChange: (changes: EdgeChange[]) => {
|
||||||
|
set({
|
||||||
|
edges: applyEdgeChanges(changes, get().edges),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onConnect: (connection: Connection) => {
|
||||||
|
set({
|
||||||
|
edges: addEdge(connection, get().edges),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
addNode: (type: string, position: XYPosition) => {
|
||||||
|
const newNode: Node = {
|
||||||
|
id: `node_${nanoid()}`,
|
||||||
|
type: type,
|
||||||
|
position: position,
|
||||||
|
data: { label: `${type} node` },
|
||||||
|
};
|
||||||
|
set({
|
||||||
|
nodes: get().nodes.concat(newNode),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
togglePanelState: () => {
|
||||||
|
set({
|
||||||
|
panelClosed: !get().panelClosed,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default useStore;
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { Edge } from "react-flow-renderer";
|
||||||
|
|
||||||
|
const initialEdges: Edge[] = [];
|
||||||
|
|
||||||
|
export default initialEdges;
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { Node } from "react-flow-renderer";
|
||||||
|
|
||||||
|
const initialNodes: Node[] = [
|
||||||
|
];
|
||||||
|
|
||||||
|
export default initialNodes;
|
||||||
|
|
@ -6,16 +6,22 @@
|
||||||
"start": "next start -p 4000"
|
"start": "next start -p 4000"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@dnd-kit/core": "^6.0.5",
|
||||||
|
"@monaco-editor/react": "^4.4.5",
|
||||||
"next": "latest",
|
"next": "latest",
|
||||||
"next-pwa": "^5.4.1",
|
"next-pwa": "^5.4.1",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-icons": "^4.4.0"
|
"react-flow-renderer": "^10.3.9",
|
||||||
|
"react-icons": "^4.4.0",
|
||||||
|
"zustand": "^4.0.0-rc.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "17.0.4",
|
"@types/node": "17.0.4",
|
||||||
"@types/react": "17.0.38",
|
"@types/react": "17.0.38",
|
||||||
"autoprefixer": "^10.4.7",
|
"autoprefixer": "^10.4.7",
|
||||||
|
"eslint": "^8.19.0",
|
||||||
|
"eslint-config-next": "12.2.2",
|
||||||
"postcss": "^8.4.14",
|
"postcss": "^8.4.14",
|
||||||
"prettier": "^2.7.1",
|
"prettier": "^2.7.1",
|
||||||
"tailwindcss": "^3.1.6",
|
"tailwindcss": "^3.1.6",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
const Board = () => {
|
||||||
|
return "Настройки платы";
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Board;
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
const Console = () => {
|
||||||
|
return "Консоль";
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Console;
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
import CodeEditor from "../components/CodeEditor/CodeEditor";
|
||||||
|
|
||||||
|
const Editor = () => {
|
||||||
|
return <CodeEditor />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Editor;
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
import FlowEditor from "../components/FlowEditor/FlowEditor";
|
||||||
|
|
||||||
|
const Flow = () => {
|
||||||
|
return <FlowEditor />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Flow;
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
const Settings = () => {
|
||||||
|
return "Настройки";
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Settings;
|
||||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 8.9 KiB |
|
Before Width: | Height: | Size: 761 B After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.1 KiB |
|
|
@ -4,7 +4,7 @@
|
||||||
"theme_color": "#ffffff",
|
"theme_color": "#ffffff",
|
||||||
"background_color": "#004740",
|
"background_color": "#004740",
|
||||||
"display": "fullscreen",
|
"display": "fullscreen",
|
||||||
"orientation": "portrait",
|
"orientation": "landscape",
|
||||||
"scope": "/",
|
"scope": "/",
|
||||||
"start_url": "/",
|
"start_url": "/",
|
||||||
"icons": [
|
"icons": [
|
||||||
|
|
|
||||||
|
|
@ -7,22 +7,23 @@
|
||||||
@apply fixed top-0 left-0
|
@apply fixed top-0 left-0
|
||||||
h-screen w-16 m-0
|
h-screen w-16 m-0
|
||||||
flex flex-col
|
flex flex-col
|
||||||
bg-sidebar text-primary
|
bg-sidebar text-secondary
|
||||||
z-40;
|
z-40;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-icon {
|
.sidebar-icon {
|
||||||
@apply relative flex items-center justify-center
|
@apply relative flex items-center justify-center
|
||||||
h-14 w-14 my-2 mx-auto
|
h-14 w-14 my-2 mx-auto
|
||||||
shadow-lg bg-gray-800 text-primary
|
shadow-lg bg-graySecondary text-secondary
|
||||||
hover:bg-green-900 hover:text-white
|
hover:bg-green-900 hover:text-primary
|
||||||
rounded-3xl hover:rounded-xl
|
rounded-3xl
|
||||||
|
hover:rounded-xl
|
||||||
transition-all duration-300 ease-linear
|
transition-all duration-300 ease-linear
|
||||||
cursor-pointer;
|
cursor-pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-icon-active {
|
.sidebar-icon-active {
|
||||||
@apply bg-green-900 text-white rounded-xl;
|
@apply bg-green-900 text-primary rounded-xl;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-tooltip {
|
.sidebar-tooltip {
|
||||||
|
|
@ -37,7 +38,68 @@
|
||||||
border border-gray-600 rounded-full
|
border border-gray-600 rounded-full
|
||||||
mx-2;
|
mx-2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
@apply ml-16;
|
@apply ml-16;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.flow-editor {
|
||||||
|
@apply flex flex-row
|
||||||
|
bg-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-panel {
|
||||||
|
@apply relative flex flex-col
|
||||||
|
bg-panel
|
||||||
|
z-30;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-panel-button {
|
||||||
|
@apply absolute flex items-center justify-center
|
||||||
|
w-10 h-10 mt-4 -right-12
|
||||||
|
bg-grayPrimary text-secondary
|
||||||
|
rounded-xl cursor-pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-layout {
|
||||||
|
@apply flex flex-col items-center
|
||||||
|
rounded-lg
|
||||||
|
w-[150px] h-fit bg-grayPrimary
|
||||||
|
p-[4px];
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-handle-big {
|
||||||
|
@apply w-[150px] h-[20px];
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-header {
|
||||||
|
@apply flex flex-row w-full h-[20px] justify-between items-center
|
||||||
|
mb-1 px-[4px]
|
||||||
|
border-b border-b-gray-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-title {
|
||||||
|
@apply text-white text-xs font-bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-content {
|
||||||
|
@apply flex flex-col w-full h-fit
|
||||||
|
mb-[4px];
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-content-select {
|
||||||
|
@apply bg-grayPrimary border border-gray-900 text-sm rounded-lg
|
||||||
|
text-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-component {
|
||||||
|
@apply relative flex flex-row h-fit rounded-md
|
||||||
|
bg-grayPrimary
|
||||||
|
pr-2 my-1 mx-2
|
||||||
|
text-white align-middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-component-handle {
|
||||||
|
@apply left-0 w-[12px] h-[30px] mr-2 rounded-tl-md rounded-bl-md;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,13 @@ module.exports = {
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
colors: {
|
colors: {
|
||||||
primary: "#78b856",
|
primary: "#39ff14",
|
||||||
|
secondary: "#008a00",
|
||||||
sidebar: "#333333",
|
sidebar: "#333333",
|
||||||
panel: "#252526",
|
panel: "#252526",
|
||||||
content: "#1e1e1e",
|
content: "#1e1e1e",
|
||||||
|
grayPrimary: "#4f4f4f",
|
||||||
|
graySecondary: "#202020",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||