mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-30 16:47:13 +01:00
Refactors UI code of integrated circuits completely into separate folders and adds dragndrop functionality for the UI (#60514)
Co-authored-by: Watermelon914 <3052169-Watermelon914@users.noreply.gitlab.com>
This commit is contained in:
co-authored by
Watermelon914
parent
9f546e1f78
commit
27f3ab0d5d
@@ -27,6 +27,8 @@ export class InfinitePlane extends Component {
|
||||
|
||||
this.handleMouseDown = this.handleMouseDown.bind(this);
|
||||
this.handleMouseMove = this.handleMouseMove.bind(this);
|
||||
this.handleZoomIncrease = this.handleZoomIncrease.bind(this);
|
||||
this.handleZoomDecrease = this.handleZoomDecrease.bind(this);
|
||||
this.onMouseUp = this.onMouseUp.bind(this);
|
||||
|
||||
this.doOffsetMouse = this.doOffsetMouse.bind(this);
|
||||
@@ -70,14 +72,46 @@ export class InfinitePlane extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
handleZoomIncrease(event) {
|
||||
const { onZoomChange } = this.props;
|
||||
const { zoom } = this.state;
|
||||
const newZoomValue = Math.min(zoom+ZOOM_INCREMENT, ZOOM_MAX_VAL);
|
||||
this.setState({
|
||||
zoom: newZoomValue,
|
||||
});
|
||||
if (onZoomChange) {
|
||||
onZoomChange(newZoomValue);
|
||||
}
|
||||
}
|
||||
|
||||
handleZoomDecrease(event) {
|
||||
const { onZoomChange } = this.props;
|
||||
const { zoom } = this.state;
|
||||
const newZoomValue = Math.max(zoom-ZOOM_INCREMENT, ZOOM_MIN_VAL);
|
||||
this.setState({
|
||||
zoom: newZoomValue,
|
||||
});
|
||||
|
||||
if (onZoomChange) {
|
||||
onZoomChange(newZoomValue);
|
||||
}
|
||||
}
|
||||
|
||||
handleMouseMove(event) {
|
||||
const { onBackgroundMoved } = this.props;
|
||||
if (this.state.mouseDown) {
|
||||
let newX, newY;
|
||||
this.setState((state) => {
|
||||
newX = event.clientX - state.lastLeft;
|
||||
newY = event.clientY - state.lastTop;
|
||||
return {
|
||||
left: event.clientX - state.lastLeft,
|
||||
top: event.clientY - state.lastTop,
|
||||
left: newX,
|
||||
top: newY,
|
||||
};
|
||||
});
|
||||
if (onBackgroundMoved) {
|
||||
onBackgroundMoved(newX, newY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,9 +174,7 @@ export class InfinitePlane extends Component {
|
||||
<Stack.Item>
|
||||
<Button
|
||||
icon="minus"
|
||||
onClick={() => this.setState({
|
||||
zoom: Math.max(zoom-ZOOM_INCREMENT, ZOOM_MIN_VAL),
|
||||
})}
|
||||
onClick={this.handleZoomDecrease}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow={1}>
|
||||
@@ -157,9 +189,7 @@ export class InfinitePlane extends Component {
|
||||
<Stack.Item>
|
||||
<Button
|
||||
icon="plus"
|
||||
onClick={() => this.setState({
|
||||
zoom: Math.min(zoom+ZOOM_INCREMENT, ZOOM_MAX_VAL),
|
||||
})}
|
||||
onClick={this.handleZoomIncrease}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Stack, Button } from '../../components';
|
||||
|
||||
export const BasicInput = (props, context) => {
|
||||
const { children, name, setValue, defaultValue, value } = props;
|
||||
return (
|
||||
(value !== null && (
|
||||
<Stack onMouseDown={(e) => e.stopPropagation()}>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
color="transparent"
|
||||
compact
|
||||
icon="times"
|
||||
onClick={() => setValue(null, { set_null: true })}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>{children}</Stack.Item>
|
||||
</Stack>
|
||||
)) || (
|
||||
<Button
|
||||
content={name}
|
||||
color="transparent"
|
||||
compact
|
||||
onClick={() => setValue(defaultValue)}
|
||||
/>
|
||||
)
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
import { CSS_COLORS } from '../../constants';
|
||||
import { SVG_CURVE_INTENSITY } from './constants';
|
||||
import { classes } from '../../../common/react';
|
||||
|
||||
export const Connections = (props, context) => {
|
||||
const { connections } = props;
|
||||
|
||||
const isColorClass = (str) => {
|
||||
if (typeof str === 'string') {
|
||||
return CSS_COLORS.includes(str);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<svg
|
||||
width="100%"
|
||||
height="100%"
|
||||
style={{
|
||||
'position': 'absolute',
|
||||
'pointer-events': 'none',
|
||||
'z-index': -1,
|
||||
}}>
|
||||
{connections.map((val, index) => {
|
||||
const from = val.from;
|
||||
const to = val.to;
|
||||
if (!to || !from) {
|
||||
return;
|
||||
}
|
||||
// Starting point
|
||||
let path = `M ${from.x} ${from.y}`;
|
||||
// DEFAULT STYLE
|
||||
path += `C ${from.x + SVG_CURVE_INTENSITY}, ${from.y},`;
|
||||
path += `${to.x - SVG_CURVE_INTENSITY}, ${to.y},`;
|
||||
path += `${to.x}, ${to.y}`;
|
||||
|
||||
// SUBWAY STYLE
|
||||
// const yDiff = Math.abs(from.y - (to.y - 16));
|
||||
// path += `L ${to.x - yDiff} ${from.y}`;
|
||||
// path += `L ${to.x - 16} ${to.y}`;
|
||||
// path += `L ${to.x} ${to.y}`;
|
||||
|
||||
val.color = val.color || 'blue';
|
||||
return (
|
||||
<path
|
||||
className={classes([
|
||||
isColorClass(val.color) && `color-stroke-${val.color}`,
|
||||
])}
|
||||
key={index}
|
||||
d={path}
|
||||
fill="transparent"
|
||||
stroke-width="2px"
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,60 @@
|
||||
import { useBackend } from '../../backend';
|
||||
import {
|
||||
Box,
|
||||
Button, Flex,
|
||||
} from '../../components';
|
||||
import { FUNDAMENTAL_DATA_TYPES } from './FundamentalTypes';
|
||||
import { NULL_REF } from './constants';
|
||||
|
||||
|
||||
export const DisplayName = (props, context) => {
|
||||
const { act } = useBackend(context);
|
||||
const { port, isOutput, componentId, portIndex, ...rest } = props;
|
||||
|
||||
const InputComponent = FUNDAMENTAL_DATA_TYPES[port.type || 'any'];
|
||||
|
||||
const hasInput = !isOutput
|
||||
&& port.connected_to === NULL_REF
|
||||
&& InputComponent;
|
||||
|
||||
return (
|
||||
<Box {...rest}>
|
||||
<Flex direction="column">
|
||||
<Flex.Item>
|
||||
{(hasInput && (
|
||||
<InputComponent
|
||||
setValue={(val, extraParams) => act('set_component_input', {
|
||||
component_id: componentId,
|
||||
port_id: portIndex,
|
||||
input: val,
|
||||
...extraParams,
|
||||
})}
|
||||
color={port.color}
|
||||
name={port.name}
|
||||
value={port.current_data} />
|
||||
))
|
||||
|| (isOutput && (
|
||||
<Button
|
||||
compact
|
||||
color="transparent"
|
||||
onClick={() => act('get_component_value', {
|
||||
component_id: componentId,
|
||||
port_id: portIndex,
|
||||
})}>
|
||||
<Box color="white">{port.name}</Box>
|
||||
</Button>
|
||||
))
|
||||
|| port.name}
|
||||
</Flex.Item>
|
||||
<Flex.Item>
|
||||
<Box
|
||||
fontSize={0.75}
|
||||
opacity={0.25}
|
||||
textAlign={isOutput ? 'right' : 'left'}>
|
||||
{port.type || 'any'}
|
||||
</Box>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
import { BasicInput } from './BasicInput';
|
||||
import { NumberInput, Button, Stack, Input } from '../../components';
|
||||
|
||||
export const FUNDAMENTAL_DATA_TYPES = {
|
||||
'string': (props, context) => {
|
||||
const { name, value, setValue, color } = props;
|
||||
return (
|
||||
<BasicInput name={name} setValue={setValue} value={value} defaultValue="">
|
||||
<Input
|
||||
placeholder={name}
|
||||
value={value}
|
||||
onChange={(e, val) => setValue(val)}
|
||||
/>
|
||||
</BasicInput>
|
||||
);
|
||||
},
|
||||
'number': (props, context) => {
|
||||
const { name, value, setValue, color } = props;
|
||||
return (
|
||||
<BasicInput
|
||||
name={name}
|
||||
setValue={setValue}
|
||||
value={value}
|
||||
defaultValue={0}>
|
||||
<NumberInput
|
||||
value={value}
|
||||
color={color}
|
||||
onChange={(e, val) => setValue(val)}
|
||||
unit={name}
|
||||
/>
|
||||
</BasicInput>
|
||||
);
|
||||
},
|
||||
'entity': (props, context) => {
|
||||
const { name, setValue, color } = props;
|
||||
return (
|
||||
<Button
|
||||
content={name}
|
||||
color="transparent"
|
||||
icon="upload"
|
||||
compact
|
||||
onClick={() => setValue(null, { marked_atom: true })}
|
||||
/>
|
||||
);
|
||||
},
|
||||
'signal': (props, context) => {
|
||||
const { name, setValue } = props;
|
||||
return (
|
||||
<Button
|
||||
content={name}
|
||||
color="transparent"
|
||||
compact
|
||||
onClick={() => setValue()}
|
||||
/>
|
||||
);
|
||||
},
|
||||
'any': (props, context) => {
|
||||
const { name, value, setValue, color } = props;
|
||||
return (
|
||||
<BasicInput
|
||||
name={name}
|
||||
setValue={setValue}
|
||||
value={value}
|
||||
defaultValue={''}>
|
||||
<Stack>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
color={color}
|
||||
icon="upload"
|
||||
onClick={() => setValue(null, { marked_atom: true })}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Input
|
||||
placeholder={name}
|
||||
value={value}
|
||||
onChange={(e, val) => setValue(val)}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</BasicInput>
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,221 @@
|
||||
import { useBackend } from '../../backend';
|
||||
import {
|
||||
Box,
|
||||
Stack, Button, Dropdown,
|
||||
} from '../../components';
|
||||
import { Component } from 'inferno';
|
||||
import { shallowDiffers } from '../../../common/react';
|
||||
import { ABSOLUTE_Y_OFFSET } from './constants';
|
||||
import { Port } from "./Port";
|
||||
|
||||
|
||||
export class ObjectComponent extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
isDragging: false,
|
||||
dragPos: null,
|
||||
startPos: null,
|
||||
lastMousePos: null,
|
||||
};
|
||||
|
||||
this.handleStartDrag = this.handleStartDrag.bind(this);
|
||||
this.handleStopDrag = this.handleStopDrag.bind(this);
|
||||
this.handleDrag = this.handleDrag.bind(this);
|
||||
}
|
||||
|
||||
handleStartDrag(e) {
|
||||
const { x, y } = this.props;
|
||||
e.stopPropagation();
|
||||
this.setState({
|
||||
lastMousePos: null,
|
||||
isDragging: true,
|
||||
dragPos: { x: x, y: y },
|
||||
startPos: { x: x, y: y },
|
||||
});
|
||||
window.addEventListener('mousemove', this.handleDrag);
|
||||
window.addEventListener('mouseup', this.handleStopDrag);
|
||||
}
|
||||
|
||||
handleStopDrag(e) {
|
||||
const { act } = useBackend(this.context);
|
||||
const { dragPos } = this.state;
|
||||
const { index } = this.props;
|
||||
if (dragPos) {
|
||||
act('set_component_coordinates', {
|
||||
component_id: index,
|
||||
rel_x: dragPos.x,
|
||||
rel_y: dragPos.y,
|
||||
});
|
||||
}
|
||||
|
||||
window.removeEventListener('mousemove', this.handleDrag);
|
||||
window.removeEventListener('mouseup', this.handleStopDrag);
|
||||
this.setState({ isDragging: false });
|
||||
}
|
||||
|
||||
handleDrag(e) {
|
||||
const { dragPos, isDragging, lastMousePos } = this.state;
|
||||
if (dragPos && isDragging) {
|
||||
e.preventDefault();
|
||||
const { screenZoomX, screenZoomY, screenX, screenY } = e;
|
||||
let xPos = screenZoomX || screenX;
|
||||
let yPos = screenZoomY || screenY;
|
||||
if (lastMousePos) {
|
||||
this.setState({
|
||||
dragPos: {
|
||||
x: dragPos.x - (lastMousePos.x - xPos),
|
||||
y: dragPos.y - (lastMousePos.y - yPos),
|
||||
},
|
||||
});
|
||||
}
|
||||
this.setState({
|
||||
lastMousePos: { x: xPos, y: yPos },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
const { input_ports, output_ports } = this.props;
|
||||
|
||||
return (
|
||||
shallowDiffers(this.props, nextProps)
|
||||
|| shallowDiffers(this.state, nextState)
|
||||
|| shallowDiffers(input_ports, nextProps.input_ports)
|
||||
|| shallowDiffers(output_ports, nextProps.output_ports)
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
input_ports,
|
||||
output_ports,
|
||||
name,
|
||||
x,
|
||||
y,
|
||||
index,
|
||||
color = 'blue',
|
||||
options,
|
||||
option,
|
||||
removable,
|
||||
locations,
|
||||
onPortUpdated,
|
||||
onPortLoaded,
|
||||
onPortMouseDown,
|
||||
onPortRightClick,
|
||||
onPortMouseUp,
|
||||
...rest
|
||||
} = this.props;
|
||||
const { act } = useBackend(this.context);
|
||||
const { startPos, dragPos } = this.state;
|
||||
|
||||
let [x_pos, y_pos] = [x, y];
|
||||
if (dragPos && startPos && startPos.x === x_pos && startPos.y === y_pos) {
|
||||
x_pos = dragPos.x;
|
||||
y_pos = dragPos.y;
|
||||
}
|
||||
|
||||
// Assigned onto the ports
|
||||
const PortOptions = {
|
||||
onPortLoaded: onPortLoaded,
|
||||
onPortUpdated: onPortUpdated,
|
||||
onPortMouseDown: onPortMouseDown,
|
||||
onPortRightClick: onPortRightClick,
|
||||
onPortMouseUp: onPortMouseUp,
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
{...rest}
|
||||
position="absolute"
|
||||
left={`${x_pos}px`}
|
||||
top={`${y_pos}px`}
|
||||
onMouseDown={this.handleStartDrag}
|
||||
onMouseUp={this.handleStopDrag}
|
||||
onComponentWillUnmount={this.handleDrag}>
|
||||
<Box
|
||||
backgroundColor={color}
|
||||
py={1}
|
||||
px={1}
|
||||
className="ObjectComponent__Titlebar">
|
||||
<Stack>
|
||||
<Stack.Item grow={1} unselectable="on">
|
||||
{name}
|
||||
</Stack.Item>
|
||||
{!!options && (
|
||||
<Stack.Item>
|
||||
<Dropdown
|
||||
color={color}
|
||||
nochevron
|
||||
over
|
||||
options={options}
|
||||
displayText={option}
|
||||
noscroll
|
||||
onSelected={(selected) => act('set_component_option', {
|
||||
component_id: index,
|
||||
option: selected,
|
||||
})} />
|
||||
</Stack.Item>
|
||||
)}
|
||||
<Stack.Item>
|
||||
<Button
|
||||
color="transparent"
|
||||
icon="info"
|
||||
compact
|
||||
onClick={(e) => act('set_examined_component', {
|
||||
component_id: index,
|
||||
x: e.pageX,
|
||||
y: e.pageY + ABSOLUTE_Y_OFFSET,
|
||||
})} />
|
||||
</Stack.Item>
|
||||
{!!removable && (
|
||||
<Stack.Item>
|
||||
<Button
|
||||
color="transparent"
|
||||
icon="times"
|
||||
compact
|
||||
onClick={() => act('detach_component', { component_id: index })} />
|
||||
</Stack.Item>
|
||||
)}
|
||||
</Stack>
|
||||
</Box>
|
||||
<Box
|
||||
className="ObjectComponent__Content"
|
||||
unselectable="on"
|
||||
py={1}
|
||||
px={1}>
|
||||
<Stack>
|
||||
<Stack.Item grow={1}>
|
||||
<Stack vertical fill>
|
||||
{input_ports.map((port, portIndex) => (
|
||||
<Stack.Item key={portIndex}>
|
||||
<Port
|
||||
port={port}
|
||||
portIndex={portIndex + 1}
|
||||
componentId={index}
|
||||
{...PortOptions}
|
||||
/>
|
||||
</Stack.Item>
|
||||
))}
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Stack.Item ml={5}>
|
||||
<Stack vertical>
|
||||
{output_ports.map((port, portIndex) => (
|
||||
<Stack.Item key={portIndex}>
|
||||
<Port
|
||||
port={port}
|
||||
portIndex={portIndex + 1}
|
||||
componentId={index}
|
||||
{...PortOptions}
|
||||
isOutput />
|
||||
</Stack.Item>
|
||||
))}
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
import {
|
||||
Stack,
|
||||
Icon,
|
||||
} from '../../components';
|
||||
import { Component, createRef } from 'inferno';
|
||||
import { DisplayName } from "./DisplayName";
|
||||
|
||||
|
||||
export class Port extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.iconRef = createRef();
|
||||
this.componentDidUpdate = this.componentDidUpdate.bind(this);
|
||||
this.componentDidMount = this.componentDidMount.bind(this);
|
||||
this.handlePortMouseDown = this.handlePortMouseDown.bind(this);
|
||||
this.handlePortRightClick = this.handlePortRightClick.bind(this);
|
||||
this.handlePortMouseUp = this.handlePortMouseUp.bind(this);
|
||||
}
|
||||
|
||||
handlePortMouseDown(e) {
|
||||
const {
|
||||
port,
|
||||
portIndex,
|
||||
componentId,
|
||||
isOutput,
|
||||
onPortMouseDown,
|
||||
} = this.props;
|
||||
onPortMouseDown(portIndex, componentId, port, isOutput, e);
|
||||
}
|
||||
|
||||
handlePortMouseUp(e) {
|
||||
const {
|
||||
port,
|
||||
portIndex,
|
||||
componentId,
|
||||
isOutput,
|
||||
onPortMouseUp,
|
||||
} = this.props;
|
||||
onPortMouseUp(portIndex, componentId, port, isOutput, e);
|
||||
}
|
||||
|
||||
handlePortRightClick(e) {
|
||||
const {
|
||||
port,
|
||||
portIndex,
|
||||
componentId,
|
||||
isOutput,
|
||||
onPortRightClick,
|
||||
} = this.props;
|
||||
onPortRightClick(portIndex, componentId, port, isOutput, e);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
const { port, onPortUpdated } = this.props;
|
||||
if (onPortUpdated) {
|
||||
onPortUpdated(port, this.iconRef.current);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { port, onPortLoaded } = this.props;
|
||||
if (onPortLoaded) {
|
||||
onPortLoaded(port, this.iconRef.current);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
port,
|
||||
portIndex,
|
||||
componentId,
|
||||
isOutput,
|
||||
...rest
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Stack {...rest} justify={isOutput ? 'flex-end' : 'flex-start'}>
|
||||
{!!isOutput && (
|
||||
<Stack.Item>
|
||||
<DisplayName
|
||||
port={port}
|
||||
isOutput={isOutput}
|
||||
componentId={componentId}
|
||||
portIndex={portIndex} />
|
||||
</Stack.Item>
|
||||
)}
|
||||
<Stack.Item>
|
||||
<Icon
|
||||
color={port.color || 'blue'}
|
||||
name={'circle'}
|
||||
position="relative"
|
||||
onMouseDown={this.handlePortMouseDown}
|
||||
onContextMenu={this.handlePortRightClick}
|
||||
onMouseUp={this.handlePortMouseUp}
|
||||
>
|
||||
<span ref={this.iconRef} className="ObjectComponent__PortPos" />
|
||||
</Icon>
|
||||
</Stack.Item>
|
||||
{!isOutput && (
|
||||
<Stack.Item>
|
||||
<DisplayName
|
||||
port={port}
|
||||
isOutput={isOutput}
|
||||
componentId={componentId}
|
||||
portIndex={portIndex} />
|
||||
</Stack.Item>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export const NULL_REF = '[0x0]';
|
||||
export const ABSOLUTE_Y_OFFSET = -32;
|
||||
export const SVG_CURVE_INTENSITY = 64;
|
||||
|
||||
export const MOUSE_BUTTON_LEFT = 0;
|
||||
@@ -1,142 +1,41 @@
|
||||
import { useBackend, useLocalState } from '../../backend';
|
||||
import { useBackend } from '../../backend';
|
||||
import {
|
||||
Box,
|
||||
Stack,
|
||||
Icon,
|
||||
Button,
|
||||
Input,
|
||||
Flex,
|
||||
NumberInput,
|
||||
Dropdown,
|
||||
InfinitePlane,
|
||||
Stack,
|
||||
Box,
|
||||
Button,
|
||||
} from '../../components';
|
||||
import { Component, createRef } from 'inferno';
|
||||
import { Component } from 'inferno';
|
||||
import { Window } from '../../layouts';
|
||||
import { CSS_COLORS } from '../../constants';
|
||||
import { classes, shallowDiffers } from '../../../common/react';
|
||||
import { resolveAsset } from '../../assets';
|
||||
import { CircuitInfo } from './CircuitInfo';
|
||||
|
||||
const NULL_REF = '[0x0]';
|
||||
const ABSOLUTE_Y_OFFSET = -32;
|
||||
const SVG_CURVE_INTENSITY = 64;
|
||||
|
||||
const BasicInput = (props, context) => {
|
||||
const { children, name, setValue, defaultValue, value } = props;
|
||||
return (
|
||||
(value !== null && (
|
||||
<Stack onMouseDown={(e) => e.stopPropagation()}>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
color="transparent"
|
||||
compact
|
||||
icon="times"
|
||||
onClick={() => setValue(null, { set_null: true })}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>{children}</Stack.Item>
|
||||
</Stack>
|
||||
)) || (
|
||||
<Button
|
||||
content={name}
|
||||
color="transparent"
|
||||
compact
|
||||
onClick={() => setValue(defaultValue)}
|
||||
/>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const FUNDAMENTAL_DATA_TYPES = {
|
||||
'string': (props, context) => {
|
||||
const { name, value, setValue, color } = props;
|
||||
return (
|
||||
<BasicInput name={name} setValue={setValue} value={value} defaultValue="">
|
||||
<Input
|
||||
placeholder={name}
|
||||
value={value}
|
||||
onChange={(e, val) => setValue(val)}
|
||||
/>
|
||||
</BasicInput>
|
||||
);
|
||||
},
|
||||
'number': (props, context) => {
|
||||
const { name, value, setValue, color } = props;
|
||||
return (
|
||||
<BasicInput
|
||||
name={name}
|
||||
setValue={setValue}
|
||||
value={value}
|
||||
defaultValue={0}>
|
||||
<NumberInput
|
||||
value={value}
|
||||
color={color}
|
||||
onChange={(e, val) => setValue(val)}
|
||||
unit={name}
|
||||
/>
|
||||
</BasicInput>
|
||||
);
|
||||
},
|
||||
'entity': (props, context) => {
|
||||
const { name, setValue, color } = props;
|
||||
return (
|
||||
<Button
|
||||
content={name}
|
||||
color="transparent"
|
||||
icon="upload"
|
||||
compact
|
||||
onClick={() => setValue(null, { marked_atom: true })}
|
||||
/>
|
||||
);
|
||||
},
|
||||
'signal': (props, context) => {
|
||||
const { name, setValue } = props;
|
||||
return (
|
||||
<Button
|
||||
content={name}
|
||||
color="transparent"
|
||||
compact
|
||||
onClick={() => setValue()}
|
||||
/>
|
||||
);
|
||||
},
|
||||
'any': (props, context) => {
|
||||
const { name, value, setValue, color } = props;
|
||||
return (
|
||||
<BasicInput
|
||||
name={name}
|
||||
setValue={setValue}
|
||||
value={value}
|
||||
defaultValue={''}>
|
||||
<Stack>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
color={color}
|
||||
icon="upload"
|
||||
onClick={() => setValue(null, { marked_atom: true })}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Input
|
||||
placeholder={name}
|
||||
value={value}
|
||||
onChange={(e, val) => setValue(val)}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</BasicInput>
|
||||
);
|
||||
},
|
||||
};
|
||||
import { NULL_REF, ABSOLUTE_Y_OFFSET, MOUSE_BUTTON_LEFT } from './constants';
|
||||
import { Connections } from './Connections';
|
||||
import { ObjectComponent } from './ObjectComponent';
|
||||
|
||||
export class IntegratedCircuit extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
locations: {},
|
||||
selectedPort: null,
|
||||
mouseX: null,
|
||||
mouseY: null,
|
||||
zoom: 1,
|
||||
backgroundX: 0,
|
||||
backgroundY: 0,
|
||||
};
|
||||
this.handlePortLocation = this.handlePortLocation.bind(this);
|
||||
this.handleMouseDown = this.handleMouseDown.bind(this);
|
||||
this.handlePortClick = this.handlePortClick.bind(this);
|
||||
this.handlePortRightClick = this.handlePortRightClick.bind(this);
|
||||
this.handlePortUp = this.handlePortUp.bind(this);
|
||||
|
||||
this.handlePortDrag = this.handlePortDrag.bind(this);
|
||||
this.handlePortRelease = this.handlePortRelease.bind(this);
|
||||
this.handleZoomChange = this.handleZoomChange.bind(this);
|
||||
this.handleBackgroundMoved = this.handleBackgroundMoved.bind(this);
|
||||
}
|
||||
|
||||
// Helper function to get an element's exact position
|
||||
@@ -179,6 +78,99 @@ export class IntegratedCircuit extends Component {
|
||||
this.setState({ locations: locations });
|
||||
}
|
||||
|
||||
handlePortClick(portIndex, componentId, port, isOutput, event) {
|
||||
if (event.button !== MOUSE_BUTTON_LEFT) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.stopPropagation();
|
||||
this.setState({
|
||||
selectedPort: {
|
||||
index: portIndex,
|
||||
component_id: componentId,
|
||||
is_output: isOutput,
|
||||
ref: port.ref,
|
||||
},
|
||||
});
|
||||
|
||||
this.handlePortDrag(event);
|
||||
|
||||
window.addEventListener('mousemove', this.handlePortDrag);
|
||||
window.addEventListener('mouseup', this.handlePortRelease);
|
||||
}
|
||||
|
||||
// mouse up called whilst over a port. This means we can check if selectedPort
|
||||
// exists and do perform some actions if it does.
|
||||
handlePortUp(portIndex, componentId, port, isOutput, event) {
|
||||
const { act } = useBackend(this.context);
|
||||
const {
|
||||
selectedPort,
|
||||
} = this.state;
|
||||
if (!selectedPort) {
|
||||
return;
|
||||
}
|
||||
if (selectedPort.is_output === isOutput) {
|
||||
return;
|
||||
}
|
||||
let data;
|
||||
if (isOutput) {
|
||||
data = {
|
||||
input_port_id: selectedPort.index,
|
||||
output_port_id: portIndex,
|
||||
input_component_id: selectedPort.component_id,
|
||||
output_component_id: componentId,
|
||||
};
|
||||
} else {
|
||||
data = {
|
||||
input_port_id: portIndex,
|
||||
output_port_id: selectedPort.index,
|
||||
input_component_id: componentId,
|
||||
output_component_id: selectedPort.component_id,
|
||||
};
|
||||
}
|
||||
act("add_connection", data);
|
||||
}
|
||||
|
||||
handlePortDrag(event) {
|
||||
this.setState((state) => ({
|
||||
mouseX: event.clientX - state.backgroundX,
|
||||
mouseY: event.clientY - state.backgroundY,
|
||||
}));
|
||||
}
|
||||
|
||||
handlePortRelease(event) {
|
||||
this.setState({
|
||||
selectedPort: null,
|
||||
});
|
||||
|
||||
window.removeEventListener('mousemove', this.handlePortDrag);
|
||||
window.removeEventListener('mouseup', this.handlePortRelease);
|
||||
}
|
||||
|
||||
handlePortRightClick(portIndex, componentId, port, isOutput, event) {
|
||||
const { act } = useBackend(this.context);
|
||||
|
||||
event.preventDefault();
|
||||
act('remove_connection', {
|
||||
component_id: componentId,
|
||||
is_input: !isOutput,
|
||||
port_id: portIndex,
|
||||
});
|
||||
}
|
||||
|
||||
handleZoomChange(newZoom) {
|
||||
this.setState({
|
||||
zoom: newZoom,
|
||||
});
|
||||
}
|
||||
|
||||
handleBackgroundMoved(newX, newY) {
|
||||
this.setState({
|
||||
backgroundX: newX,
|
||||
backgroundY: newY,
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
window.addEventListener('mousedown', this.handleMouseDown);
|
||||
}
|
||||
@@ -207,7 +199,40 @@ export class IntegratedCircuit extends Component {
|
||||
examined_rel_y,
|
||||
is_admin,
|
||||
} = data;
|
||||
const { locations } = this.state;
|
||||
const { locations, selectedPort } = this.state;
|
||||
const connections = [];
|
||||
|
||||
for (const comp of components) {
|
||||
if (comp === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const port of comp.input_ports) {
|
||||
if (port.connected_to === NULL_REF
|
||||
|| selectedPort?.ref === port.ref) continue;
|
||||
const output_port = locations[port.connected_to];
|
||||
connections.push({
|
||||
color: (output_port && output_port.color) || 'blue',
|
||||
from: output_port,
|
||||
to: locations[port.ref],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedPort) {
|
||||
const { mouseX, mouseY, zoom } = this.state;
|
||||
const isOutput = selectedPort.is_output;
|
||||
const portLocation = locations[selectedPort.ref];
|
||||
const mouseCoords = {
|
||||
x: (mouseX)*Math.pow(zoom, -1),
|
||||
y: (mouseY + ABSOLUTE_Y_OFFSET)*Math.pow(zoom, -1),
|
||||
};
|
||||
connections.push({
|
||||
color: (portLocation && portLocation.color) || 'blue',
|
||||
from: isOutput? portLocation : mouseCoords,
|
||||
to: isOutput? mouseCoords : portLocation,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Window
|
||||
@@ -247,7 +272,10 @@ export class IntegratedCircuit extends Component {
|
||||
width="100%"
|
||||
height="100%"
|
||||
backgroundImage={resolveAsset('grid_background.png')}
|
||||
imageWidth={900}>
|
||||
imageWidth={900}
|
||||
onZoomChange={this.handleZoomChange}
|
||||
onBackgroundMoved={this.handleBackgroundMoved}
|
||||
>
|
||||
{components.map(
|
||||
(comp, index) =>
|
||||
comp && (
|
||||
@@ -257,10 +285,13 @@ export class IntegratedCircuit extends Component {
|
||||
index={index + 1}
|
||||
onPortUpdated={this.handlePortLocation}
|
||||
onPortLoaded={this.handlePortLocation}
|
||||
onPortMouseDown={this.handlePortClick}
|
||||
onPortRightClick={this.handlePortRightClick}
|
||||
onPortMouseUp={this.handlePortUp}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
<Connections locations={locations} />
|
||||
<Connections connections={connections} />
|
||||
</InfinitePlane>
|
||||
{!!examined_name && (
|
||||
<CircuitInfo
|
||||
@@ -279,465 +310,3 @@ export class IntegratedCircuit extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
const Connections = (props, context) => {
|
||||
const { data } = useBackend(context);
|
||||
const { locations } = props;
|
||||
const { components } = data;
|
||||
const connections = [];
|
||||
|
||||
for (const comp of components) {
|
||||
if (comp === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const port of comp.input_ports) {
|
||||
if (port.connected_to === NULL_REF) continue;
|
||||
const output_port = locations[port.connected_to];
|
||||
connections.push({
|
||||
color: (output_port && output_port.color) || 'blue',
|
||||
from: output_port,
|
||||
to: locations[port.ref],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const isColorClass = (str) => {
|
||||
if (typeof str === 'string') {
|
||||
return CSS_COLORS.includes(str);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<svg
|
||||
width="100%"
|
||||
height="100%"
|
||||
style={{
|
||||
'position': 'absolute',
|
||||
'pointer-events': 'none',
|
||||
'z-index': -1,
|
||||
}}>
|
||||
{connections.map((val, index) => {
|
||||
const from = val.from;
|
||||
const to = val.to;
|
||||
if (!to || !from) {
|
||||
return;
|
||||
}
|
||||
// Starting point
|
||||
let path = `M ${from.x} ${from.y}`;
|
||||
path += `C ${from.x + SVG_CURVE_INTENSITY}, ${from.y},`;
|
||||
path += `${to.x - SVG_CURVE_INTENSITY}, ${to.y},`;
|
||||
path += `${to.x}, ${to.y}`;
|
||||
|
||||
val.color = val.color || 'blue';
|
||||
return (
|
||||
<path
|
||||
className={classes([
|
||||
isColorClass(val.color) && `color-stroke-${val.color}`,
|
||||
])}
|
||||
key={index}
|
||||
d={path}
|
||||
fill="transparent"
|
||||
stroke-width="2px"
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export class ObjectComponent extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
isDragging: false,
|
||||
dragPos: null,
|
||||
startPos: null,
|
||||
lastMousePos: null,
|
||||
};
|
||||
|
||||
this.handleStartDrag = this.handleStartDrag.bind(this);
|
||||
this.handleStopDrag = this.handleStopDrag.bind(this);
|
||||
this.handleDrag = this.handleDrag.bind(this);
|
||||
}
|
||||
|
||||
handleStartDrag(e) {
|
||||
const { x, y } = this.props;
|
||||
e.stopPropagation();
|
||||
this.setState({
|
||||
lastMousePos: null,
|
||||
isDragging: true,
|
||||
dragPos: { x: x, y: y },
|
||||
startPos: { x: x, y: y },
|
||||
});
|
||||
window.addEventListener('mousemove', this.handleDrag);
|
||||
window.addEventListener('mouseup', this.handleStopDrag);
|
||||
}
|
||||
|
||||
handleStopDrag(e) {
|
||||
const { act } = useBackend(this.context);
|
||||
const { dragPos } = this.state;
|
||||
const { index } = this.props;
|
||||
if (dragPos) {
|
||||
act('set_component_coordinates', {
|
||||
component_id: index,
|
||||
rel_x: dragPos.x,
|
||||
rel_y: dragPos.y,
|
||||
});
|
||||
}
|
||||
|
||||
window.removeEventListener('mousemove', this.handleDrag);
|
||||
window.removeEventListener('mouseup', this.handleStopDrag);
|
||||
this.setState({ isDragging: false });
|
||||
}
|
||||
|
||||
handleDrag(e) {
|
||||
const { dragPos, isDragging, lastMousePos } = this.state;
|
||||
if (dragPos && isDragging) {
|
||||
e.preventDefault();
|
||||
const { screenZoomX, screenZoomY, screenX, screenY } = e;
|
||||
let xPos = screenZoomX || screenX;
|
||||
let yPos = screenZoomY || screenY;
|
||||
if (lastMousePos) {
|
||||
this.setState({
|
||||
dragPos: {
|
||||
x: dragPos.x - (lastMousePos.x - xPos),
|
||||
y: dragPos.y - (lastMousePos.y - yPos),
|
||||
},
|
||||
});
|
||||
}
|
||||
this.setState({
|
||||
lastMousePos: { x: xPos, y: yPos },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
const { input_ports, output_ports } = this.props;
|
||||
|
||||
return (
|
||||
shallowDiffers(this.props, nextProps)
|
||||
|| shallowDiffers(this.state, nextState)
|
||||
|| shallowDiffers(input_ports, nextProps.input_ports)
|
||||
|| shallowDiffers(output_ports, nextProps.output_ports)
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
input_ports,
|
||||
output_ports,
|
||||
name,
|
||||
x,
|
||||
y,
|
||||
index,
|
||||
color = 'blue',
|
||||
options,
|
||||
option,
|
||||
removable,
|
||||
locations,
|
||||
onPortUpdated,
|
||||
onPortLoaded,
|
||||
...rest
|
||||
} = this.props;
|
||||
const { act } = useBackend(this.context);
|
||||
const { startPos, dragPos } = this.state;
|
||||
|
||||
let [x_pos, y_pos] = [x, y];
|
||||
if (dragPos && startPos && startPos.x === x_pos && startPos.y === y_pos) {
|
||||
x_pos = dragPos.x;
|
||||
y_pos = dragPos.y;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
{...rest}
|
||||
position="absolute"
|
||||
left={`${x_pos}px`}
|
||||
top={`${y_pos}px`}
|
||||
onMouseDown={this.handleStartDrag}
|
||||
onMouseUp={this.handleStopDrag}
|
||||
onComponentWillUnmount={this.handleDrag}>
|
||||
<Box
|
||||
backgroundColor={color}
|
||||
py={1}
|
||||
px={1}
|
||||
className="ObjectComponent__Titlebar">
|
||||
<Stack>
|
||||
<Stack.Item grow={1} unselectable="on">
|
||||
{name}
|
||||
</Stack.Item>
|
||||
{!!options && (
|
||||
<Stack.Item>
|
||||
<Dropdown
|
||||
color={color}
|
||||
nochevron
|
||||
over
|
||||
options={options}
|
||||
displayText={option}
|
||||
noscroll
|
||||
onSelected={(selected) =>
|
||||
act('set_component_option', {
|
||||
component_id: index,
|
||||
option: selected,
|
||||
})}
|
||||
/>
|
||||
</Stack.Item>
|
||||
)}
|
||||
<Stack.Item>
|
||||
<Button
|
||||
color="transparent"
|
||||
icon="info"
|
||||
compact
|
||||
onClick={(e) =>
|
||||
act('set_examined_component', {
|
||||
component_id: index,
|
||||
x: e.pageX,
|
||||
y: e.pageY + ABSOLUTE_Y_OFFSET,
|
||||
})}
|
||||
/>
|
||||
</Stack.Item>
|
||||
{!!removable && (
|
||||
<Stack.Item>
|
||||
<Button
|
||||
color="transparent"
|
||||
icon="times"
|
||||
compact
|
||||
onClick={() =>
|
||||
act('detach_component', { component_id: index })}
|
||||
/>
|
||||
</Stack.Item>
|
||||
)}
|
||||
</Stack>
|
||||
</Box>
|
||||
<Box
|
||||
className="ObjectComponent__Content"
|
||||
unselectable="on"
|
||||
py={1}
|
||||
px={1}>
|
||||
<Stack>
|
||||
<Stack.Item grow={1}>
|
||||
<Stack vertical fill>
|
||||
{input_ports.map((port, portIndex) => (
|
||||
<Stack.Item key={portIndex}>
|
||||
<Port
|
||||
port={port}
|
||||
portIndex={portIndex + 1}
|
||||
componentId={index}
|
||||
onPortLoaded={onPortLoaded}
|
||||
onPortUpdated={onPortUpdated}
|
||||
/>
|
||||
</Stack.Item>
|
||||
))}
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Stack.Item ml={5}>
|
||||
<Stack vertical>
|
||||
{output_ports.map((port, portIndex) => (
|
||||
<Stack.Item key={portIndex}>
|
||||
<Port
|
||||
port={port}
|
||||
portIndex={portIndex + 1}
|
||||
componentId={index}
|
||||
onPortLoaded={onPortLoaded}
|
||||
onPortUpdated={onPortUpdated}
|
||||
isOutput
|
||||
/>
|
||||
</Stack.Item>
|
||||
))}
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class Port extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.iconRef = createRef();
|
||||
|
||||
this.componentDidUpdate = this.componentDidUpdate.bind(this);
|
||||
this.componentDidMount = this.componentDidMount.bind(this);
|
||||
this.handlePortClick = this.handlePortClick.bind(this);
|
||||
this.handlePortRightClick = this.handlePortRightClick.bind(this);
|
||||
}
|
||||
|
||||
handlePortClick() {
|
||||
const { act } = useBackend(this.context);
|
||||
const [selectedPort, setSelectedPort] = useLocalState(
|
||||
this.context,
|
||||
'selected_port',
|
||||
null
|
||||
);
|
||||
|
||||
const { port, portIndex, componentId, isOutput } = this.props;
|
||||
|
||||
if (selectedPort) {
|
||||
if (selectedPort.ref === port.ref) {
|
||||
setSelectedPort(null);
|
||||
return;
|
||||
} else {
|
||||
if (selectedPort.is_output === isOutput) {
|
||||
setSelectedPort(null);
|
||||
return;
|
||||
}
|
||||
let data;
|
||||
if (isOutput) {
|
||||
data = {
|
||||
input_port_id: selectedPort.index,
|
||||
output_port_id: portIndex,
|
||||
input_component_id: selectedPort.component_id,
|
||||
output_component_id: componentId,
|
||||
};
|
||||
} else {
|
||||
data = {
|
||||
input_port_id: portIndex,
|
||||
output_port_id: selectedPort.index,
|
||||
input_component_id: componentId,
|
||||
output_component_id: selectedPort.component_id,
|
||||
};
|
||||
}
|
||||
act('add_connection', data);
|
||||
setSelectedPort(null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
setSelectedPort({
|
||||
index: portIndex,
|
||||
component_id: componentId,
|
||||
is_output: isOutput,
|
||||
ref: port.ref,
|
||||
});
|
||||
}
|
||||
|
||||
handlePortRightClick(e) {
|
||||
const { act } = useBackend(this.context);
|
||||
const { port, portIndex, componentId, isOutput, ...rest } = this.props;
|
||||
|
||||
e.preventDefault();
|
||||
act('remove_connection', {
|
||||
component_id: componentId,
|
||||
is_input: !isOutput,
|
||||
port_id: portIndex,
|
||||
});
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
const { port, onPortUpdated } = this.props;
|
||||
if (onPortUpdated) {
|
||||
onPortUpdated(port, this.iconRef.current);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { port, onPortLoaded } = this.props;
|
||||
if (onPortLoaded) {
|
||||
onPortLoaded(port, this.iconRef.current);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { port, portIndex, componentId, isOutput, ...rest } = this.props;
|
||||
|
||||
const [selectedPort, setSelectedPort] = useLocalState(
|
||||
this.context,
|
||||
'selected_port',
|
||||
null
|
||||
);
|
||||
|
||||
return (
|
||||
<Stack {...rest} justify={isOutput ? 'flex-end' : 'flex-start'}>
|
||||
{!!isOutput && (
|
||||
<Stack.Item>
|
||||
<DisplayName
|
||||
port={port}
|
||||
isOutput={isOutput}
|
||||
componentId={componentId}
|
||||
portIndex={portIndex}
|
||||
/>
|
||||
</Stack.Item>
|
||||
)}
|
||||
<Stack.Item>
|
||||
<Icon
|
||||
color={port.color || 'blue'}
|
||||
name={
|
||||
selectedPort && selectedPort.ref === port.ref
|
||||
? 'dot-circle'
|
||||
: 'circle'
|
||||
}
|
||||
position="relative"
|
||||
onClick={this.handlePortClick}
|
||||
onContextMenu={this.handlePortRightClick}>
|
||||
<span ref={this.iconRef} className="ObjectComponent__PortPos" />
|
||||
</Icon>
|
||||
</Stack.Item>
|
||||
{!isOutput && (
|
||||
<Stack.Item>
|
||||
<DisplayName
|
||||
port={port}
|
||||
isOutput={isOutput}
|
||||
componentId={componentId}
|
||||
portIndex={portIndex}
|
||||
/>
|
||||
</Stack.Item>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const DisplayName = (props, context) => {
|
||||
const { act } = useBackend(context);
|
||||
const { port, isOutput, componentId, portIndex, ...rest } = props;
|
||||
|
||||
const InputComponent = FUNDAMENTAL_DATA_TYPES[port.type || 'any'];
|
||||
|
||||
const hasInput
|
||||
= !isOutput && port.connected_to === NULL_REF && InputComponent;
|
||||
|
||||
return (
|
||||
<Box {...rest}>
|
||||
<Flex direction="column">
|
||||
<Flex.Item>
|
||||
{(hasInput && (
|
||||
<InputComponent
|
||||
setValue={(val, extraParams) =>
|
||||
act('set_component_input', {
|
||||
component_id: componentId,
|
||||
port_id: portIndex,
|
||||
input: val,
|
||||
...extraParams,
|
||||
})}
|
||||
color={port.color}
|
||||
name={port.name}
|
||||
value={port.current_data}
|
||||
/>
|
||||
))
|
||||
|| (isOutput && (
|
||||
<Button
|
||||
compact
|
||||
color="transparent"
|
||||
onClick={() =>
|
||||
act('get_component_value', {
|
||||
component_id: componentId,
|
||||
port_id: portIndex,
|
||||
})}>
|
||||
<Box color="white">{port.name}</Box>
|
||||
</Button>
|
||||
))
|
||||
|| port.name}
|
||||
</Flex.Item>
|
||||
<Flex.Item>
|
||||
<Box
|
||||
fontSize={0.75}
|
||||
opacity={0.25}
|
||||
textAlign={isOutput ? 'right' : 'left'}>
|
||||
{port.type || 'any'}
|
||||
</Box>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user