diff --git a/package-lock.json b/package-lock.json
index 11cdc21..ccb235f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -15,7 +15,8 @@
"@testing-library/user-event": "^13.5.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
- "react-scripts": "^5.0.1"
+ "react-scripts": "^5.0.1",
+ "react-svg": "^16.3.0"
}
},
"node_modules/-": {
@@ -3441,6 +3442,17 @@
"url": "https://github.com/sponsors/gregberge"
}
},
+ "node_modules/@tanem/svg-injector": {
+ "version": "10.1.68",
+ "resolved": "https://registry.npmjs.org/@tanem/svg-injector/-/svg-injector-10.1.68.tgz",
+ "integrity": "sha512-UkJajeR44u73ujtr5GVSbIlELDWD/mzjqWe54YMK61ljKxFcJoPd9RBSaO7xj02ISCWUqJW99GjrS+sVF0UnrA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.23.2",
+ "content-type": "^1.0.5",
+ "tslib": "^2.6.2"
+ }
+ },
"node_modules/@testing-library/dom": {
"version": "10.4.0",
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz",
@@ -3809,6 +3821,12 @@
"integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==",
"license": "MIT"
},
+ "node_modules/@types/prop-types": {
+ "version": "15.7.14",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz",
+ "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==",
+ "license": "MIT"
+ },
"node_modules/@types/q": {
"version": "1.5.8",
"resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.8.tgz",
@@ -13996,6 +14014,22 @@
}
}
},
+ "node_modules/react-svg": {
+ "version": "16.3.0",
+ "resolved": "https://registry.npmjs.org/react-svg/-/react-svg-16.3.0.tgz",
+ "integrity": "sha512-MvoQbITgkmpPJYwDTNdiUyoncJFfoa0D86WzoZuMQ9c/ORJURPR6rPMnXDsLOWDCAyXuV9nKZhQhGyP0HZ0MVQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.26.0",
+ "@tanem/svg-injector": "^10.1.68",
+ "@types/prop-types": "^15.7.14",
+ "prop-types": "^15.8.1"
+ },
+ "peerDependencies": {
+ "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
diff --git a/package.json b/package.json
index 57d923d..6e18768 100644
--- a/package.json
+++ b/package.json
@@ -10,7 +10,8 @@
"@testing-library/user-event": "^13.5.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
- "react-scripts": "^5.0.1"
+ "react-scripts": "^5.0.1",
+ "react-svg": "^16.3.0"
},
"scripts": {
"start": "react-scripts start",
diff --git a/public/assets/signal.svg b/public/assets/signal.svg
new file mode 100644
index 0000000..8d3392a
--- /dev/null
+++ b/public/assets/signal.svg
@@ -0,0 +1,11 @@
+
+
diff --git a/src/components/map/Map.css b/src/components/map/Map.css
index 5d48734..64851bf 100644
--- a/src/components/map/Map.css
+++ b/src/components/map/Map.css
@@ -16,6 +16,7 @@
.map svg text.switch-name {
fill: #eee;
+ fill: #bbf;
}
.map svg text.isolation-id {
@@ -33,4 +34,8 @@
.map svg g.track-object:hover text {
display: block;
+}
+
+.map svg g.signal g {
+ stroke: #eee;
}
\ No newline at end of file
diff --git a/src/components/map/ObjectsRenderQueue.js b/src/components/map/ObjectsRenderQueue.js
index b4282b0..7307b8f 100644
--- a/src/components/map/ObjectsRenderQueue.js
+++ b/src/components/map/ObjectsRenderQueue.js
@@ -2,6 +2,7 @@ import TrackRenderer from './object-renderers/TrackRenderer';
import SwitchNameRenderer from './object-renderers/SwitchNameRenderer';
import IsolationIdRenderer from './object-renderers/IsolationIdRenderer';
import TrackObjectRenderer from './object-renderers/TrackObjectRenderer';
+import SignalRenderer from './object-renderers/SignalRenderer';
const ObjectRendererQueue = [
{
@@ -28,6 +29,13 @@ const ObjectRendererQueue = [
'category': 'track-objects',
'renderer': TrackObjectRenderer,
'cond': (settings) => settings.layers['track-objects']
+ },
+ {
+ 'name': 'signals',
+ 'category': 'track-objects',
+ 'type': 'Signal',
+ 'renderer': SignalRenderer,
+ 'cond': (settings) => settings.layers['signals']
}
]
diff --git a/src/components/map/object-renderers/SignalRenderer.js b/src/components/map/object-renderers/SignalRenderer.js
new file mode 100644
index 0000000..50738d2
--- /dev/null
+++ b/src/components/map/object-renderers/SignalRenderer.js
@@ -0,0 +1,27 @@
+import { ReactSVG } from 'react-svg'
+
+export default function SignalRenderer(props) {
+ const { object } = props;
+
+ const x = object.x;
+ const z = -object.z;
+
+ return (
+
+
+ {
+ svg.setAttribute('width', '1mm');
+ svg.setAttribute('height', '1mm');
+ }}
+ />
+
+
+ {object.getPrintableSignalName()}
+
+
+ );
+}
diff --git a/src/helpers/constants.js b/src/helpers/constants.js
index 2e56cda..4bad6dc 100644
--- a/src/helpers/constants.js
+++ b/src/helpers/constants.js
@@ -17,12 +17,17 @@ const Constants = {
{
id: 'isolations-ids',
name: 'Isolations IDs',
- default: true,
+ default: false,
},
{
id: 'track-objects',
name: 'Track objects',
default: false,
+ },
+ {
+ id: 'signals',
+ name: 'Signals',
+ default: true,
}
]
};
diff --git a/src/model/scenery.js b/src/model/scenery.js
index 793a4df..a1b93cf 100644
--- a/src/model/scenery.js
+++ b/src/model/scenery.js
@@ -2,6 +2,7 @@ import Switch from './switch';
import StandardTrack from './standard-track';
import BezierTrack from './bezier-track';
import TrackObject from './track-object';
+import Signal from './signal';
export default class Scenery
{
@@ -94,6 +95,10 @@ export default class Scenery
switch(prefabName) {
default:
return TrackObject.fromText(text);
+ if(Signal.isSignal(text)) {
+ return Signal.fromText(text);
+ } else {
+ return TrackObject.fromText(text);
}
}
diff --git a/src/model/signal.js b/src/model/signal.js
new file mode 100644
index 0000000..3bbdd6b
--- /dev/null
+++ b/src/model/signal.js
@@ -0,0 +1,64 @@
+import SpawnInfo from "./spawn-info";
+import TrackObject from "./track-object";
+
+export default class Signal extends TrackObject {
+ is_spawn;
+ spawn_info;
+ signal_name;
+ type = "Signal";
+
+ constructor(id, prefab_name, x, y, z, rot, track_id, name, is_spawn, spawn_info, signal_name) {
+ super(id, prefab_name, x, y, z, rot, track_id, name);
+
+ Object.assign(this, {
+ is_spawn,
+ spawn_info: is_spawn ? spawn_info : null,
+ signal_name
+ });
+ }
+
+ getPrintableSignalName() {
+ // remove everything after the first '[' or '<' character (inclusive)
+ const match = this.signal_name.match(/^[^[<]*/);
+ if (match) {
+ return match[0].trim();
+ }
+ }
+
+ /*
+ Check if a TrackObject text is a Signal.
+ @param {string} text - The text representation of the TrackObject.
+ @returns {boolean} - Returns true if the text represents a Signal, false otherwise.
+ @static
+ @memberof Signal
+ */
+ static isSignal(text) {
+ const values = text.split(";");
+ if(values.length < 32) return false;
+
+ const prefabInfo = values[2].split(",");
+ if(prefabInfo.length < 2) return false;
+
+ return true;
+ }
+
+ static fromText(text) {
+ const values = text.split(";");
+ const signal = new Signal(
+ values[1], // id
+ values[2], // prefab_name
+ parseFloat(values[3]), // x
+ parseFloat(values[4]), // y
+ parseFloat(values[5]), // z
+ parseFloat(values[7]), // rot
+ values[9], // track_id
+ values[11], // name,
+ values[12] === "Spawn Signal", // is_spawn
+ SpawnInfo.fromText(values[15]), // spawn_info
+ values[29]
+ );
+
+ return signal;
+ }
+
+}
\ No newline at end of file
diff --git a/src/model/spawn-info.js b/src/model/spawn-info.js
new file mode 100644
index 0000000..0ca2696
--- /dev/null
+++ b/src/model/spawn-info.js
@@ -0,0 +1,28 @@
+export default class SpawnInfo {
+ max_length;
+ allow_electric;
+ multiple_units_only;
+ light_only;
+ name;
+
+ constructor(max_length, allow_electric, multiple_units_only, light_only, name) {
+ Object.assign(this, {
+ max_length,
+ allow_electric,
+ multiple_units_only,
+ light_only,
+ name
+ });
+ }
+
+ static fromText(text) {
+ const values = text.split(",");
+ return new SpawnInfo(
+ parseFloat(values[1]), // max_length
+ values[2] === "1", // allow_electric
+ values[3] === "1", // multiple_units_only
+ values[4] === "1", // light_only
+ values[5] // name
+ );
+ }
+}
\ No newline at end of file