diff --git a/public/assets/signs/w5.svg b/public/assets/signs/w5.svg
new file mode 100644
index 0000000..8955e2e
--- /dev/null
+++ b/public/assets/signs/w5.svg
@@ -0,0 +1,7 @@
+
+
diff --git a/src/components/map/Map.css b/src/components/map/Map.css
index a62a4d9..82a7f8b 100644
--- a/src/components/map/Map.css
+++ b/src/components/map/Map.css
@@ -63,4 +63,8 @@
.map svg g.signalbox text {
fill: #eee;
font-size: 4px;
-}
\ No newline at end of file
+}
+
+.map svg g.sign path {
+ fill: #eee;
+}
diff --git a/src/components/map/ObjectsRenderQueue.js b/src/components/map/ObjectsRenderQueue.js
index f1d9134..560eb17 100644
--- a/src/components/map/ObjectsRenderQueue.js
+++ b/src/components/map/ObjectsRenderQueue.js
@@ -5,6 +5,7 @@ import TrackObjectRenderer from './object-renderers/TrackObjectRenderer';
import SignalRenderer from './object-renderers/SignalRenderer';
import RouteRenderer from './object-renderers/RouteRenderer';
import SignalBoxRenderer from './object-renderers/SignalBoxRenderer';
+import SignRenderer from './object-renderers/SignRenderer';
const ObjectRendererQueue = [
{
@@ -26,6 +27,13 @@ const ObjectRendererQueue = [
'renderer': SwitchNameRenderer,
'cond': (layers) => layers['switches-names']
},
+ {
+ 'name': 'signs',
+ 'category': 'track-objects',
+ 'type': 'Sign',
+ 'renderer': SignRenderer,
+ 'cond': (layers) => layers['signs']
+ },
{
'name': 'signals',
'category': 'track-objects',
diff --git a/src/components/map/object-renderers/SignRenderer.js b/src/components/map/object-renderers/SignRenderer.js
new file mode 100644
index 0000000..d6df35e
--- /dev/null
+++ b/src/components/map/object-renderers/SignRenderer.js
@@ -0,0 +1,25 @@
+import { ReactSVG } from 'react-svg'
+
+export default function SignRenderer(props) {
+ const { object } = props;
+ if(!object.def) return null;
+
+ const [x, y] = object.pos.toSVGCoords();
+
+ const offX = -1.89 + (object.def.offsetX || 0);
+ const offY = -1.89 + (object.def.offsetY || 0);
+
+ return (
+
+ {
+ svg.setAttribute('width', '1mm');
+ svg.setAttribute('height', '1mm');
+ }}
+ />
+
+ );
+}
diff --git a/src/components/scenery-info-dialog/SceneryInfoView.js b/src/components/scenery-info-dialog/SceneryInfoView.js
index 8ee4f3d..87963d2 100644
--- a/src/components/scenery-info-dialog/SceneryInfoView.js
+++ b/src/components/scenery-info-dialog/SceneryInfoView.js
@@ -22,6 +22,8 @@ export default function SceneryInfoView({ scenery }) {
}
function SceneryInfoViewItem({ label, value, isLink = false }) {
+ if(!value) value = 'No data';
+
return (
| {label} |
diff --git a/src/helpers/constants.js b/src/helpers/constants.js
index 0a85c17..c170048 100644
--- a/src/helpers/constants.js
+++ b/src/helpers/constants.js
@@ -23,6 +23,7 @@ const Constants = {
switchNoModel: true,
signalBoxUndefinedPrefabName: true,
invalidSceneryInfoVersion: true,
+ signUndefinedPrefabName: true,
},
errors: {
invalidSceneryInfo: true
@@ -53,6 +54,11 @@ const Constants = {
name: 'Signals',
default: true,
},
+ {
+ id: 'signs',
+ name: 'Signs',
+ default: true,
+ },
{
id: 'signalboxes',
name: 'Signal boxes',
diff --git a/src/model/defined-signs.js b/src/model/defined-signs.js
new file mode 100644
index 0000000..71184e3
--- /dev/null
+++ b/src/model/defined-signs.js
@@ -0,0 +1,11 @@
+// remember to place sign_w1X before sign_w1 etc.
+
+const DefinedSigns = {
+ "sign_w5": {
+ "name": "W5",
+ "icon": "w5.svg",
+ "offsetY": -1.4364
+ }
+};
+
+export default DefinedSigns;
\ No newline at end of file
diff --git a/src/model/scenery.js b/src/model/scenery.js
index e498255..7242352 100644
--- a/src/model/scenery.js
+++ b/src/model/scenery.js
@@ -9,6 +9,7 @@ import SceneryParserLog from './scenery-parser-log';
import SignalBox from './signalbox';
import Constants from '../helpers/constants';
import SceneryInfo from './scenery-info';
+import Sign from './sign';
/*
TODO: move parsers to another static class
@@ -164,7 +165,9 @@ export default class Scenery
}
static _parseTrackObject(text) {
- if(Signal.isSignal(text)) {
+ if(Sign.isSign(text)) {
+ return Sign.fromText(text);
+ } else if(Signal.isSignal(text)) {
return Signal.fromText(text);
} else {
return TrackObject.fromText(text);
diff --git a/src/model/sign.js b/src/model/sign.js
new file mode 100644
index 0000000..9eb33d5
--- /dev/null
+++ b/src/model/sign.js
@@ -0,0 +1,53 @@
+import DefinedSigns from "./defined-signs";
+import SceneryParserLog from "./scenery-parser-log";
+import TrackObject from "./track-object";
+import Vector3 from "./vector3";
+
+export default class Sign extends TrackObject {
+ type = "Sign";
+ def;
+
+ constructor(id, prefab_name, pos, rot, track_id, name) {
+ super(id, prefab_name, pos, rot, track_id, name);
+
+ this.def = Sign.getDef(name, prefab_name);
+ }
+
+ /*
+ Check if a TrackObject text is a Sign.
+ @param {string} text - The text representation of the TrackObject.
+ @returns {boolean} - Returns true if the text represents a Sign, false otherwise.
+ @static
+ @memberof Sign
+ */
+ static isSign(text) {
+ const values = text.split(";");
+
+ const prefabInfo = values[2]?.trim() || "";
+ return prefabInfo.startsWith("sign_");
+ }
+
+ static fromText(text) {
+ const values = text.split(";");
+ const sign = new Sign(
+ values[1], // id
+ values[2], // prefab_name
+ Vector3.fromValuesArray(values, 3), // pos
+ Vector3.fromValuesArray(values, 6), // rot
+ values[9], // track_id
+ values[11], // name
+ );
+
+ return sign;
+ }
+
+ static getDef(name, prefabName) {
+ const key = Object.keys(DefinedSigns).find(k => prefabName.startsWith(k));
+ if (!key) {
+ SceneryParserLog.warn('signUndefinedPrefabName', `Sign ${name} has an undefined prefab name ${prefabName}`);
+ return null;
+ }
+
+ return DefinedSigns[key];
+ }
+}
\ No newline at end of file