mirror of
https://github.com/CHOMPStation2/CHOMPStation2.git
synced 2025-12-10 18:22:39 +00:00
[MIRROR] Switch circuits to a grid visual coding system (#9173)
Co-authored-by: Heroman3003 <31296024+Heroman3003@users.noreply.github.com> Co-authored-by: CHOMPStation2 <chompsation2@gmail.com>
This commit is contained in:
4
code/modules/asset_cache/assets/circuits.dm
Normal file
4
code/modules/asset_cache/assets/circuits.dm
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
/datum/asset/simple/circuit_assets
|
||||||
|
assets = list(
|
||||||
|
"grid_background.png" = 'icons/UI_Icons/tgui/grid_background.png',
|
||||||
|
)
|
||||||
@@ -60,6 +60,11 @@
|
|||||||
ui = new(user, src, "ICAssembly", name, parent_ui)
|
ui = new(user, src, "ICAssembly", name, parent_ui)
|
||||||
ui.open()
|
ui.open()
|
||||||
|
|
||||||
|
/obj/item/electronic_assembly/ui_assets(mob/user)
|
||||||
|
return list(
|
||||||
|
get_asset_datum(/datum/asset/simple/circuit_assets)
|
||||||
|
)
|
||||||
|
|
||||||
/obj/item/electronic_assembly/tgui_data(mob/user, datum/tgui/ui, datum/tgui_state/state)
|
/obj/item/electronic_assembly/tgui_data(mob/user, datum/tgui/ui, datum/tgui_state/state)
|
||||||
var/list/data = ..()
|
var/list/data = ..()
|
||||||
|
|
||||||
@@ -78,19 +83,10 @@
|
|||||||
data["battery_max"] = round(battery?.maxcharge, 0.1)
|
data["battery_max"] = round(battery?.maxcharge, 0.1)
|
||||||
data["net_power"] = net_power / CELLRATE
|
data["net_power"] = net_power / CELLRATE
|
||||||
|
|
||||||
// This works because lists are always passed by reference in BYOND, so modifying unremovable_circuits
|
var/list/circuits = list()
|
||||||
// after setting data["unremovable_circuits"] = unremovable_circuits also modifies data["unremovable_circuits"]
|
|
||||||
// Same for the removable one
|
|
||||||
var/list/unremovable_circuits = list()
|
|
||||||
data["unremovable_circuits"] = unremovable_circuits
|
|
||||||
var/list/removable_circuits = list()
|
|
||||||
data["removable_circuits"] = removable_circuits
|
|
||||||
for(var/obj/item/integrated_circuit/circuit in contents)
|
for(var/obj/item/integrated_circuit/circuit in contents)
|
||||||
var/list/target = circuit.removable ? removable_circuits : unremovable_circuits
|
UNTYPED_LIST_ADD(circuits, circuit.tgui_data(user, ui, state))
|
||||||
target.Add(list(list(
|
data["circuits"] = circuits
|
||||||
"name" = circuit.displayed_name,
|
|
||||||
"ref" = REF(circuit),
|
|
||||||
)))
|
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@@ -98,8 +94,6 @@
|
|||||||
if(..())
|
if(..())
|
||||||
return TRUE
|
return TRUE
|
||||||
|
|
||||||
var/obj/held_item = usr.get_active_hand()
|
|
||||||
|
|
||||||
switch(action)
|
switch(action)
|
||||||
// Actual assembly actions
|
// Actual assembly actions
|
||||||
if("rename")
|
if("rename")
|
||||||
@@ -118,6 +112,48 @@
|
|||||||
return TRUE
|
return TRUE
|
||||||
|
|
||||||
// Circuit actions
|
// Circuit actions
|
||||||
|
if("wire_internal")
|
||||||
|
var/datum/integrated_io/pin1 = locate(params["pin1"])
|
||||||
|
if(!istype(pin1))
|
||||||
|
return
|
||||||
|
var/datum/integrated_io/pin2 = locate(params["pin2"])
|
||||||
|
if(!istype(pin2))
|
||||||
|
return
|
||||||
|
|
||||||
|
var/obj/item/integrated_circuit/holder1 = pin1.holder
|
||||||
|
if(!istype(holder1) || holder1.loc != src || holder1.assembly != src)
|
||||||
|
return
|
||||||
|
|
||||||
|
var/obj/item/integrated_circuit/holder2 = pin2.holder
|
||||||
|
if(!istype(holder2) || holder2.loc != src || holder2.assembly != src)
|
||||||
|
return
|
||||||
|
|
||||||
|
// Wiring the same pin will unwire it
|
||||||
|
if(pin2 in pin1.linked)
|
||||||
|
pin1.linked -= pin2
|
||||||
|
pin2.linked -= pin1
|
||||||
|
else
|
||||||
|
pin1.linked |= pin2
|
||||||
|
pin2.linked |= pin1
|
||||||
|
|
||||||
|
return TRUE
|
||||||
|
|
||||||
|
if("remove_all_wires")
|
||||||
|
var/datum/integrated_io/pin1 = locate(params["pin"])
|
||||||
|
if(!istype(pin1))
|
||||||
|
return
|
||||||
|
|
||||||
|
var/obj/item/integrated_circuit/holder1 = pin1.holder
|
||||||
|
if(!istype(holder1) || holder1.loc != src || holder1.assembly != src)
|
||||||
|
return
|
||||||
|
|
||||||
|
for(var/datum/integrated_io/other as anything in pin1.linked)
|
||||||
|
other.linked -= pin1
|
||||||
|
|
||||||
|
pin1.linked.Cut()
|
||||||
|
|
||||||
|
return TRUE
|
||||||
|
|
||||||
if("open_circuit")
|
if("open_circuit")
|
||||||
var/obj/item/integrated_circuit/C = locate(params["ref"]) in contents
|
var/obj/item/integrated_circuit/C = locate(params["ref"]) in contents
|
||||||
if(!istype(C))
|
if(!istype(C))
|
||||||
@@ -125,27 +161,6 @@
|
|||||||
C.tgui_interact(usr, null, ui)
|
C.tgui_interact(usr, null, ui)
|
||||||
return TRUE
|
return TRUE
|
||||||
|
|
||||||
if("rename_circuit")
|
|
||||||
var/obj/item/integrated_circuit/C = locate(params["ref"]) in contents
|
|
||||||
if(!istype(C))
|
|
||||||
return
|
|
||||||
C.rename_component(usr)
|
|
||||||
return TRUE
|
|
||||||
|
|
||||||
if("scan_circuit")
|
|
||||||
var/obj/item/integrated_circuit/C = locate(params["ref"]) in contents
|
|
||||||
if(!istype(C))
|
|
||||||
return
|
|
||||||
if(istype(held_item, /obj/item/integrated_electronics/debugger))
|
|
||||||
var/obj/item/integrated_electronics/debugger/D = held_item
|
|
||||||
if(D.accepting_refs)
|
|
||||||
D.afterattack(C, usr, TRUE)
|
|
||||||
else
|
|
||||||
to_chat(usr, span_warning("The Debugger's 'ref scanner' needs to be on."))
|
|
||||||
else
|
|
||||||
to_chat(usr, span_warning("You need a multitool/debugger set to 'ref' mode to do that."))
|
|
||||||
return TRUE
|
|
||||||
|
|
||||||
if("remove_circuit")
|
if("remove_circuit")
|
||||||
var/obj/item/integrated_circuit/C = locate(params["ref"]) in contents
|
var/obj/item/integrated_circuit/C = locate(params["ref"]) in contents
|
||||||
if(!istype(C))
|
if(!istype(C))
|
||||||
@@ -153,14 +168,6 @@
|
|||||||
C.remove(usr)
|
C.remove(usr)
|
||||||
return TRUE
|
return TRUE
|
||||||
|
|
||||||
if("bottom_circuit")
|
|
||||||
var/obj/item/integrated_circuit/C = locate(params["ref"]) in contents
|
|
||||||
if(!istype(C))
|
|
||||||
return
|
|
||||||
// Puts it at the bottom of our contents
|
|
||||||
// Note, this intentionally does *not* use forceMove, because forceMove will stop if it detects the same loc
|
|
||||||
C.loc = null
|
|
||||||
C.loc = src
|
|
||||||
return FALSE
|
return FALSE
|
||||||
// End TGUI
|
// End TGUI
|
||||||
|
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ a creative player the means to solve many problems. Circuits are held inside an
|
|||||||
|
|
||||||
data["name"] = name
|
data["name"] = name
|
||||||
data["desc"] = desc
|
data["desc"] = desc
|
||||||
|
data["ref"] = REF(src)
|
||||||
data["displayed_name"] = displayed_name
|
data["displayed_name"] = displayed_name
|
||||||
data["removable"] = removable
|
data["removable"] = removable
|
||||||
|
|
||||||
@@ -110,21 +111,7 @@ a creative player the means to solve many problems. Circuits are held inside an
|
|||||||
outputs_list.Add(list(tgui_pin_data(io)))
|
outputs_list.Add(list(tgui_pin_data(io)))
|
||||||
|
|
||||||
for(var/datum/integrated_io/io in activators)
|
for(var/datum/integrated_io/io in activators)
|
||||||
var/list/list/activator = list(
|
activators_list.Add(list(tgui_pin_data(io)))
|
||||||
"ref" = REF(io),
|
|
||||||
"name" = io.name,
|
|
||||||
"pulse_out" = io.data,
|
|
||||||
"linked" = list()
|
|
||||||
)
|
|
||||||
for(var/datum/integrated_io/linked in io.linked)
|
|
||||||
activator["linked"].Add(list(list(
|
|
||||||
"ref" = REF(linked),
|
|
||||||
"name" = linked.name,
|
|
||||||
"holder_ref" = REF(linked.holder),
|
|
||||||
"holder_name" = linked.holder.displayed_name,
|
|
||||||
)))
|
|
||||||
|
|
||||||
activators_list.Add(list(activator))
|
|
||||||
|
|
||||||
data["inputs"] = inputs_list
|
data["inputs"] = inputs_list
|
||||||
data["outputs"] = outputs_list
|
data["outputs"] = outputs_list
|
||||||
@@ -139,6 +126,7 @@ a creative player the means to solve many problems. Circuits are held inside an
|
|||||||
pindata["type"] = io.display_pin_type()
|
pindata["type"] = io.display_pin_type()
|
||||||
pindata["name"] = io.name
|
pindata["name"] = io.name
|
||||||
pindata["data"] = io.display_data(io.data)
|
pindata["data"] = io.display_data(io.data)
|
||||||
|
pindata["rawdata"] = io.data
|
||||||
pindata["ref"] = REF(io)
|
pindata["ref"] = REF(io)
|
||||||
var/list/linked_list = list()
|
var/list/linked_list = list()
|
||||||
for(var/datum/integrated_io/linked in io.linked)
|
for(var/datum/integrated_io/linked in io.linked)
|
||||||
|
|||||||
BIN
icons/UI_Icons/tgui/grid_background.png
Normal file
BIN
icons/UI_Icons/tgui/grid_background.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
222
tgui/packages/tgui/components/InfinitePlane.tsx
Normal file
222
tgui/packages/tgui/components/InfinitePlane.tsx
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
// TODO: Replace when tgui-core is fixed https://github.com/tgstation/tgui-core/issues/25
|
||||||
|
|
||||||
|
import { round } from 'common/math';
|
||||||
|
import { Component, PropsWithChildren } from 'react';
|
||||||
|
import { Button, ProgressBar, Stack } from 'tgui-core/components';
|
||||||
|
|
||||||
|
import { BoxProps, computeBoxProps } from './Box';
|
||||||
|
|
||||||
|
const ZOOM_MIN_VAL = 0.5;
|
||||||
|
const ZOOM_MAX_VAL = 1.5;
|
||||||
|
|
||||||
|
const ZOOM_INCREMENT = 0.1;
|
||||||
|
|
||||||
|
export type InfinitePlaneProps = PropsWithChildren<
|
||||||
|
{
|
||||||
|
onZoomChange?: (newZoomValue: number) => void;
|
||||||
|
onBackgroundMoved?: (newX: number, newY: number) => void;
|
||||||
|
initialLeft?: number;
|
||||||
|
initialTop?: number;
|
||||||
|
backgroundImage?: string;
|
||||||
|
imageWidth: number;
|
||||||
|
} & BoxProps
|
||||||
|
>;
|
||||||
|
|
||||||
|
type InfinitePlaneState = {
|
||||||
|
mouseDown: boolean;
|
||||||
|
|
||||||
|
left: number;
|
||||||
|
top: number;
|
||||||
|
|
||||||
|
lastLeft: number;
|
||||||
|
lastTop: number;
|
||||||
|
|
||||||
|
zoom: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MouseEventExtension = {
|
||||||
|
screenZoomX: number;
|
||||||
|
screenZoomY: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class InfinitePlane extends Component<
|
||||||
|
InfinitePlaneProps,
|
||||||
|
InfinitePlaneState
|
||||||
|
> {
|
||||||
|
constructor(props: InfinitePlaneProps) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
mouseDown: false,
|
||||||
|
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
|
||||||
|
lastLeft: 0,
|
||||||
|
lastTop: 0,
|
||||||
|
|
||||||
|
zoom: 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
window.addEventListener('mouseup', this.onMouseUp);
|
||||||
|
|
||||||
|
window.addEventListener('mousedown', this.doOffsetMouse);
|
||||||
|
window.addEventListener('mousemove', this.doOffsetMouse);
|
||||||
|
window.addEventListener('mouseup', this.doOffsetMouse);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
window.removeEventListener('mouseup', this.onMouseUp);
|
||||||
|
|
||||||
|
window.removeEventListener('mousedown', this.doOffsetMouse);
|
||||||
|
window.removeEventListener('mousemove', this.doOffsetMouse);
|
||||||
|
window.removeEventListener('mouseup', this.doOffsetMouse);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is really, REALLY cursed and basically overrides a built-in browser event via propagation rules
|
||||||
|
doOffsetMouse = (event: MouseEvent & MouseEventExtension) => {
|
||||||
|
const { zoom } = this.state;
|
||||||
|
event.screenZoomX = event.screenX * Math.pow(zoom, -1);
|
||||||
|
event.screenZoomY = event.screenY * Math.pow(zoom, -1);
|
||||||
|
};
|
||||||
|
|
||||||
|
handleMouseDown = (event: React.MouseEvent<HTMLDivElement>) => {
|
||||||
|
this.setState((state) => {
|
||||||
|
return {
|
||||||
|
mouseDown: true,
|
||||||
|
lastLeft: event.clientX - state.left,
|
||||||
|
lastTop: event.clientY - state.top,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onMouseUp = () => {
|
||||||
|
this.setState({
|
||||||
|
mouseDown: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleZoomIncrease = (event: any) => {
|
||||||
|
const { onZoomChange } = this.props;
|
||||||
|
const { zoom } = this.state;
|
||||||
|
const newZoomValue = round(
|
||||||
|
Math.min(zoom + ZOOM_INCREMENT, ZOOM_MAX_VAL),
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
this.setState({
|
||||||
|
zoom: newZoomValue,
|
||||||
|
});
|
||||||
|
if (onZoomChange) {
|
||||||
|
onZoomChange(newZoomValue);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleZoomDecrease = (event: any) => {
|
||||||
|
const { onZoomChange } = this.props;
|
||||||
|
const { zoom } = this.state;
|
||||||
|
const newZoomValue = round(
|
||||||
|
Math.max(zoom - ZOOM_INCREMENT, ZOOM_MIN_VAL),
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
this.setState({
|
||||||
|
zoom: newZoomValue,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (onZoomChange) {
|
||||||
|
onZoomChange(newZoomValue);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleMouseMove = (event: React.MouseEvent<HTMLDivElement>) => {
|
||||||
|
const { onBackgroundMoved, initialLeft = 0, initialTop = 0 } = this.props;
|
||||||
|
if (this.state.mouseDown) {
|
||||||
|
let newX, newY;
|
||||||
|
this.setState((state) => {
|
||||||
|
newX = event.clientX - state.lastLeft;
|
||||||
|
newY = event.clientY - state.lastTop;
|
||||||
|
if (onBackgroundMoved) {
|
||||||
|
onBackgroundMoved(newX + initialLeft, newY + initialTop);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
left: newX,
|
||||||
|
top: newY,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
children,
|
||||||
|
backgroundImage,
|
||||||
|
imageWidth,
|
||||||
|
initialLeft = 0,
|
||||||
|
initialTop = 0,
|
||||||
|
...rest
|
||||||
|
} = this.props;
|
||||||
|
const { left, top, zoom } = this.state;
|
||||||
|
|
||||||
|
const finalLeft = initialLeft + left;
|
||||||
|
const finalTop = initialTop + top;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
{...computeBoxProps({
|
||||||
|
...rest,
|
||||||
|
style: {
|
||||||
|
...rest.style,
|
||||||
|
overflow: 'hidden',
|
||||||
|
position: 'relative',
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
onMouseDown={this.handleMouseDown}
|
||||||
|
onMouseMove={this.handleMouseMove}
|
||||||
|
style={{
|
||||||
|
position: 'fixed',
|
||||||
|
height: '100%',
|
||||||
|
width: '100%',
|
||||||
|
backgroundImage: `url("${backgroundImage}")`,
|
||||||
|
backgroundPosition: `${finalLeft}px ${finalTop}px`,
|
||||||
|
backgroundRepeat: 'repeat',
|
||||||
|
backgroundSize: `${zoom * imageWidth}px`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
onMouseDown={this.handleMouseDown}
|
||||||
|
onMouseMove={this.handleMouseMove}
|
||||||
|
style={{
|
||||||
|
position: 'fixed',
|
||||||
|
transform: `translate(${finalLeft}px, ${finalTop}px) scale(${zoom})`,
|
||||||
|
transformOrigin: 'top left',
|
||||||
|
height: '100%',
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Stack position="absolute" width="100%">
|
||||||
|
<Stack.Item>
|
||||||
|
<Button icon="minus" onClick={this.handleZoomDecrease} />
|
||||||
|
</Stack.Item>
|
||||||
|
<Stack.Item grow={1}>
|
||||||
|
<ProgressBar
|
||||||
|
minValue={ZOOM_MIN_VAL}
|
||||||
|
value={zoom}
|
||||||
|
maxValue={ZOOM_MAX_VAL}
|
||||||
|
>
|
||||||
|
{zoom}x
|
||||||
|
</ProgressBar>
|
||||||
|
</Stack.Item>
|
||||||
|
<Stack.Item>
|
||||||
|
<Button icon="plus" onClick={this.handleZoomIncrease} />
|
||||||
|
</Stack.Item>
|
||||||
|
</Stack>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ export { Dropdown } from './Dropdown';
|
|||||||
export { Flex } from './Flex';
|
export { Flex } from './Flex';
|
||||||
export { Icon } from './Icon';
|
export { Icon } from './Icon';
|
||||||
export { Image } from './Image';
|
export { Image } from './Image';
|
||||||
|
export { InfinitePlane } from './InfinitePlane';
|
||||||
export { Input } from './Input';
|
export { Input } from './Input';
|
||||||
export { Knob } from './Knob';
|
export { Knob } from './Knob';
|
||||||
export { LabeledControls } from './LabeledControls';
|
export { LabeledControls } from './LabeledControls';
|
||||||
|
|||||||
@@ -1,179 +0,0 @@
|
|||||||
import { toFixed } from 'common/math';
|
|
||||||
|
|
||||||
import { useBackend } from '../backend';
|
|
||||||
import {
|
|
||||||
AnimatedNumber,
|
|
||||||
Box,
|
|
||||||
Button,
|
|
||||||
LabeledList,
|
|
||||||
ProgressBar,
|
|
||||||
Section,
|
|
||||||
} from '../components';
|
|
||||||
import { formatPower } from '../format';
|
|
||||||
import { Window } from '../layouts';
|
|
||||||
|
|
||||||
type Data = {
|
|
||||||
total_parts: number;
|
|
||||||
max_components: number;
|
|
||||||
total_complexity: number;
|
|
||||||
max_complexity: number;
|
|
||||||
battery_charge: number;
|
|
||||||
battery_max: number;
|
|
||||||
net_power: number;
|
|
||||||
unremovable_circuits: circuit[];
|
|
||||||
removable_circuits: circuit[];
|
|
||||||
};
|
|
||||||
|
|
||||||
type circuit = { name: string; ref: string };
|
|
||||||
|
|
||||||
export const ICAssembly = (props) => {
|
|
||||||
const { data } = useBackend<Data>();
|
|
||||||
|
|
||||||
const {
|
|
||||||
total_parts,
|
|
||||||
max_components,
|
|
||||||
total_complexity,
|
|
||||||
max_complexity,
|
|
||||||
battery_charge,
|
|
||||||
battery_max,
|
|
||||||
net_power,
|
|
||||||
unremovable_circuits,
|
|
||||||
removable_circuits,
|
|
||||||
} = data;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Window width={600} height={380}>
|
|
||||||
<Window.Content scrollable>
|
|
||||||
<Section title="Status">
|
|
||||||
<LabeledList>
|
|
||||||
<LabeledList.Item label="Space in Assembly">
|
|
||||||
<ProgressBar
|
|
||||||
ranges={{
|
|
||||||
good: [0, 0.25],
|
|
||||||
average: [0.5, 0.75],
|
|
||||||
bad: [0.75, 1],
|
|
||||||
}}
|
|
||||||
value={total_parts / max_components}
|
|
||||||
maxValue={1}
|
|
||||||
>
|
|
||||||
{total_parts +
|
|
||||||
' / ' +
|
|
||||||
max_components +
|
|
||||||
' (' +
|
|
||||||
toFixed((total_parts / max_components) * 100, 1) +
|
|
||||||
'%)'}
|
|
||||||
</ProgressBar>
|
|
||||||
</LabeledList.Item>
|
|
||||||
<LabeledList.Item label="Complexity">
|
|
||||||
<ProgressBar
|
|
||||||
ranges={{
|
|
||||||
good: [0, 0.25],
|
|
||||||
average: [0.5, 0.75],
|
|
||||||
bad: [0.75, 1],
|
|
||||||
}}
|
|
||||||
value={total_complexity / max_complexity}
|
|
||||||
maxValue={1}
|
|
||||||
>
|
|
||||||
{total_complexity +
|
|
||||||
' / ' +
|
|
||||||
max_complexity +
|
|
||||||
' (' +
|
|
||||||
toFixed((total_complexity / max_complexity) * 100, 1) +
|
|
||||||
'%)'}
|
|
||||||
</ProgressBar>
|
|
||||||
</LabeledList.Item>
|
|
||||||
<LabeledList.Item label="Cell Charge">
|
|
||||||
{(battery_charge && (
|
|
||||||
<ProgressBar
|
|
||||||
ranges={{
|
|
||||||
bad: [0, 0.25],
|
|
||||||
average: [0.5, 0.75],
|
|
||||||
good: [0.75, 1],
|
|
||||||
}}
|
|
||||||
value={battery_charge / battery_max}
|
|
||||||
maxValue={1}
|
|
||||||
>
|
|
||||||
{battery_charge +
|
|
||||||
' / ' +
|
|
||||||
battery_max +
|
|
||||||
' (' +
|
|
||||||
toFixed((battery_charge / battery_max) * 100, 1) +
|
|
||||||
'%)'}
|
|
||||||
</ProgressBar>
|
|
||||||
)) || <Box color="bad">No cell detected.</Box>}
|
|
||||||
</LabeledList.Item>
|
|
||||||
<LabeledList.Item label="Net Energy">
|
|
||||||
{(net_power === 0 && '0 W/s') || (
|
|
||||||
<AnimatedNumber
|
|
||||||
value={net_power}
|
|
||||||
format={(val) => '-' + formatPower(Math.abs(val)) + '/s'}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</LabeledList.Item>
|
|
||||||
</LabeledList>
|
|
||||||
</Section>
|
|
||||||
{(unremovable_circuits.length && (
|
|
||||||
<ICAssemblyCircuits
|
|
||||||
title="Built-in Components"
|
|
||||||
circuits={unremovable_circuits}
|
|
||||||
/>
|
|
||||||
)) ||
|
|
||||||
null}
|
|
||||||
{(removable_circuits.length && (
|
|
||||||
<ICAssemblyCircuits
|
|
||||||
title="Removable Components"
|
|
||||||
circuits={removable_circuits}
|
|
||||||
/>
|
|
||||||
)) ||
|
|
||||||
null}
|
|
||||||
</Window.Content>
|
|
||||||
</Window>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const ICAssemblyCircuits = (props: { title: string; circuits: circuit[] }) => {
|
|
||||||
const { act } = useBackend();
|
|
||||||
|
|
||||||
const { title, circuits } = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Section title={title}>
|
|
||||||
<LabeledList>
|
|
||||||
{circuits.map((circuit) => (
|
|
||||||
<LabeledList.Item key={circuit.ref} label={circuit.name}>
|
|
||||||
<Button
|
|
||||||
icon="eye"
|
|
||||||
onClick={() => act('open_circuit', { ref: circuit.ref })}
|
|
||||||
>
|
|
||||||
View
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
icon="eye"
|
|
||||||
onClick={() => act('rename_circuit', { ref: circuit.ref })}
|
|
||||||
>
|
|
||||||
Rename
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
icon="eye"
|
|
||||||
onClick={() => act('scan_circuit', { ref: circuit.ref })}
|
|
||||||
>
|
|
||||||
Debugger Scan
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
icon="eye"
|
|
||||||
onClick={() => act('remove_circuit', { ref: circuit.ref })}
|
|
||||||
>
|
|
||||||
Remove
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
icon="eye"
|
|
||||||
onClick={() => act('bottom_circuit', { ref: circuit.ref })}
|
|
||||||
>
|
|
||||||
Move to Bottom
|
|
||||||
</Button>
|
|
||||||
</LabeledList.Item>
|
|
||||||
))}
|
|
||||||
</LabeledList>
|
|
||||||
</Section>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
318
tgui/packages/tgui/interfaces/ICAssembly/CircuitComponent.tsx
Normal file
318
tgui/packages/tgui/interfaces/ICAssembly/CircuitComponent.tsx
Normal file
@@ -0,0 +1,318 @@
|
|||||||
|
import { decodeHtmlEntities } from 'common/string';
|
||||||
|
import { Component } from 'react';
|
||||||
|
import { useBackend } from 'tgui/backend';
|
||||||
|
import { Box } from 'tgui/components';
|
||||||
|
import { BoxProps } from 'tgui/components/Box';
|
||||||
|
import { Button, Icon, Stack } from 'tgui-core/components';
|
||||||
|
import { shallowDiffers } from 'tgui-core/react';
|
||||||
|
|
||||||
|
import { Port, PortProps } from './Port';
|
||||||
|
import { CircuitData, PortTypesToColor as PORT_TYPES_TO_COLOR } from './types';
|
||||||
|
|
||||||
|
export type CircuitProps = {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
circuit: CircuitData;
|
||||||
|
color?: string;
|
||||||
|
gridMode?: boolean;
|
||||||
|
onComponentMoved?: (newPos: { x: number; y: number }) => void;
|
||||||
|
} & BoxProps &
|
||||||
|
Pick<
|
||||||
|
PortProps,
|
||||||
|
| 'onPortUpdated'
|
||||||
|
| 'onPortLoaded'
|
||||||
|
| 'onPortMouseDown'
|
||||||
|
| 'onPortMouseUp'
|
||||||
|
| 'onPortRightClick'
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type CircuitState = {
|
||||||
|
lastMousePos: { x: number; y: number } | null;
|
||||||
|
isDragging: boolean;
|
||||||
|
dragPos: { x: number; y: number } | null;
|
||||||
|
startPos: { x: number; y: number } | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// This has to be a class component to manage window state unfortunately
|
||||||
|
export class CircuitComponent extends Component<CircuitProps, CircuitState> {
|
||||||
|
constructor(props: CircuitProps) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
isDragging: false,
|
||||||
|
dragPos: null,
|
||||||
|
startPos: null,
|
||||||
|
lastMousePos: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// THIS IS IMPORTANT:
|
||||||
|
// This reduces the amount of unnecessary updates, which reduces the amount of work that has to be done
|
||||||
|
// by the `Plane` component to keep track of where ports are located.
|
||||||
|
shouldComponentUpdate = (nextProps, nextState) => {
|
||||||
|
const { inputs, outputs, activators } = this.props.circuit;
|
||||||
|
|
||||||
|
return (
|
||||||
|
shallowDiffers(this.props, nextProps) ||
|
||||||
|
shallowDiffers(this.state, nextState) ||
|
||||||
|
shallowDiffers(inputs, nextProps.inputs) ||
|
||||||
|
shallowDiffers(outputs, nextProps.outputs) ||
|
||||||
|
shallowDiffers(activators, nextProps.activators)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
handleStartDrag = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||||
|
const { x, y } = this.props;
|
||||||
|
e.stopPropagation();
|
||||||
|
this.setState({
|
||||||
|
lastMousePos: null,
|
||||||
|
isDragging: true,
|
||||||
|
dragPos: { x, y },
|
||||||
|
startPos: { x, y },
|
||||||
|
});
|
||||||
|
window.addEventListener('mousemove', this.handleDrag);
|
||||||
|
window.addEventListener('mouseup', this.handleStopDrag);
|
||||||
|
};
|
||||||
|
|
||||||
|
handleStopDrag = (e: React.MouseEvent<HTMLDivElement> | MouseEvent) => {
|
||||||
|
const { onComponentMoved } = this.props;
|
||||||
|
const { dragPos } = this.state;
|
||||||
|
|
||||||
|
if (dragPos && onComponentMoved) {
|
||||||
|
onComponentMoved({
|
||||||
|
x: this.roundToGrid(dragPos.x),
|
||||||
|
y: this.roundToGrid(dragPos.y),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
window.removeEventListener('mousemove', this.handleDrag);
|
||||||
|
window.removeEventListener('mouseup', this.handleStopDrag);
|
||||||
|
this.setState({ isDragging: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
handleDrag = (e: any) => {
|
||||||
|
const { dragPos, isDragging, lastMousePos } = this.state;
|
||||||
|
if (!dragPos || !isDragging) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Round the units to the grid (bypass if grid mode is off)
|
||||||
|
roundToGrid = (input_value) => {
|
||||||
|
if (!this.props.gridMode) return input_value;
|
||||||
|
return Math.round(input_value / 10) * 10;
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
circuit,
|
||||||
|
color = 'blue',
|
||||||
|
onPortUpdated,
|
||||||
|
onPortLoaded,
|
||||||
|
onPortMouseDown,
|
||||||
|
onPortMouseUp,
|
||||||
|
onPortRightClick,
|
||||||
|
...rest
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const { name, ref, inputs, outputs, activators } = circuit;
|
||||||
|
|
||||||
|
const { startPos, dragPos } = this.state;
|
||||||
|
|
||||||
|
const { act } = useBackend();
|
||||||
|
|
||||||
|
let [x_pos, y_pos] = [x, y];
|
||||||
|
if (dragPos && startPos && startPos.x === x_pos && startPos.y === y_pos) {
|
||||||
|
x_pos = this.roundToGrid(dragPos.x);
|
||||||
|
y_pos = this.roundToGrid(dragPos.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
position="absolute"
|
||||||
|
left={x_pos + 'px'}
|
||||||
|
top={y_pos + 'px'}
|
||||||
|
onMouseDown={this.handleStartDrag}
|
||||||
|
onMouseUp={this.handleStopDrag}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
backgroundColor={color}
|
||||||
|
py={1}
|
||||||
|
px={1}
|
||||||
|
className="ObjectComponent__Titlebar"
|
||||||
|
>
|
||||||
|
<Stack align="center" justify="space-between">
|
||||||
|
<Stack.Item>{name}</Stack.Item>
|
||||||
|
<Stack.Item>
|
||||||
|
<Button
|
||||||
|
icon="external-link-alt"
|
||||||
|
color="transparent"
|
||||||
|
compact
|
||||||
|
tooltip="View Component UI"
|
||||||
|
tooltipPosition="bottom-end"
|
||||||
|
onClick={() => act('open_circuit', { ref })}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
icon="info"
|
||||||
|
color="transparent"
|
||||||
|
compact
|
||||||
|
tooltip={
|
||||||
|
<Box>
|
||||||
|
<Box mb={1}>{circuit.extended_desc}</Box>
|
||||||
|
{!!circuit.power_draw_idle && (
|
||||||
|
<Box
|
||||||
|
backgroundColor="orange"
|
||||||
|
style={{ borderRadius: '4px' }}
|
||||||
|
px={1}
|
||||||
|
>
|
||||||
|
<Icon name="bolt" mr={1} />
|
||||||
|
Power Draw (Passive): {circuit.power_draw_idle}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
{!!circuit.power_draw_per_use && (
|
||||||
|
<Box
|
||||||
|
backgroundColor="orange"
|
||||||
|
style={{ borderRadius: '4px' }}
|
||||||
|
px={1}
|
||||||
|
>
|
||||||
|
<Icon name="bolt" mr={1} />
|
||||||
|
Power Draw (Active): {circuit.power_draw_per_use}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
tooltipPosition="bottom-end"
|
||||||
|
/>
|
||||||
|
{!!circuit.removable && (
|
||||||
|
<Button
|
||||||
|
icon="times"
|
||||||
|
color="transparent"
|
||||||
|
compact
|
||||||
|
tooltip="Remove Circuit"
|
||||||
|
tooltipPosition="bottom-end"
|
||||||
|
onClick={() => act('remove_circuit', { ref })}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Stack.Item>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
<Box className="ObjectComponent__Content" p={1}>
|
||||||
|
<Stack vertical>
|
||||||
|
<Stack.Item>
|
||||||
|
<Stack align="flex-start" justify="space-between">
|
||||||
|
<Stack.Item>
|
||||||
|
<Stack vertical fill>
|
||||||
|
{inputs.map((port) => (
|
||||||
|
<Stack.Item key={port.ref}>
|
||||||
|
<Port
|
||||||
|
color={
|
||||||
|
PORT_TYPES_TO_COLOR[decodeHtmlEntities(port.type)]
|
||||||
|
}
|
||||||
|
port={port}
|
||||||
|
onPortUpdated={onPortUpdated}
|
||||||
|
onPortLoaded={onPortLoaded}
|
||||||
|
onPortMouseDown={onPortMouseDown}
|
||||||
|
onPortMouseUp={onPortMouseUp}
|
||||||
|
onPortRightClick={onPortRightClick}
|
||||||
|
/>
|
||||||
|
</Stack.Item>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
</Stack.Item>
|
||||||
|
<Stack.Item ml={5}>
|
||||||
|
<Stack vertical>
|
||||||
|
{outputs.map((port) => (
|
||||||
|
<Stack.Item key={port.ref}>
|
||||||
|
<Port
|
||||||
|
color={
|
||||||
|
PORT_TYPES_TO_COLOR[decodeHtmlEntities(port.type)]
|
||||||
|
}
|
||||||
|
output
|
||||||
|
port={port}
|
||||||
|
onPortUpdated={onPortUpdated}
|
||||||
|
onPortLoaded={onPortLoaded}
|
||||||
|
onPortMouseDown={onPortMouseDown}
|
||||||
|
onPortMouseUp={onPortMouseUp}
|
||||||
|
onPortRightClick={onPortRightClick}
|
||||||
|
/>
|
||||||
|
</Stack.Item>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
</Stack.Item>
|
||||||
|
</Stack>
|
||||||
|
</Stack.Item>
|
||||||
|
<Stack.Item mt={inputs.length || outputs.length ? 5 : 0}>
|
||||||
|
<Stack align="flex-start" justify="space-between">
|
||||||
|
<Stack.Item>
|
||||||
|
<Stack vertical>
|
||||||
|
{activators
|
||||||
|
.filter((port) => !port.rawdata)
|
||||||
|
.map((port) => (
|
||||||
|
<Stack.Item key={port.ref}>
|
||||||
|
<Port
|
||||||
|
color={
|
||||||
|
PORT_TYPES_TO_COLOR[decodeHtmlEntities(port.type)]
|
||||||
|
}
|
||||||
|
output={!!port.rawdata}
|
||||||
|
port={port}
|
||||||
|
onPortUpdated={onPortUpdated}
|
||||||
|
onPortLoaded={onPortLoaded}
|
||||||
|
onPortMouseDown={onPortMouseDown}
|
||||||
|
onPortMouseUp={onPortMouseUp}
|
||||||
|
onPortRightClick={onPortRightClick}
|
||||||
|
/>
|
||||||
|
</Stack.Item>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
</Stack.Item>
|
||||||
|
<Stack.Item>
|
||||||
|
<Stack vertical>
|
||||||
|
{activators
|
||||||
|
.filter((port) => !!port.rawdata)
|
||||||
|
.map((port) => (
|
||||||
|
<Stack.Item key={port.ref}>
|
||||||
|
<Port
|
||||||
|
color={
|
||||||
|
PORT_TYPES_TO_COLOR[decodeHtmlEntities(port.type)]
|
||||||
|
}
|
||||||
|
output={!!port.rawdata}
|
||||||
|
port={port}
|
||||||
|
onPortUpdated={onPortUpdated}
|
||||||
|
onPortLoaded={onPortLoaded}
|
||||||
|
onPortMouseDown={onPortMouseDown}
|
||||||
|
onPortMouseUp={onPortMouseUp}
|
||||||
|
onPortRightClick={onPortRightClick}
|
||||||
|
/>
|
||||||
|
</Stack.Item>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
</Stack.Item>
|
||||||
|
</Stack>
|
||||||
|
</Stack.Item>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
299
tgui/packages/tgui/interfaces/ICAssembly/Plane.tsx
Normal file
299
tgui/packages/tgui/interfaces/ICAssembly/Plane.tsx
Normal file
@@ -0,0 +1,299 @@
|
|||||||
|
import { decodeHtmlEntities } from 'common/string';
|
||||||
|
import { Component } from 'react';
|
||||||
|
import { resolveAsset } from 'tgui/assets';
|
||||||
|
import { useBackend, useSharedState } from 'tgui/backend';
|
||||||
|
// TODO: Replace when tgui-core is fixed https://github.com/tgstation/tgui-core/issues/25
|
||||||
|
import { InfinitePlane } from 'tgui/components';
|
||||||
|
|
||||||
|
import { Connection, Connections } from '../common/Connections';
|
||||||
|
import { CircuitComponent } from './CircuitComponent';
|
||||||
|
import { PortProps } from './Port';
|
||||||
|
import {
|
||||||
|
ABSOLUTE_Y_OFFSET,
|
||||||
|
CircuitData,
|
||||||
|
Data,
|
||||||
|
MOUSE_BUTTON_LEFT,
|
||||||
|
PortData,
|
||||||
|
PortTypesToColor,
|
||||||
|
TIME_UNTIL_PORT_RELEASE_WORKS,
|
||||||
|
} from './types';
|
||||||
|
|
||||||
|
export type PlaneProps = {};
|
||||||
|
|
||||||
|
type PlaneState = {
|
||||||
|
locations: Record<string, { x: number; y: number }>;
|
||||||
|
selectedPort: PortData | null;
|
||||||
|
timeUntilPortReleaseTimesOut: number;
|
||||||
|
backgroundX: number;
|
||||||
|
backgroundY: number;
|
||||||
|
zoom: number;
|
||||||
|
mouseX: number;
|
||||||
|
mouseY: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/prefer-stateless-function
|
||||||
|
export class Plane extends Component<PlaneProps, PlaneState> {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
locations: {},
|
||||||
|
selectedPort: null,
|
||||||
|
timeUntilPortReleaseTimesOut: 0,
|
||||||
|
backgroundX: 50,
|
||||||
|
backgroundY: 50,
|
||||||
|
zoom: 1,
|
||||||
|
mouseX: 0,
|
||||||
|
mouseY: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getPosition = (dom: HTMLElement | null) => {
|
||||||
|
let xPos = 0;
|
||||||
|
let yPos = 0;
|
||||||
|
|
||||||
|
while (dom) {
|
||||||
|
xPos += dom.offsetLeft;
|
||||||
|
yPos += dom.offsetTop;
|
||||||
|
dom = dom.offsetParent as HTMLElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
x: xPos,
|
||||||
|
y: yPos + ABSOLUTE_Y_OFFSET,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
handlePortLocation = (port: PortData, dom: HTMLSpanElement) => {
|
||||||
|
const { locations } = this.state;
|
||||||
|
|
||||||
|
if (!dom) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastPosition = locations[port.ref];
|
||||||
|
const position = this.getPosition(dom);
|
||||||
|
|
||||||
|
if (
|
||||||
|
isNaN(position.x) ||
|
||||||
|
isNaN(position.y) ||
|
||||||
|
(lastPosition &&
|
||||||
|
lastPosition.x === position.x &&
|
||||||
|
lastPosition.y === position.y)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
locations[port.ref] = position;
|
||||||
|
|
||||||
|
this.setState({ locations });
|
||||||
|
};
|
||||||
|
|
||||||
|
handleZoomChange = (newZoom) => {
|
||||||
|
this.setState({
|
||||||
|
zoom: newZoom,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleBackgroundMoved = (newX, newY) => {
|
||||||
|
this.setState({
|
||||||
|
backgroundX: newX,
|
||||||
|
backgroundY: newY,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleDragging = (event: MouseEvent) => {
|
||||||
|
this.setState((state) => ({
|
||||||
|
mouseX: event.clientX - state.backgroundX,
|
||||||
|
mouseY: event.clientY - state.backgroundY,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
handlePortUp = (port: PortData, ref: HTMLDivElement, event: MouseEvent) => {
|
||||||
|
const { act } = useBackend();
|
||||||
|
const { selectedPort } = this.state;
|
||||||
|
|
||||||
|
if (!selectedPort || selectedPort.ref === port.ref) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
selectedPort: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
act('wire_internal', { pin1: selectedPort.ref, pin2: port.ref });
|
||||||
|
};
|
||||||
|
|
||||||
|
handlePortRelease = (event: MouseEvent) => {
|
||||||
|
window.removeEventListener('mouseup', this.handlePortRelease);
|
||||||
|
|
||||||
|
// This will let players release their mouse when dragging
|
||||||
|
// to stop connecting the port, whilst letting players
|
||||||
|
// click on the port to click and connect.
|
||||||
|
if (this.state.timeUntilPortReleaseTimesOut > Date.now()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
selectedPort: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
window.removeEventListener('mousemove', this.handleDragging);
|
||||||
|
};
|
||||||
|
|
||||||
|
handlePortClick = (
|
||||||
|
port: PortData,
|
||||||
|
ref: HTMLDivElement,
|
||||||
|
event: MouseEvent,
|
||||||
|
) => {
|
||||||
|
if (this.state.selectedPort) {
|
||||||
|
this.handlePortUp(port, ref, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.button !== MOUSE_BUTTON_LEFT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.stopPropagation();
|
||||||
|
this.setState({
|
||||||
|
selectedPort: port,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.handleDragging(event);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
timeUntilPortReleaseTimesOut: Date.now() + TIME_UNTIL_PORT_RELEASE_WORKS,
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('mousemove', this.handleDragging);
|
||||||
|
window.addEventListener('mouseup', this.handlePortRelease);
|
||||||
|
};
|
||||||
|
|
||||||
|
handlePortRightClick = (
|
||||||
|
port: PortData,
|
||||||
|
ref: HTMLDivElement,
|
||||||
|
event: MouseEvent,
|
||||||
|
) => {
|
||||||
|
const { act } = useBackend();
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
act('remove_all_wires', {
|
||||||
|
pin: port.ref,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { act, data } = useBackend<Data>();
|
||||||
|
const { locations, selectedPort, mouseX, mouseY } = this.state;
|
||||||
|
|
||||||
|
const connections: Connection[] = [];
|
||||||
|
|
||||||
|
for (const circuit of data.circuits) {
|
||||||
|
for (const input of circuit.inputs) {
|
||||||
|
for (const output of input.linked) {
|
||||||
|
const output_port = locations[output.ref];
|
||||||
|
connections.push({
|
||||||
|
color: PortTypesToColor[decodeHtmlEntities(input.type)] || 'blue',
|
||||||
|
from: output_port,
|
||||||
|
to: locations[input.ref],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const activator of circuit.activators) {
|
||||||
|
if (activator.rawdata) {
|
||||||
|
// input
|
||||||
|
for (const output of activator.linked) {
|
||||||
|
const output_port = locations[output.ref];
|
||||||
|
connections.push({
|
||||||
|
color:
|
||||||
|
PortTypesToColor[decodeHtmlEntities(activator.type)] || 'blue',
|
||||||
|
to: output_port,
|
||||||
|
from: locations[activator.ref],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedPort) {
|
||||||
|
const { zoom } = this.state;
|
||||||
|
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:
|
||||||
|
PortTypesToColor[decodeHtmlEntities(selectedPort.type)] || 'blue',
|
||||||
|
from: portLocation,
|
||||||
|
to: mouseCoords,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InfinitePlane
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
backgroundImage={resolveAsset('grid_background.png')}
|
||||||
|
imageWidth={900}
|
||||||
|
onZoomChange={this.handleZoomChange}
|
||||||
|
onBackgroundMoved={this.handleBackgroundMoved}
|
||||||
|
initialLeft={50}
|
||||||
|
initialTop={50}
|
||||||
|
>
|
||||||
|
{data.circuits.map((circuit) => (
|
||||||
|
<Circuit
|
||||||
|
circuit={circuit}
|
||||||
|
key={circuit.ref}
|
||||||
|
onPortLoaded={this.handlePortLocation}
|
||||||
|
onPortUpdated={this.handlePortLocation}
|
||||||
|
onPortMouseDown={this.handlePortClick}
|
||||||
|
onPortMouseUp={this.handlePortUp}
|
||||||
|
onPortRightClick={this.handlePortRightClick}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<Connections connections={connections} />
|
||||||
|
</InfinitePlane>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Circuit = (
|
||||||
|
props: { circuit: CircuitData } & Pick<
|
||||||
|
PortProps,
|
||||||
|
| 'onPortUpdated'
|
||||||
|
| 'onPortLoaded'
|
||||||
|
| 'onPortMouseDown'
|
||||||
|
| 'onPortMouseUp'
|
||||||
|
| 'onPortRightClick'
|
||||||
|
>,
|
||||||
|
) => {
|
||||||
|
const {
|
||||||
|
circuit,
|
||||||
|
onPortUpdated,
|
||||||
|
onPortLoaded,
|
||||||
|
onPortMouseDown,
|
||||||
|
onPortMouseUp,
|
||||||
|
onPortRightClick,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const [pos, setPos] = useSharedState('component-pos-' + circuit.ref, {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CircuitComponent
|
||||||
|
circuit={circuit}
|
||||||
|
gridMode
|
||||||
|
x={pos.x}
|
||||||
|
y={pos.y}
|
||||||
|
onComponentMoved={(val) => setPos(val)}
|
||||||
|
onPortUpdated={onPortUpdated}
|
||||||
|
onPortLoaded={onPortLoaded}
|
||||||
|
onPortMouseDown={onPortMouseDown}
|
||||||
|
onPortMouseUp={onPortMouseUp}
|
||||||
|
onPortRightClick={onPortRightClick}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
117
tgui/packages/tgui/interfaces/ICAssembly/Port.tsx
Normal file
117
tgui/packages/tgui/interfaces/ICAssembly/Port.tsx
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
import { decodeHtmlEntities } from 'common/string';
|
||||||
|
import { Component, createRef } from 'react';
|
||||||
|
import { Box, Stack, Tooltip } from 'tgui-core/components';
|
||||||
|
|
||||||
|
import { PortData } from './types';
|
||||||
|
|
||||||
|
export type PortProps = {
|
||||||
|
port: PortData;
|
||||||
|
onPortUpdated?: (port: PortData, iconRef: HTMLSpanElement | null) => void;
|
||||||
|
onPortLoaded?: (port: PortData, iconRef: HTMLSpanElement | null) => void;
|
||||||
|
onPortMouseDown?: (
|
||||||
|
port: PortData,
|
||||||
|
iconRef: HTMLSpanElement | null,
|
||||||
|
e: MouseEvent,
|
||||||
|
) => void;
|
||||||
|
onPortMouseUp?: (
|
||||||
|
port: PortData,
|
||||||
|
iconRef: HTMLSpanElement | null,
|
||||||
|
e: MouseEvent,
|
||||||
|
) => void;
|
||||||
|
onPortRightClick?: (
|
||||||
|
port: PortData,
|
||||||
|
iconRef: HTMLSpanElement | null,
|
||||||
|
e: MouseEvent,
|
||||||
|
) => void;
|
||||||
|
color?: string;
|
||||||
|
output?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PortState = {
|
||||||
|
portRef: React.RefObject<HTMLSpanElement>;
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/prefer-stateless-function
|
||||||
|
export class Port extends Component<PortProps, PortState> {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
portRef: createRef(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePortMouseDown = (e) => {
|
||||||
|
const { port, onPortMouseDown = () => {} } = this.props;
|
||||||
|
onPortMouseDown(port, this.state.portRef.current, e);
|
||||||
|
};
|
||||||
|
|
||||||
|
handlePortMouseUp = (e) => {
|
||||||
|
const { port, onPortMouseUp = () => {} } = this.props;
|
||||||
|
onPortMouseUp(port, this.state.portRef.current, e);
|
||||||
|
};
|
||||||
|
|
||||||
|
handlePortRightClick = (e) => {
|
||||||
|
const { port, onPortRightClick = () => {} } = this.props;
|
||||||
|
onPortRightClick(port, this.state.portRef.current, e);
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidUpdate = () => {
|
||||||
|
const { port, onPortUpdated } = this.props;
|
||||||
|
if (onPortUpdated) {
|
||||||
|
onPortUpdated(port, this.state.portRef.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount = () => {
|
||||||
|
const { port, onPortLoaded } = this.props;
|
||||||
|
if (onPortLoaded) {
|
||||||
|
onPortLoaded(port, this.state.portRef.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
renderDisplayName = () => {
|
||||||
|
const { port } = this.props;
|
||||||
|
|
||||||
|
return <Stack.Item>{port.name}</Stack.Item>;
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { portRef: iconRef } = this.state;
|
||||||
|
const { port, color, output } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip
|
||||||
|
content={decodeHtmlEntities(port.type)}
|
||||||
|
position={'bottom-start'}
|
||||||
|
>
|
||||||
|
<Stack align="center" justify={output ? 'flex-end' : 'flex-start'}>
|
||||||
|
{!!output && this.renderDisplayName()}
|
||||||
|
<Stack.Item>
|
||||||
|
<Box
|
||||||
|
className="ObjectComponent__Port"
|
||||||
|
onMouseDown={this.handlePortMouseDown}
|
||||||
|
onContextMenu={this.handlePortRightClick}
|
||||||
|
onMouseUp={this.handlePortMouseUp}
|
||||||
|
textAlign="center"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
style={{ width: '100%', height: '100%', position: 'absolute' }}
|
||||||
|
viewBox="0, 0, 100, 100"
|
||||||
|
>
|
||||||
|
<circle
|
||||||
|
cx="50"
|
||||||
|
cy="50"
|
||||||
|
r="50"
|
||||||
|
className={`color-fill-${color}`}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span ref={iconRef} className="ObjectComponent__PortPos" />
|
||||||
|
</Box>
|
||||||
|
</Stack.Item>
|
||||||
|
{!output && this.renderDisplayName()}
|
||||||
|
</Stack>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
161
tgui/packages/tgui/interfaces/ICAssembly/index.tsx
Normal file
161
tgui/packages/tgui/interfaces/ICAssembly/index.tsx
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
import { toFixed } from 'common/math';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useBackend } from 'tgui/backend';
|
||||||
|
import { LabeledList } from 'tgui/components';
|
||||||
|
import { formatPower } from 'tgui/format';
|
||||||
|
import { Window } from 'tgui/layouts';
|
||||||
|
import {
|
||||||
|
AnimatedNumber,
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
ProgressBar,
|
||||||
|
Section,
|
||||||
|
} from 'tgui-core/components';
|
||||||
|
|
||||||
|
import { Plane } from './Plane';
|
||||||
|
import { Data } from './types';
|
||||||
|
|
||||||
|
export const ICAssembly = (props) => {
|
||||||
|
const [showInfo, setShowInfo] = useState(false);
|
||||||
|
|
||||||
|
const { act } = useBackend();
|
||||||
|
return (
|
||||||
|
<Window
|
||||||
|
buttons={
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
color="transparent"
|
||||||
|
width={2.5}
|
||||||
|
height={2}
|
||||||
|
textAlign="center"
|
||||||
|
icon="pencil"
|
||||||
|
tooltip="Edit Name"
|
||||||
|
tooltipPosition="bottom-start"
|
||||||
|
onClick={() => act('rename')}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
color="transparent"
|
||||||
|
width={2.5}
|
||||||
|
height={2}
|
||||||
|
textAlign="center"
|
||||||
|
icon="info"
|
||||||
|
tooltip="Circuit Info"
|
||||||
|
tooltipPosition="bottom-start"
|
||||||
|
selected={showInfo}
|
||||||
|
onClick={() => setShowInfo(!showInfo)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
width={1280}
|
||||||
|
height={800}
|
||||||
|
>
|
||||||
|
<Window.Content style={{ background: 'none' }}>
|
||||||
|
<Plane />
|
||||||
|
{showInfo && <CircuitInfo />}
|
||||||
|
</Window.Content>
|
||||||
|
</Window>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const CircuitInfo = (props) => {
|
||||||
|
const { act, data } = useBackend<Data>();
|
||||||
|
|
||||||
|
const {
|
||||||
|
total_parts,
|
||||||
|
max_components,
|
||||||
|
total_complexity,
|
||||||
|
max_complexity,
|
||||||
|
battery_charge,
|
||||||
|
battery_max,
|
||||||
|
net_power,
|
||||||
|
} = data;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Section
|
||||||
|
position="absolute"
|
||||||
|
top={4}
|
||||||
|
right={2}
|
||||||
|
backgroundColor="rgba(0, 0, 0, 0.7)"
|
||||||
|
style={{ borderRadius: '16px' }}
|
||||||
|
width={40}
|
||||||
|
p={2}
|
||||||
|
title="Circuit Info"
|
||||||
|
>
|
||||||
|
<LabeledList>
|
||||||
|
<LabeledList.Item label="Space in Assembly">
|
||||||
|
<ProgressBar
|
||||||
|
ranges={{
|
||||||
|
good: [0, 0.25],
|
||||||
|
average: [0.5, 0.75],
|
||||||
|
bad: [0.75, 1],
|
||||||
|
}}
|
||||||
|
value={total_parts / max_components}
|
||||||
|
maxValue={1}
|
||||||
|
>
|
||||||
|
{total_parts +
|
||||||
|
' / ' +
|
||||||
|
max_components +
|
||||||
|
' (' +
|
||||||
|
toFixed((total_parts / max_components) * 100, 1) +
|
||||||
|
'%)'}
|
||||||
|
</ProgressBar>
|
||||||
|
</LabeledList.Item>
|
||||||
|
<LabeledList.Item label="Complexity">
|
||||||
|
<ProgressBar
|
||||||
|
ranges={{
|
||||||
|
good: [0, 0.25],
|
||||||
|
average: [0.5, 0.75],
|
||||||
|
bad: [0.75, 1],
|
||||||
|
}}
|
||||||
|
value={total_complexity / max_complexity}
|
||||||
|
maxValue={1}
|
||||||
|
>
|
||||||
|
{total_complexity +
|
||||||
|
' / ' +
|
||||||
|
max_complexity +
|
||||||
|
' (' +
|
||||||
|
toFixed((total_complexity / max_complexity) * 100, 1) +
|
||||||
|
'%)'}
|
||||||
|
</ProgressBar>
|
||||||
|
</LabeledList.Item>
|
||||||
|
<LabeledList.Item
|
||||||
|
label="Cell Charge"
|
||||||
|
buttons={
|
||||||
|
<Button
|
||||||
|
icon="eject"
|
||||||
|
onClick={() => act('remove_cell')}
|
||||||
|
tooltip="Remove Cell"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{(battery_charge && (
|
||||||
|
<ProgressBar
|
||||||
|
ranges={{
|
||||||
|
bad: [0, 0.25],
|
||||||
|
average: [0.5, 0.75],
|
||||||
|
good: [0.75, 1],
|
||||||
|
}}
|
||||||
|
value={battery_charge / battery_max}
|
||||||
|
maxValue={1}
|
||||||
|
>
|
||||||
|
{battery_charge +
|
||||||
|
' / ' +
|
||||||
|
battery_max +
|
||||||
|
' (' +
|
||||||
|
toFixed((battery_charge / battery_max) * 100, 1) +
|
||||||
|
'%)'}
|
||||||
|
</ProgressBar>
|
||||||
|
)) || <Box color="bad">No cell detected.</Box>}
|
||||||
|
</LabeledList.Item>
|
||||||
|
<LabeledList.Item label="Net Energy">
|
||||||
|
{(net_power === 0 && '0 W/s') || (
|
||||||
|
<AnimatedNumber
|
||||||
|
value={net_power}
|
||||||
|
format={(val) => '-' + formatPower(Math.abs(val)) + '/s'}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</LabeledList.Item>
|
||||||
|
</LabeledList>
|
||||||
|
</Section>
|
||||||
|
);
|
||||||
|
};
|
||||||
70
tgui/packages/tgui/interfaces/ICAssembly/types.ts
Normal file
70
tgui/packages/tgui/interfaces/ICAssembly/types.ts
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
export type Data = {
|
||||||
|
total_parts: number;
|
||||||
|
max_components: number;
|
||||||
|
total_complexity: number;
|
||||||
|
max_complexity: number;
|
||||||
|
battery_charge: number;
|
||||||
|
battery_max: number;
|
||||||
|
net_power: number;
|
||||||
|
circuits: CircuitData[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CircuitData = {
|
||||||
|
name: string;
|
||||||
|
desc: string;
|
||||||
|
ref: string;
|
||||||
|
removable: boolean;
|
||||||
|
complexity: number;
|
||||||
|
power_draw_idle: number;
|
||||||
|
power_draw_per_use: number;
|
||||||
|
extended_desc: string;
|
||||||
|
inputs: PortData[];
|
||||||
|
outputs: PortData[];
|
||||||
|
activators: PortData[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PortData = {
|
||||||
|
type: string;
|
||||||
|
name: string;
|
||||||
|
data: string;
|
||||||
|
rawdata: string;
|
||||||
|
ref: string;
|
||||||
|
linked: LinkedPortData[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type LinkedPortData = {
|
||||||
|
ref: string;
|
||||||
|
name: string;
|
||||||
|
holder_ref: string;
|
||||||
|
holder_name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export enum PortTypes {
|
||||||
|
IC_FORMAT_ANY = '<ANY>',
|
||||||
|
IC_FORMAT_STRING = '<TEXT>',
|
||||||
|
IC_FORMAT_CHAR = '<CHAR>',
|
||||||
|
IC_FORMAT_COLOR = '<COLOR>',
|
||||||
|
IC_FORMAT_NUMBER = '<NUM>',
|
||||||
|
IC_FORMAT_DIR = '<DIR>',
|
||||||
|
IC_FORMAT_BOOLEAN = '<BOOL>',
|
||||||
|
IC_FORMAT_REF = '<REF>',
|
||||||
|
IC_FORMAT_LIST = '<LIST>',
|
||||||
|
IC_FORMAT_PULSE = '<PULSE>',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PortTypesToColor = {
|
||||||
|
[PortTypes.IC_FORMAT_ANY]: 'olive',
|
||||||
|
[PortTypes.IC_FORMAT_STRING]: 'green',
|
||||||
|
[PortTypes.IC_FORMAT_CHAR]: 'darkyellow',
|
||||||
|
[PortTypes.IC_FORMAT_COLOR]: 'pink',
|
||||||
|
[PortTypes.IC_FORMAT_NUMBER]: 'blue',
|
||||||
|
[PortTypes.IC_FORMAT_DIR]: 'darkred',
|
||||||
|
[PortTypes.IC_FORMAT_BOOLEAN]: 'red',
|
||||||
|
[PortTypes.IC_FORMAT_REF]: 'darkblue',
|
||||||
|
[PortTypes.IC_FORMAT_LIST]: 'darkgreen',
|
||||||
|
[PortTypes.IC_FORMAT_PULSE]: 'yellow',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ABSOLUTE_Y_OFFSET = -32;
|
||||||
|
export const MOUSE_BUTTON_LEFT = 0;
|
||||||
|
export const TIME_UNTIL_PORT_RELEASE_WORKS = 100;
|
||||||
95
tgui/packages/tgui/interfaces/common/Connections.tsx
Normal file
95
tgui/packages/tgui/interfaces/common/Connections.tsx
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import { classes } from 'common/react';
|
||||||
|
import { CSS_COLORS } from 'tgui/constants';
|
||||||
|
|
||||||
|
const SVG_CURVE_INTENSITY = 64;
|
||||||
|
|
||||||
|
enum ConnectionStyle {
|
||||||
|
CURVE = 'curve',
|
||||||
|
SUBWAY = 'subway',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Position = {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Connection = {
|
||||||
|
// X, Y starting point
|
||||||
|
from: Position;
|
||||||
|
// X, Y ending point
|
||||||
|
to: Position;
|
||||||
|
// Color of the line, defaults to blue
|
||||||
|
color?: string;
|
||||||
|
// Type of line - Curvy or Straight / angled, defaults to curvy
|
||||||
|
style?: ConnectionStyle;
|
||||||
|
// Optional: the ref of what element this connection is sourced
|
||||||
|
ref?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Connections = (props: {
|
||||||
|
connections: Connection[];
|
||||||
|
zLayer?: number;
|
||||||
|
lineWidth?: number;
|
||||||
|
}) => {
|
||||||
|
const { connections, zLayer = -1, lineWidth = '2px' } = props;
|
||||||
|
|
||||||
|
const isColorClass = (str) => {
|
||||||
|
if (typeof str === 'string') {
|
||||||
|
return CSS_COLORS.includes(str as any);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
pointerEvents: 'none',
|
||||||
|
zIndex: zLayer,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{connections.map((val, index) => {
|
||||||
|
const from = val.from;
|
||||||
|
const to = val.to;
|
||||||
|
if (!to || !from) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
val.color = val.color || 'blue';
|
||||||
|
val.style = val.style || ConnectionStyle.CURVE;
|
||||||
|
|
||||||
|
// Starting point
|
||||||
|
let path = `M ${from.x} ${from.y}`;
|
||||||
|
|
||||||
|
switch (val.style) {
|
||||||
|
case ConnectionStyle.CURVE: {
|
||||||
|
path += `C ${from.x + SVG_CURVE_INTENSITY}, ${from.y},`;
|
||||||
|
path += `${to.x - SVG_CURVE_INTENSITY}, ${to.y},`;
|
||||||
|
path += `${to.x}, ${to.y}`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ConnectionStyle.SUBWAY: {
|
||||||
|
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}`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<path
|
||||||
|
className={classes([
|
||||||
|
isColorClass(val.color) && `color-stroke-${val.color}`,
|
||||||
|
])}
|
||||||
|
key={index}
|
||||||
|
d={path}
|
||||||
|
fill="transparent"
|
||||||
|
stroke-width={lineWidth}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,7 +1,16 @@
|
|||||||
@use '../colors.scss';
|
@use '../colors.scss';
|
||||||
@use '../base.scss';
|
@use '../base.scss';
|
||||||
|
|
||||||
$bg-map: colors.$bg-map !default;
|
$fg-map: colors.$fg-map !default;
|
||||||
|
|
||||||
|
.CircuitInfo__Examined {
|
||||||
|
background-color: rgba(0, 0, 0, 1);
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: 5px;
|
||||||
|
|
||||||
|
user-select: none;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
.ObjectComponent__Titlebar {
|
.ObjectComponent__Titlebar {
|
||||||
border-top-left-radius: 12px;
|
border-top-left-radius: 12px;
|
||||||
@@ -21,6 +30,20 @@ $bg-map: colors.$bg-map !default;
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ObjectComponent__Greyed_Content {
|
||||||
|
white-space: nowrap;
|
||||||
|
background-color: rgba(19, 19, 19, 0.5);
|
||||||
|
|
||||||
|
-ms-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ObjectComponent__Port {
|
||||||
|
position: relative;
|
||||||
|
width: 13px;
|
||||||
|
height: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
.ObjectComponent__PortPos {
|
.ObjectComponent__PortPos {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
@@ -29,8 +52,21 @@ $bg-map: colors.$bg-map !default;
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@each $color-name, $color-value in $bg-map {
|
@each $color-name, $color-value in $fg-map {
|
||||||
.color-stroke-#{$color-name} {
|
.color-stroke-#{$color-name} {
|
||||||
stroke: $color-value !important;
|
stroke: $color-value !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.color-fill-#{$color-name} {
|
||||||
|
fill: $color-value !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$border-color: #88bfff !default;
|
||||||
|
$border-radius: base.$border-radius !default;
|
||||||
|
|
||||||
|
.IntegratedCircuit__BlueBorder {
|
||||||
|
border: base.em(1px) solid $border-color;
|
||||||
|
border: base.em(1px) solid rgba($border-color, 0.75);
|
||||||
|
border-radius: $border-radius;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2037,6 +2037,7 @@
|
|||||||
#include "code\modules\asset_cache\asset_list.dm"
|
#include "code\modules\asset_cache\asset_list.dm"
|
||||||
#include "code\modules\asset_cache\asset_list_items.dm"
|
#include "code\modules\asset_cache\asset_list_items.dm"
|
||||||
#include "code\modules\asset_cache\assets\chat.dm"
|
#include "code\modules\asset_cache\assets\chat.dm"
|
||||||
|
#include "code\modules\asset_cache\assets\circuits.dm"
|
||||||
#include "code\modules\asset_cache\assets\fontawesome.dm"
|
#include "code\modules\asset_cache\assets\fontawesome.dm"
|
||||||
#include "code\modules\asset_cache\assets\icon_ref_map.dm"
|
#include "code\modules\asset_cache\assets\icon_ref_map.dm"
|
||||||
#include "code\modules\asset_cache\assets\jquery.dm"
|
#include "code\modules\asset_cache\assets\jquery.dm"
|
||||||
|
|||||||
Reference in New Issue
Block a user