Fixed switch UI updating, websocket bugs, and context switching.
This commit is contained in:
parent
c16627e7d3
commit
bd6c87149a
|
@ -12,6 +12,7 @@
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"pinia": "^2.0.11",
|
"pinia": "^2.0.11",
|
||||||
"quasar": "^2.6.0",
|
"quasar": "^2.6.0",
|
||||||
|
"randomcolor": "^0.6.2",
|
||||||
"vue": "^3.0.0",
|
"vue": "^3.0.0",
|
||||||
"vue-router": "^4.0.0"
|
"vue-router": "^4.0.0"
|
||||||
},
|
},
|
||||||
|
@ -3666,6 +3667,11 @@
|
||||||
"safe-buffer": "^5.1.0"
|
"safe-buffer": "^5.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/randomcolor": {
|
||||||
|
"version": "0.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/randomcolor/-/randomcolor-0.6.2.tgz",
|
||||||
|
"integrity": "sha512-Mn6TbyYpFgwFuQ8KJKqf3bqqY9O1y37/0jgSK/61PUxV4QfIMv0+K2ioq8DfOjkBslcjwSzRfIDEXfzA9aCx7A=="
|
||||||
|
},
|
||||||
"node_modules/range-parser": {
|
"node_modules/range-parser": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||||
|
@ -7276,6 +7282,11 @@
|
||||||
"safe-buffer": "^5.1.0"
|
"safe-buffer": "^5.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"randomcolor": {
|
||||||
|
"version": "0.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/randomcolor/-/randomcolor-0.6.2.tgz",
|
||||||
|
"integrity": "sha512-Mn6TbyYpFgwFuQ8KJKqf3bqqY9O1y37/0jgSK/61PUxV4QfIMv0+K2ioq8DfOjkBslcjwSzRfIDEXfzA9aCx7A=="
|
||||||
|
},
|
||||||
"range-parser": {
|
"range-parser": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||||
|
|
|
@ -11,24 +11,25 @@
|
||||||
"test": "echo \"No test specified\" && exit 0"
|
"test": "echo \"No test specified\" && exit 0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@quasar/extras": "^1.0.0",
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"pinia": "^2.0.11",
|
"pinia": "^2.0.11",
|
||||||
"@quasar/extras": "^1.0.0",
|
|
||||||
"quasar": "^2.6.0",
|
"quasar": "^2.6.0",
|
||||||
|
"randomcolor": "^0.6.2",
|
||||||
"vue": "^3.0.0",
|
"vue": "^3.0.0",
|
||||||
"vue-router": "^4.0.0"
|
"vue-router": "^4.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint": "^8.10.0",
|
|
||||||
"eslint-plugin-vue": "^8.5.0",
|
|
||||||
"eslint-config-prettier": "^8.1.0",
|
|
||||||
"prettier": "^2.5.1",
|
|
||||||
"@quasar/app-vite": "^1.0.0",
|
"@quasar/app-vite": "^1.0.0",
|
||||||
"autoprefixer": "^10.4.2"
|
"autoprefixer": "^10.4.2",
|
||||||
|
"eslint": "^8.10.0",
|
||||||
|
"eslint-config-prettier": "^8.1.0",
|
||||||
|
"eslint-plugin-vue": "^8.5.0",
|
||||||
|
"prettier": "^2.5.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18 || ^16 || ^14.19",
|
"node": "^18 || ^16 || ^14.19",
|
||||||
"npm": ">= 6.13.4",
|
"npm": ">= 6.13.4",
|
||||||
"yarn": ">= 1.21.1"
|
"yarn": ">= 1.21.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,8 +31,7 @@ module.exports = configure(function (ctx) {
|
||||||
// --> boot files are part of "main.js"
|
// --> boot files are part of "main.js"
|
||||||
// https://v2.quasar.dev/quasar-cli/boot-files
|
// https://v2.quasar.dev/quasar-cli/boot-files
|
||||||
boot: [
|
boot: [
|
||||||
|
'axios'
|
||||||
'axios',
|
|
||||||
],
|
],
|
||||||
|
|
||||||
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css
|
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { defineComponent } from 'vue'
|
import { defineComponent } from "vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'App'
|
name: 'App'
|
||||||
|
|
|
@ -2,6 +2,9 @@ import axios from "axios";
|
||||||
import {API_URL} from "./constants";
|
import {API_URL} from "./constants";
|
||||||
import { refreshSegments } from "src/api/segments";
|
import { refreshSegments } from "src/api/segments";
|
||||||
import { refreshComponents } from "src/api/components";
|
import { refreshComponents } from "src/api/components";
|
||||||
|
import { closeWebsocketConnection, establishWebsocketConnection } from "src/api/websocket";
|
||||||
|
import { refreshLinkTokens } from "src/api/linkTokens";
|
||||||
|
import { refreshSettings } from "src/api/settings";
|
||||||
|
|
||||||
export class RailSystem {
|
export class RailSystem {
|
||||||
constructor(data) {
|
constructor(data) {
|
||||||
|
@ -15,6 +18,7 @@ export class RailSystem {
|
||||||
|
|
||||||
this.websocket = null;
|
this.websocket = null;
|
||||||
this.selectedComponents = [];
|
this.selectedComponents = [];
|
||||||
|
this.loaded = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,7 +61,9 @@ export function removeRailSystem(rsStore, id) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
axios.delete(`${API_URL}/rs/${id}`)
|
axios.delete(`${API_URL}/rs/${id}`)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
rsStore.selectedRailSystem = null;
|
if (rsStore.selectedRailSystem !== null && rsStore.selectedRailSystem.id === id) {
|
||||||
|
rsStore.selectRailSystem(null);
|
||||||
|
}
|
||||||
refreshRailSystems(rsStore)
|
refreshRailSystems(rsStore)
|
||||||
.then(resolve)
|
.then(resolve)
|
||||||
.catch(reject);
|
.catch(reject);
|
||||||
|
@ -65,3 +71,41 @@ export function removeRailSystem(rsStore, id) {
|
||||||
.catch(reject);
|
.catch(reject);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads all data for a rail system. This is generally done when a rail system
|
||||||
|
* is selected.
|
||||||
|
* @param {RailSystem} rs
|
||||||
|
*/
|
||||||
|
export async function loadData(rs) {
|
||||||
|
console.log("Loading rail system " + rs.id);
|
||||||
|
await closeWebsocketConnection(rs);
|
||||||
|
console.log("Closed websocket connection to " + rs.id);
|
||||||
|
const updatePromises = [];
|
||||||
|
updatePromises.push(refreshSegments(rs));
|
||||||
|
updatePromises.push(refreshComponents(rs));
|
||||||
|
updatePromises.push(refreshLinkTokens(rs));
|
||||||
|
updatePromises.push(refreshSettings(rs));
|
||||||
|
await Promise.all(updatePromises);
|
||||||
|
await establishWebsocketConnection(rs);
|
||||||
|
console.log("Finished loading rail system " + rs.id);
|
||||||
|
rs.loaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unloads all data for a rail system. This is generally done when the user
|
||||||
|
* navigates away from a rail system's page.
|
||||||
|
* @param {RailSystem} rs
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
export async function unloadData(rs) {
|
||||||
|
console.log("Unloading data for rail system " + rs.id);
|
||||||
|
await closeWebsocketConnection(rs);
|
||||||
|
rs.segments = [];
|
||||||
|
rs.components = [];
|
||||||
|
rs.linkTokens = [];
|
||||||
|
rs.selectedComponents = [];
|
||||||
|
rs.settings = null;
|
||||||
|
rs.loaded = false;
|
||||||
|
console.log("Finished unloading data for rail system " + rs.id);
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
import { WS_URL } from "./constants";
|
import { WS_URL } from "./constants";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The time to wait before attempting to reconnect if a websocket connection is
|
||||||
|
* abruptly closed.
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
const WS_RECONNECT_TIMEOUT = 3000;
|
const WS_RECONNECT_TIMEOUT = 3000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -8,6 +13,9 @@ const WS_RECONNECT_TIMEOUT = 3000;
|
||||||
* @return {Promise} A promise that resolves when a connection is established.
|
* @return {Promise} A promise that resolves when a connection is established.
|
||||||
*/
|
*/
|
||||||
export function establishWebsocketConnection(rs) {
|
export function establishWebsocketConnection(rs) {
|
||||||
|
if (rs.websocket) {
|
||||||
|
console.log('rail system ' + rs.id + ' already has websocket')
|
||||||
|
}
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
rs.websocket = new WebSocket(`${WS_URL}/${rs.id}`);
|
rs.websocket = new WebSocket(`${WS_URL}/${rs.id}`);
|
||||||
rs.websocket.onopen = resolve;
|
rs.websocket.onopen = resolve;
|
||||||
|
@ -29,7 +37,7 @@ export function establishWebsocketConnection(rs) {
|
||||||
const id = data.cId;
|
const id = data.cId;
|
||||||
const idx = rs.components.findIndex(c => c.id === id);
|
const idx = rs.components.findIndex(c => c.id === id);
|
||||||
if (idx > -1) {
|
if (idx > -1) {
|
||||||
rs.components[idx] = data.data;
|
Object.assign(rs.components[idx], data.data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -46,12 +54,15 @@ export function establishWebsocketConnection(rs) {
|
||||||
*/
|
*/
|
||||||
export function closeWebsocketConnection(rs) {
|
export function closeWebsocketConnection(rs) {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
if (rs.websocket) {
|
if (rs.websocket && rs.websocket.readyState !== WebSocket.CLOSED) {
|
||||||
rs.websocket.onclose = resolve;
|
rs.websocket.onclose = () => {
|
||||||
|
rs.websocket = null;
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
rs.websocket.close();
|
rs.websocket.close();
|
||||||
} else {
|
} else {
|
||||||
|
rs.websocket = null;
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
<template>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 q-pa-md">
|
||||||
|
<div class="text-h4">{{title}}</div>
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "IndexPageSection",
|
||||||
|
props: {
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -176,6 +176,9 @@ export default {
|
||||||
},
|
},
|
||||||
setActiveSwitchConfig(sw, configId) {
|
setActiveSwitchConfig(sw, configId) {
|
||||||
updateSwitchConfiguration(this.railSystem, sw, configId);
|
updateSwitchConfiguration(this.railSystem, sw, configId);
|
||||||
|
},
|
||||||
|
isConfigActive(sw, config) {
|
||||||
|
return sw.activeConfiguration !== null && sw.activeConfiguration.id === config.id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
clickable
|
clickable
|
||||||
v-ripple
|
v-ripple
|
||||||
:to="'/'"
|
:to="'/'"
|
||||||
@click="rsStore.selectedRailSystem = null"
|
@click="rsStore.selectRailSystem(null)"
|
||||||
>
|
>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label>Home</q-item-label>
|
<q-item-label>Home</q-item-label>
|
||||||
|
@ -40,7 +40,7 @@
|
||||||
clickable
|
clickable
|
||||||
v-ripple
|
v-ripple
|
||||||
:to="'/about'"
|
:to="'/about'"
|
||||||
@click="rsStore.selectedRailSystem = null"
|
@click="rsStore.selectRailSystem(null)"
|
||||||
>
|
>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label>About</q-item-label>
|
<q-item-label>About</q-item-label>
|
||||||
|
|
|
@ -7,8 +7,20 @@
|
||||||
<p>
|
<p>
|
||||||
This application was developed by <a href="https://andrewlalis.github.io">Andrew Lalis</a>
|
This application was developed by <a href="https://andrewlalis.github.io">Andrew Lalis</a>
|
||||||
after several years of tinkering with various ways to automate rail
|
after several years of tinkering with various ways to automate rail
|
||||||
networks in Minecraft.
|
networks in Minecraft. However, Rail Signal was designed from the
|
||||||
|
ground up to be free from the constraints of any particular system.
|
||||||
|
<em>Theoretically</em>, you could use Rail Signal to manage a real-world
|
||||||
|
rail network, although I wouldn't recommend it, due to the complex and
|
||||||
|
secure nature of real-world networks.
|
||||||
</p>
|
</p>
|
||||||
|
<p>
|
||||||
|
Of course, Rail Signal was originally designed for Minecraft, so we've
|
||||||
|
started by adding Rail Signal compatible drivers for Computercraft
|
||||||
|
and Immersive Railroading by default. If you have a different system
|
||||||
|
and would like to integrate it with Rail Signal, please <a href="https://github.com/andrewlalis/RailSignalAPI/issues" target="_blank">create a new issue</a>
|
||||||
|
on the GitHub repository.
|
||||||
|
</p>
|
||||||
|
<div class="text-h4"><q-icon :name="fasMicrochip"/> Technologies</div>
|
||||||
<p>
|
<p>
|
||||||
This web app was built using <a href="https://quasar.dev">Quasar</a>
|
This web app was built using <a href="https://quasar.dev">Quasar</a>
|
||||||
and <a href="https://vuejs.org/">VueJS</a>. The API that powers this
|
and <a href="https://vuejs.org/">VueJS</a>. The API that powers this
|
||||||
|
@ -16,6 +28,7 @@
|
||||||
and Java 17. For more technical information, please visit Rail Signal's
|
and Java 17. For more technical information, please visit Rail Signal's
|
||||||
<a href="https://github.com/andrewlalis/RailSignalAPI">GitHub repository</a>.
|
<a href="https://github.com/andrewlalis/RailSignalAPI">GitHub repository</a>.
|
||||||
</p>
|
</p>
|
||||||
|
<div class="text-h4"><q-icon :name="fasHeart"/> Support</div>
|
||||||
<p>
|
<p>
|
||||||
If you're enjoying this app, please consider making a <a href="https://paypal.me/andrewlalis" target="_blank">donation to
|
If you're enjoying this app, please consider making a <a href="https://paypal.me/andrewlalis" target="_blank">donation to
|
||||||
Andrew's work on paypal</a>.
|
Andrew's work on paypal</a>.
|
||||||
|
@ -26,8 +39,13 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import {fasHeart, fasMicrochip} from "@quasar/extras/fontawesome-v6";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "AboutPage"
|
name: "AboutPage",
|
||||||
|
setup() {
|
||||||
|
return {fasHeart, fasMicrochip};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -12,110 +12,117 @@
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<index-page-section title="Introduction to Rail Networks">
|
||||||
<div class="col-md-6 q-pa-md">
|
<q-img src="~assets/img/guide/layout.png"/>
|
||||||
<div class="text-h4">Introduction to Rail Networks</div>
|
<p>
|
||||||
<q-img src="~assets/img/guide/layout.png"/>
|
The above diagram illustrates all of the basic concepts you need to
|
||||||
<p>
|
know in order to build and manage your rail networks.
|
||||||
The above diagram illustrates all of the basic concepts you need to
|
</p>
|
||||||
know in order to build and manage your rail networks.
|
<p>
|
||||||
</p>
|
Each rail system can be conceptually split up into lots of small
|
||||||
<p>
|
<strong>segments</strong>, each of which represents a single part of
|
||||||
Each rail system can be conceptually split up into lots of small
|
the network that a single train should go through at once. For
|
||||||
<strong>segments</strong>, each of which represents a single part of
|
example, in our diagram, each shaded area is a segment. We only want
|
||||||
the network that a single train should go through at once. For
|
one train to go through the junction at once, or there might be a
|
||||||
example, in our diagram, each shaded area is a segment. We only want
|
crash!
|
||||||
one train to go through the junction at once, or there might be a
|
</p>
|
||||||
crash!
|
<p>
|
||||||
</p>
|
At the places where segments meet, we see a <strong style="color: #963ae0">segment boundary</strong>
|
||||||
<p>
|
which is also denoted with a red dotted line for convenience. This is
|
||||||
At the places where segments meet, we see a <strong style="color: #963ae0">segment boundary</strong>
|
a physical point on a track where trains travel from one segment to
|
||||||
which is also denoted with a red dotted line for convenience. This is
|
another. What's special about segment boundaries is that they're where
|
||||||
a physical point on a track where trains travel from one segment to
|
we can used devices to track trains moving in and out of segments. To
|
||||||
another. What's special about segment boundaries is that they're where
|
put it simply, imagine there's a little computer next to each segment
|
||||||
we can used devices to track trains moving in and out of segments. To
|
boundary point that sends a message saying, "Hey! A train just passed!"
|
||||||
put it simply, imagine there's a little computer next to each segment
|
every time that it detects a train going over it.
|
||||||
boundary point that sends a message saying, "Hey! A train just passed!"
|
</p>
|
||||||
every time that it detects a train going over it.
|
<p>
|
||||||
</p>
|
Now that we've covered segments and segment boundaries, we can now
|
||||||
<p>
|
display a segment's status using a <strong>signal</strong>. A signal
|
||||||
Now that we've covered segments and segment boundaries, we can now
|
is a device that is linked to a segment, and whenever the segment's
|
||||||
display a segment's status using a <strong>signal</strong>. A signal
|
status updates (<em>when a train enters or leaves it</em>), the signal
|
||||||
is a device that is linked to a segment, and whenever the segment's
|
will be updated as well. Usually, signals are placed near the segment
|
||||||
status updates (<em>when a train enters or leaves it</em>), the signal
|
boundary, so that approaching trains know whether they're safe to
|
||||||
will be updated as well. Usually, signals are placed near the segment
|
continue, but with Rail Signal, you can place a signal anywhere, and
|
||||||
boundary, so that approaching trains know whether they're safe to
|
connect it to any segment.
|
||||||
continue, but with Rail Signal, you can place a signal anywhere, and
|
</p>
|
||||||
connect it to any segment.
|
<p>
|
||||||
</p>
|
Finally, unless you're just making a boring single-line loop, you'll
|
||||||
<p>
|
most likely have some <strong style="color: #f5bc42">switches</strong>
|
||||||
Finally, unless you're just making a boring single-line loop, you'll
|
in your network. Switches are just sections of rail that allow trains
|
||||||
most likely have some <strong style="color: #f5bc42">switches</strong>
|
to choose between two different paths to take. Rail Signal gives you
|
||||||
in your network. Switches are just sections of rail that allow trains
|
the ability to manage these automatically, so you can use this web
|
||||||
to choose between two different paths to take. Rail Signal gives you
|
interface to configure switches instead of doing it manually.
|
||||||
the ability to manage these automatically, so you can use this web
|
</p>
|
||||||
interface to configure switches instead of doing it manually.
|
</index-page-section>
|
||||||
</p>
|
<index-page-section title="Paths and Path Nodes">
|
||||||
</div>
|
<p>
|
||||||
<div class="col-md-6 q-pa-md">
|
We mentioned segment boundaries and switches earlier, as simple
|
||||||
<div class="text-h4">Paths and Path Nodes</div>
|
components that you can add to your network in order to link it to the
|
||||||
<p>
|
internet. There's more to it than that, however.
|
||||||
We mentioned segment boundaries and switches earlier, as simple
|
</p>
|
||||||
components that you can add to your network in order to link it to the
|
<p>
|
||||||
internet. There's more to it than that, however.
|
Behind the scenes, your Rail Signal models your network as a set of
|
||||||
</p>
|
<strong>path nodes</strong>, where each node can be connected to any
|
||||||
<p>
|
other number of nodes. A train travels through your network by moving
|
||||||
Behind the scenes, your Rail Signal models your network as a set of
|
from node to node, until it reaches its desired destination. Both the
|
||||||
<strong>path nodes</strong>, where each node can be connected to any
|
segment boundary and switch are types of path nodes.
|
||||||
other number of nodes. A train travels through your network by moving
|
</p>
|
||||||
from node to node, until it reaches its desired destination. Both the
|
<ul>
|
||||||
segment boundary and switch are types of path nodes.
|
<li>
|
||||||
</p>
|
Segment boundaries may only be connected to at most two nodes. This
|
||||||
<ul>
|
is because a segment boundary is fundamentally just a point on a
|
||||||
<li>
|
single rail line.
|
||||||
Segment boundaries may only be connected to at most two nodes. This
|
</li>
|
||||||
is because a segment boundary is fundamentally just a point on a
|
<li>
|
||||||
single rail line.
|
Switches are connected to nodes based on their set of defined
|
||||||
</li>
|
configurations. In the example diagram, our switch allows two
|
||||||
<li>
|
possible configurations:
|
||||||
Switches are connected to nodes based on their set of defined
|
<ul>
|
||||||
configurations. In the example diagram, our switch allows two
|
<li>Between the <strong style="color: #3cadab">blue</strong> and <strong style="color: #7169b4">purple</strong> segments.</li>
|
||||||
possible configurations:
|
<li>Between the <strong style="color: #81d07b">green</strong> and <strong style="color: #7169b4">purple</strong> segments.</li>
|
||||||
<ul>
|
</ul>
|
||||||
<li>Between the <strong style="color: #3cadab">blue</strong> and <strong style="color: #7169b4">purple</strong> segments.</li>
|
This implies that our switch node is connected to three other nodes:
|
||||||
<li>Between the <strong style="color: #81d07b">green</strong> and <strong style="color: #7169b4">purple</strong> segments.</li>
|
each of the segment boundaries that it allows traffic between.
|
||||||
</ul>
|
</li>
|
||||||
This implies that our switch node is connected to three other nodes:
|
</ul>
|
||||||
each of the segment boundaries that it allows traffic between.
|
</index-page-section>
|
||||||
</li>
|
<index-page-section title="Drivers">
|
||||||
</ul>
|
<p>
|
||||||
</div>
|
While you can play around in this web app as long as you'd like, the
|
||||||
</div>
|
main point is to connect to an external rail system. That's done through
|
||||||
<div class="row">
|
a <strong>driver</strong>, which is a dedicated piece of code that sends
|
||||||
<div class="col-md-6 q-pa-md">
|
and receives messages from the Rail Signal server. Usually, driver
|
||||||
<div class="text-h4">Advanced Usage</div>
|
software will be installed into physical components in your system, like
|
||||||
<q-img src="~assets/img/guide/layout2.png"/>
|
signals and trackside detectors, and switch levers. It's the responsibility
|
||||||
<p>
|
of driver software to tell Rail Signal when a train crosses a segment
|
||||||
The above diagram shows a more typical network arrangement for a large
|
boundary, or when a switch updates, or anything else it should know
|
||||||
scale, two-way mainline. Here, we see that each side of the main line
|
about.
|
||||||
has its own segment, so that trains can travel past each other without
|
</p>
|
||||||
issue. We make the entire junction a single segment, so that only one
|
</index-page-section>
|
||||||
train can pass through at a time. More advanced setups might have
|
<index-page-section title="Advanced Usage">
|
||||||
separate segments for bypass lines to avoid traffic jams. Beside each
|
<q-img src="~assets/img/guide/layout2.png"/>
|
||||||
entrance and exit to the junction, we've placed a signal. On the
|
<p>
|
||||||
outbound segments, the signal will report the status of the outbound
|
The above diagram shows a more typical network arrangement for a large
|
||||||
segment, while on the inbound segments, the signal will show the
|
scale, two-way mainline. Here, we see that each side of the main line
|
||||||
status of the junction segment.
|
has its own segment, so that trains can travel past each other without
|
||||||
</p>
|
issue. We make the entire junction a single segment, so that only one
|
||||||
</div>
|
train can pass through at a time. More advanced setups might have
|
||||||
</div>
|
separate segments for bypass lines to avoid traffic jams. Beside each
|
||||||
|
entrance and exit to the junction, we've placed a signal. On the
|
||||||
|
outbound segments, the signal will report the status of the outbound
|
||||||
|
segment, while on the inbound segments, the signal will show the
|
||||||
|
status of the junction segment.
|
||||||
|
</p>
|
||||||
|
</index-page-section>
|
||||||
</q-page>
|
</q-page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import IndexPageSection from "components/IndexPageSection.vue";
|
||||||
export default {
|
export default {
|
||||||
name: "IndexPage"
|
name: "IndexPage",
|
||||||
|
components: { IndexPageSection }
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<q-page>
|
<q-page>
|
||||||
<div v-if="railSystem">
|
<div v-if="railSystem && railSystem.loaded">
|
||||||
<q-tabs
|
<q-tabs
|
||||||
v-model="panel"
|
v-model="panel"
|
||||||
align="left"
|
align="left"
|
||||||
|
@ -25,6 +25,10 @@
|
||||||
<router-view />
|
<router-view />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<q-inner-loading
|
||||||
|
:showing="!railSystem || !railSystem.loaded"
|
||||||
|
label="Loading rail system..."
|
||||||
|
/>
|
||||||
</q-page>
|
</q-page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -33,6 +37,7 @@ import { useRailSystemsStore } from "stores/railSystemsStore";
|
||||||
import MapView from "components/rs/MapView.vue";
|
import MapView from "components/rs/MapView.vue";
|
||||||
import SegmentsView from "components/rs/SegmentsView.vue";
|
import SegmentsView from "components/rs/SegmentsView.vue";
|
||||||
import SettingsView from "components/rs/SettingsView.vue";
|
import SettingsView from "components/rs/SettingsView.vue";
|
||||||
|
import { loadData, unloadData } from "src/api/railSystems";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "RailSystemPage",
|
name: "RailSystemPage",
|
||||||
|
@ -41,23 +46,45 @@ export default {
|
||||||
return {
|
return {
|
||||||
panel: "map",
|
panel: "map",
|
||||||
railSystem: null,
|
railSystem: null,
|
||||||
|
loading: false
|
||||||
linkTokens: []
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeRouteEnter(to, from, next) {
|
setup() {
|
||||||
const id = parseInt(to.params.id);
|
|
||||||
const rsStore = useRailSystemsStore();
|
const rsStore = useRailSystemsStore();
|
||||||
rsStore.selectRailSystem(id).then(() => {
|
return {rsStore};
|
||||||
next(vm => vm.railSystem = rsStore.selectedRailSystem);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
beforeRouteUpdate(to, from) {
|
mounted() {
|
||||||
const id = parseInt(to.params.id);
|
this.updateRailSystem();
|
||||||
const rsStore = useRailSystemsStore();
|
},
|
||||||
rsStore.selectRailSystem(id).then(() => {
|
created() {
|
||||||
this.railSystem = rsStore.selectedRailSystem;
|
this.$watch(
|
||||||
});
|
() => this.$route.params,
|
||||||
|
this.updateRailSystem,
|
||||||
|
{
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async updateRailSystem() {
|
||||||
|
if (this.loading) return;
|
||||||
|
this.loading = true;
|
||||||
|
console.log(">>>> updating rail system.")
|
||||||
|
if (this.railSystem) {
|
||||||
|
this.rsStore.selectedRailSystem = null;
|
||||||
|
await unloadData(this.railSystem);
|
||||||
|
}
|
||||||
|
if (this.$route.params.id) {
|
||||||
|
const newRsId = parseInt(this.$route.params.id);
|
||||||
|
const rs = this.rsStore.railSystems.find(r => r.id === newRsId);
|
||||||
|
if (rs) {
|
||||||
|
this.railSystem = rs;
|
||||||
|
this.rsStore.selectedRailSystem = rs;
|
||||||
|
await loadData(rs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -13,4 +13,13 @@ export function roundedRect(ctx, x, y, w, h, r) {
|
||||||
export function circle(ctx, x, y, r) {
|
export function circle(ctx, x, y, r) {
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.arc(x, y, r, 0, Math.PI * 2);
|
ctx.arc(x, y, r, 0, Math.PI * 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function mulberry32(a) {
|
||||||
|
return function() {
|
||||||
|
let t = a += 0x6D2B79F5;
|
||||||
|
t = Math.imul(t ^ t >>> 15, t | 1);
|
||||||
|
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
|
||||||
|
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,10 +2,89 @@
|
||||||
Helper functions to actually perform rendering of different components.
|
Helper functions to actually perform rendering of different components.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { getScaleFactor, isComponentHovered, isComponentSelected } from "./mapRenderer";
|
import { getScaleFactor, getWorldTransform, isComponentHovered, isComponentSelected } from "./mapRenderer";
|
||||||
import { circle, roundedRect } from "./canvasUtils";
|
import { circle, roundedRect } from "./canvasUtils";
|
||||||
|
import randomColor from "randomcolor";
|
||||||
|
|
||||||
export function drawComponent(ctx, worldTx, component) {
|
export function drawMap(ctx, rs) {
|
||||||
|
const worldTx = getWorldTransform();
|
||||||
|
ctx.setTransform(worldTx);
|
||||||
|
drawSegments(ctx, rs);
|
||||||
|
drawNodeConnections(ctx, rs, worldTx);
|
||||||
|
drawComponents(ctx, rs, worldTx);
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawSegments(ctx, rs) {
|
||||||
|
const segmentPoints = new Map();
|
||||||
|
rs.segments.forEach(segment => segmentPoints.set(segment.id, []));
|
||||||
|
for (let i = 0; i < rs.components.length; i++) {
|
||||||
|
const c = rs.components[i];
|
||||||
|
if (c.type === "SEGMENT_BOUNDARY") {
|
||||||
|
for (let j = 0; j < c.segments.length; j++) {
|
||||||
|
segmentPoints.get(c.segments[j].id).push({x: c.position.x, y: c.position.z});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < rs.segments.length; i++) {
|
||||||
|
const color = randomColor({ luminosity: 'light', format: 'rgb', seed: rs.segments[i].id });
|
||||||
|
ctx.fillStyle = color;
|
||||||
|
ctx.strokeStyle = color;
|
||||||
|
ctx.lineWidth = 5;
|
||||||
|
ctx.lineCap = "round";
|
||||||
|
ctx.lineJoin = "round";
|
||||||
|
ctx.font = "3px Sans-Serif";
|
||||||
|
|
||||||
|
const points = segmentPoints.get(rs.segments[i].id);
|
||||||
|
if (points.length === 0) continue;
|
||||||
|
const avgPoint = {x: points[0].x, y: points[0].y};
|
||||||
|
if (points.length === 1) {
|
||||||
|
circle(ctx, points[0].x, points[0].y, 5);
|
||||||
|
ctx.fill();
|
||||||
|
} else {
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(points[0].x, points[0].y);
|
||||||
|
for (let j = 1; j < points.length; j++) {
|
||||||
|
ctx.lineTo(points[j].x, points[j].y);
|
||||||
|
avgPoint.x += points[j].x;
|
||||||
|
avgPoint.y += points[j].y;
|
||||||
|
}
|
||||||
|
avgPoint.x /= points.length;
|
||||||
|
avgPoint.y /= points.length;
|
||||||
|
ctx.lineTo(points[0].x, points[0].y);
|
||||||
|
ctx.fill();
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the segment name.
|
||||||
|
ctx.fillStyle = randomColor({luminosity: 'dark', format: 'rgb', seed: rs.segments[i].id});
|
||||||
|
ctx.fillText(rs.segments[i].name, avgPoint.x, avgPoint.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawNodeConnections(ctx, rs, worldTx) {
|
||||||
|
for (let i = 0; i < rs.components.length; i++) {
|
||||||
|
const c = rs.components[i];
|
||||||
|
if (c.connectedNodes !== undefined && c.connectedNodes !== null) {
|
||||||
|
drawConnectedNodes(ctx, worldTx, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawComponents(ctx, rs, worldTx) {
|
||||||
|
// Draw switch configurations first
|
||||||
|
for (let i = 0; i < rs.components.length; i++) {
|
||||||
|
if (rs.components[i].type === "SWITCH") {
|
||||||
|
drawSwitchConfigurations(ctx, worldTx, rs.components[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < rs.components.length; i++) {
|
||||||
|
drawComponent(ctx, worldTx, rs.components[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawComponent(ctx, worldTx, component) {
|
||||||
const tx = DOMMatrix.fromMatrix(worldTx);
|
const tx = DOMMatrix.fromMatrix(worldTx);
|
||||||
tx.translateSelf(component.position.x, component.position.z, 0);
|
tx.translateSelf(component.position.x, component.position.z, 0);
|
||||||
const s = getScaleFactor();
|
const s = getScaleFactor();
|
||||||
|
@ -67,48 +146,60 @@ function drawSegmentBoundary(ctx) {
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawSwitch(ctx, sw) {
|
function drawSwitchConfigurations(ctx, worldTx, sw) {
|
||||||
const colors = [
|
const tx = DOMMatrix.fromMatrix(worldTx);
|
||||||
`rgba(61, 148, 66, 0.25)`,
|
tx.translateSelf(sw.position.x, sw.position.z, 0);
|
||||||
`rgba(59, 22, 135, 0.25)`,
|
const s = getScaleFactor();
|
||||||
`rgba(145, 17, 90, 0.25)`,
|
tx.scaleSelf(1/s, 1/s, 1/s);
|
||||||
`rgba(191, 49, 10, 0.25)`
|
tx.scaleSelf(20, 20, 20);
|
||||||
];
|
ctx.setTransform(tx);
|
||||||
ctx.lineWidth = 1;
|
|
||||||
for (let i = 0; i < sw.possibleConfigurations.length; i++) {
|
for (let i = 0; i < sw.possibleConfigurations.length; i++) {
|
||||||
const config = sw.possibleConfigurations[i];
|
const config = sw.possibleConfigurations[i];
|
||||||
ctx.strokeStyle = colors[i];
|
ctx.strokeStyle = randomColor({
|
||||||
for (let j = 0; j < config.nodes.length; j++) {
|
seed: config.id,
|
||||||
const node = config.nodes[j];
|
format: 'rgb',
|
||||||
const diff = {
|
luminosity: 'bright'
|
||||||
x: sw.position.x - node.position.x,
|
});
|
||||||
y: sw.position.z - node.position.z,
|
if (sw.activeConfiguration !== null && sw.activeConfiguration.id === config.id) {
|
||||||
};
|
ctx.lineWidth = 0.6;
|
||||||
const mag = Math.sqrt(Math.pow(diff.x, 2) + Math.pow(diff.y, 2));
|
} else {
|
||||||
diff.x = 2 * -diff.x / mag;
|
ctx.lineWidth = 0.3;
|
||||||
diff.y = 2 * -diff.y / mag;
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(0, 0);
|
|
||||||
ctx.lineTo(diff.x, diff.y);
|
|
||||||
ctx.stroke();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
ctx.fillStyle = `rgb(245, 188, 66)`;
|
for (let j = 0; j < config.nodes.length; j++) {
|
||||||
ctx.strokeStyle = `rgb(245, 188, 66)`;
|
const node = config.nodes[j];
|
||||||
ctx.lineWidth = 0.2;
|
const diff = {
|
||||||
circle(ctx, 0, 0.3, 0.2);
|
x: sw.position.x - node.position.x,
|
||||||
ctx.fill();
|
y: sw.position.z - node.position.z,
|
||||||
circle(ctx, -0.3, -0.3, 0.2);
|
};
|
||||||
ctx.fill();
|
const mag = Math.sqrt(Math.pow(diff.x, 2) + Math.pow(diff.y, 2));
|
||||||
circle(ctx, 0.3, -0.3, 0.2);
|
diff.x = 3 * -diff.x / mag;
|
||||||
ctx.fill();
|
diff.y = 3 * -diff.y / mag;
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(0, 0.3);
|
ctx.moveTo(0, 0);
|
||||||
ctx.lineTo(0, 0);
|
ctx.lineTo(diff.x, diff.y);
|
||||||
ctx.lineTo(0.3, -0.3);
|
ctx.stroke();
|
||||||
ctx.moveTo(0, 0);
|
}
|
||||||
ctx.lineTo(-0.3, -0.3);
|
}
|
||||||
ctx.stroke();
|
}
|
||||||
|
|
||||||
|
function drawSwitch(ctx, sw) {
|
||||||
|
ctx.fillStyle = `rgb(245, 188, 66)`;
|
||||||
|
ctx.strokeStyle = `rgb(245, 188, 66)`;
|
||||||
|
ctx.lineWidth = 0.2;
|
||||||
|
circle(ctx, 0, 0.3, 0.2);
|
||||||
|
ctx.fill();
|
||||||
|
circle(ctx, -0.3, -0.3, 0.2);
|
||||||
|
ctx.fill();
|
||||||
|
circle(ctx, 0.3, -0.3, 0.2);
|
||||||
|
ctx.fill();
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(0, 0.3);
|
||||||
|
ctx.lineTo(0, 0);
|
||||||
|
ctx.lineTo(0.3, -0.3);
|
||||||
|
ctx.moveTo(0, 0);
|
||||||
|
ctx.lineTo(-0.3, -0.3);
|
||||||
|
ctx.stroke();
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawLabel(ctx, lbl) {
|
function drawLabel(ctx, lbl) {
|
||||||
|
|
|
@ -3,7 +3,7 @@ This component is responsible for the rendering of a RailSystem in a 2d map
|
||||||
view.
|
view.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {drawComponent, drawConnectedNodes} from "./drawing";
|
import { drawMap } from "./drawing";
|
||||||
|
|
||||||
const SCALE_VALUES = [0.01, 0.1, 0.25, 0.5, 1.0, 1.25, 1.5, 2.0, 3.0, 4.0, 6.0, 8.0, 10.0, 12.0, 16.0, 20.0, 30.0, 45.0, 60.0, 80.0, 100.0];
|
const SCALE_VALUES = [0.01, 0.1, 0.25, 0.5, 1.0, 1.25, 1.5, 2.0, 3.0, 4.0, 6.0, 8.0, 10.0, 12.0, 16.0, 20.0, 30.0, 45.0, 60.0, 80.0, 100.0];
|
||||||
const SCALE_INDEX_NORMAL = 7;
|
const SCALE_INDEX_NORMAL = 7;
|
||||||
|
@ -56,73 +56,26 @@ export function draw() {
|
||||||
ctx.resetTransform();
|
ctx.resetTransform();
|
||||||
ctx.fillStyle = `rgb(240, 240, 240)`;
|
ctx.fillStyle = `rgb(240, 240, 240)`;
|
||||||
ctx.fillRect(0, 0, width, height);
|
ctx.fillRect(0, 0, width, height);
|
||||||
const worldTx = getWorldTransform();
|
|
||||||
ctx.setTransform(worldTx);
|
|
||||||
|
|
||||||
// Draw segments!
|
drawMap(ctx, railSystem);
|
||||||
const segmentPoints = new Map();
|
drawDebugInfo(ctx);
|
||||||
railSystem.segments.forEach(segment => segmentPoints.set(segment.id, []));
|
}
|
||||||
for (let i = 0; i < railSystem.components.length; i++) {
|
|
||||||
const c = railSystem.components[i];
|
|
||||||
if (c.type === "SEGMENT_BOUNDARY") {
|
|
||||||
for (let j = 0; j < c.segments.length; j++) {
|
|
||||||
segmentPoints.get(c.segments[j].id).push({x: c.position.x, y: c.position.z});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
railSystem.segments.forEach(segment => {
|
|
||||||
const points = segmentPoints.get(segment.id);
|
|
||||||
const avgPoint = {x: 0, y: 0};
|
|
||||||
points.forEach(point => {
|
|
||||||
avgPoint.x += point.x;
|
|
||||||
avgPoint.y += point.y;
|
|
||||||
});
|
|
||||||
avgPoint.x /= points.length;
|
|
||||||
avgPoint.y /= points.length;
|
|
||||||
let r = 5;
|
|
||||||
points.forEach(point => {
|
|
||||||
const dist2 = Math.pow(avgPoint.x - point.x, 2) + Math.pow(avgPoint.y - point.y, 2);
|
|
||||||
if (dist2 > r * r) {
|
|
||||||
r = Math.sqrt(dist2);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
ctx.fillStyle = `rgba(200, 200, 200, 0.25)`;
|
|
||||||
const p = worldPointToMap(new DOMPoint(avgPoint.x, avgPoint.y, 0, 0));
|
|
||||||
const s = getScaleFactor();
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.arc(p.x / s, p.y / s, r, 0, Math.PI * 2);
|
|
||||||
ctx.fill();
|
|
||||||
ctx.fillStyle = "rgba(0, 0, 0, 0.5)";
|
|
||||||
ctx.font = "3px Sans-Serif";
|
|
||||||
ctx.fillText(`${segment.name}`, p.x / s, p.y / s);
|
|
||||||
});
|
|
||||||
|
|
||||||
for (let i = 0; i < railSystem.components.length; i++) {
|
function drawDebugInfo(ctx) {
|
||||||
const c = railSystem.components[i];
|
ctx.resetTransform();
|
||||||
if (c.connectedNodes !== undefined && c.connectedNodes !== null) {
|
ctx.fillStyle = "black";
|
||||||
drawConnectedNodes(ctx, worldTx, c);
|
ctx.strokeStyle = "black";
|
||||||
}
|
ctx.font = "10px Sans-Serif";
|
||||||
}
|
const lastWorldPoint = mapPointToWorld(lastMousePoint);
|
||||||
|
const lines = [
|
||||||
for (let i = 0; i < railSystem.components.length; i++) {
|
"Scale factor: " + getScaleFactor(),
|
||||||
drawComponent(ctx, worldTx, railSystem.components[i]);
|
`(x = ${lastWorldPoint.x.toFixed(2)}, y = ${lastWorldPoint.y.toFixed(2)}, z = ${lastWorldPoint.z.toFixed(2)})`,
|
||||||
}
|
`Components: ${railSystem.components.length}`,
|
||||||
|
`Hovered elements: ${hoveredElements.length}`
|
||||||
// Draw debug info.
|
]
|
||||||
ctx.resetTransform();
|
for (let i = 0; i < lines.length; i++) {
|
||||||
ctx.fillStyle = "black";
|
ctx.fillText(lines[i], 10, 20 + (i * 15));
|
||||||
ctx.strokeStyle = "black";
|
}
|
||||||
ctx.font = "10px Sans-Serif";
|
|
||||||
const lastWorldPoint = mapPointToWorld(lastMousePoint);
|
|
||||||
const lines = [
|
|
||||||
"Scale factor: " + getScaleFactor(),
|
|
||||||
`(x = ${lastWorldPoint.x.toFixed(2)}, y = ${lastWorldPoint.y.toFixed(2)}, z = ${lastWorldPoint.z.toFixed(2)})`,
|
|
||||||
`Components: ${railSystem.components.length}`,
|
|
||||||
`Hovered elements: ${hoveredElements.length}`
|
|
||||||
]
|
|
||||||
for (let i = 0; i < lines.length; i++) {
|
|
||||||
ctx.fillText(lines[i], 10, 20 + (i * 15));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getScaleFactor() {
|
export function getScaleFactor() {
|
||||||
|
@ -133,7 +86,7 @@ export function getScaleFactor() {
|
||||||
* Gets a matrix that transforms world coordinates to canvas.
|
* Gets a matrix that transforms world coordinates to canvas.
|
||||||
* @returns {DOMMatrix}
|
* @returns {DOMMatrix}
|
||||||
*/
|
*/
|
||||||
function getWorldTransform() {
|
export function getWorldTransform() {
|
||||||
const canvasRect = mapCanvas.getBoundingClientRect();
|
const canvasRect = mapCanvas.getBoundingClientRect();
|
||||||
const scale = getScaleFactor();
|
const scale = getScaleFactor();
|
||||||
const tx = new DOMMatrix();
|
const tx = new DOMMatrix();
|
||||||
|
@ -159,7 +112,7 @@ export function isComponentSelected(component) {
|
||||||
* @param {DOMPoint} p
|
* @param {DOMPoint} p
|
||||||
* @returns {DOMPoint}
|
* @returns {DOMPoint}
|
||||||
*/
|
*/
|
||||||
function mapPointToWorld(p) {
|
export function mapPointToWorld(p) {
|
||||||
return getWorldTransform().invertSelf().transformPoint(p);
|
return getWorldTransform().invertSelf().transformPoint(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,7 +121,7 @@ function mapPointToWorld(p) {
|
||||||
* @param {DOMPoint} p
|
* @param {DOMPoint} p
|
||||||
* @returns {DOMPoint}
|
* @returns {DOMPoint}
|
||||||
*/
|
*/
|
||||||
function worldPointToMap(p) {
|
export function worldPointToMap(p) {
|
||||||
return getWorldTransform().transformPoint(p);
|
return getWorldTransform().transformPoint(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,20 +20,22 @@ export const useRailSystemsStore = defineStore('RailSystemsStore', {
|
||||||
actions: {
|
actions: {
|
||||||
/**
|
/**
|
||||||
* Updates the selected rail system.
|
* Updates the selected rail system.
|
||||||
* @param rsId {Number} The new rail system id.
|
* @param rsId {Number | null} The new rail system id.
|
||||||
* @returns {Promise} A promise that resolves when the new rail system is
|
* @returns {Promise} A promise that resolves when the new rail system is
|
||||||
* fully loaded and ready.
|
* fully loaded and ready.
|
||||||
*/
|
*/
|
||||||
selectRailSystem(rsId) {
|
selectRailSystem(rsId) {
|
||||||
// Close any existing websocket connections prior to refreshing.
|
// Close any existing websocket connections prior to refreshing.
|
||||||
const wsClosePromises = [];
|
const wsClosePromises = [];
|
||||||
if (this.selectedRailSystem) {
|
if (this.selectedRailSystem !== null) {
|
||||||
wsClosePromises.push(closeWebsocketConnection(this.selectedRailSystem));
|
wsClosePromises.push(closeWebsocketConnection(this.selectedRailSystem));
|
||||||
}
|
}
|
||||||
|
if (rsId === null) return Promise.all(wsClosePromises);
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
Promise.all(wsClosePromises).then(() => {
|
Promise.all(wsClosePromises).then(() => {
|
||||||
refreshRailSystems(this).then(() => {
|
refreshRailSystems(this).then(() => {
|
||||||
const rs = this.railSystems.find(r => r.id === rsId);
|
const rs = this.railSystems.find(r => r.id === rsId);
|
||||||
|
console.log(rs);
|
||||||
const updatePromises = [];
|
const updatePromises = [];
|
||||||
updatePromises.push(refreshSegments(rs));
|
updatePromises.push(refreshSegments(rs));
|
||||||
updatePromises.push(refreshComponents(rs));
|
updatePromises.push(refreshComponents(rs));
|
||||||
|
|
|
@ -9,7 +9,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
* web app's index page.
|
* web app's index page.
|
||||||
*/
|
*/
|
||||||
@Controller
|
@Controller
|
||||||
@RequestMapping(path = {"/", "/app", "/app/about", "/home", "/index.html", "/index"})
|
@RequestMapping(path = {"/", "/app", "/app/about", "/app/rail-systems/*", "/home", "/index.html", "/index"})
|
||||||
public class IndexPageController {
|
public class IndexPageController {
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public String getIndex() {
|
public String getIndex() {
|
||||||
|
|
|
@ -2,6 +2,7 @@ package nl.andrewl.railsignalapi.rest.dto.component.out;
|
||||||
|
|
||||||
import nl.andrewl.railsignalapi.model.component.PathNode;
|
import nl.andrewl.railsignalapi.model.component.PathNode;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public abstract class PathNodeResponse extends ComponentResponse {
|
public abstract class PathNodeResponse extends ComponentResponse {
|
||||||
|
@ -9,6 +10,9 @@ public abstract class PathNodeResponse extends ComponentResponse {
|
||||||
|
|
||||||
public PathNodeResponse(PathNode p) {
|
public PathNodeResponse(PathNode p) {
|
||||||
super(p);
|
super(p);
|
||||||
this.connectedNodes = p.getConnectedNodes().stream().map(SimpleComponentResponse::new).toList();
|
this.connectedNodes = p.getConnectedNodes().stream()
|
||||||
|
.sorted(Comparator.comparing(PathNode::getName))
|
||||||
|
.map(SimpleComponentResponse::new)
|
||||||
|
.toList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package nl.andrewl.railsignalapi.rest.dto.component.out;
|
||||||
import nl.andrewl.railsignalapi.model.component.SegmentBoundaryNode;
|
import nl.andrewl.railsignalapi.model.component.SegmentBoundaryNode;
|
||||||
import nl.andrewl.railsignalapi.rest.dto.SegmentResponse;
|
import nl.andrewl.railsignalapi.rest.dto.SegmentResponse;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class SegmentBoundaryNodeResponse extends PathNodeResponse {
|
public class SegmentBoundaryNodeResponse extends PathNodeResponse {
|
||||||
|
@ -10,6 +11,9 @@ public class SegmentBoundaryNodeResponse extends PathNodeResponse {
|
||||||
|
|
||||||
public SegmentBoundaryNodeResponse(SegmentBoundaryNode n) {
|
public SegmentBoundaryNodeResponse(SegmentBoundaryNode n) {
|
||||||
super(n);
|
super(n);
|
||||||
this.segments = n.getSegments().stream().map(SegmentResponse::new).toList();
|
this.segments = n.getSegments().stream()
|
||||||
|
.map(SegmentResponse::new)
|
||||||
|
.sorted(Comparator.comparing(sr -> sr.name))
|
||||||
|
.toList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package nl.andrewl.railsignalapi.rest.dto.component.out;
|
||||||
|
|
||||||
import nl.andrewl.railsignalapi.model.component.SwitchConfiguration;
|
import nl.andrewl.railsignalapi.model.component.SwitchConfiguration;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public record SwitchConfigurationResponse (
|
public record SwitchConfigurationResponse (
|
||||||
|
@ -11,7 +12,10 @@ public record SwitchConfigurationResponse (
|
||||||
public SwitchConfigurationResponse(SwitchConfiguration sc) {
|
public SwitchConfigurationResponse(SwitchConfiguration sc) {
|
||||||
this(
|
this(
|
||||||
sc.getId(),
|
sc.getId(),
|
||||||
sc.getNodes().stream().map(SimpleComponentResponse::new).toList()
|
sc.getNodes().stream()
|
||||||
|
.map(SimpleComponentResponse::new)
|
||||||
|
.sorted(Comparator.comparing(SimpleComponentResponse::name))
|
||||||
|
.toList()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package nl.andrewl.railsignalapi.rest.dto.component.out;
|
||||||
|
|
||||||
import nl.andrewl.railsignalapi.model.component.Switch;
|
import nl.andrewl.railsignalapi.model.component.Switch;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class SwitchResponse extends PathNodeResponse {
|
public class SwitchResponse extends PathNodeResponse {
|
||||||
|
@ -10,7 +11,10 @@ public class SwitchResponse extends PathNodeResponse {
|
||||||
|
|
||||||
public SwitchResponse(Switch s) {
|
public SwitchResponse(Switch s) {
|
||||||
super(s);
|
super(s);
|
||||||
this.possibleConfigurations = s.getPossibleConfigurations().stream().map(SwitchConfigurationResponse::new).toList();
|
this.possibleConfigurations = s.getPossibleConfigurations().stream()
|
||||||
|
.map(SwitchConfigurationResponse::new)
|
||||||
|
.sorted(Comparator.comparing(SwitchConfigurationResponse::id))
|
||||||
|
.toList();
|
||||||
this.activeConfiguration = s.getActiveConfiguration() == null ? null : new SwitchConfigurationResponse(s.getActiveConfiguration());
|
this.activeConfiguration = s.getActiveConfiguration() == null ? null : new SwitchConfigurationResponse(s.getActiveConfiguration());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package nl.andrewl.railsignalapi.service;
|
package nl.andrewl.railsignalapi.service;
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import nl.andrewl.railsignalapi.dao.ComponentRepository;
|
import nl.andrewl.railsignalapi.dao.ComponentRepository;
|
||||||
import nl.andrewl.railsignalapi.dao.RailSystemRepository;
|
import nl.andrewl.railsignalapi.dao.RailSystemRepository;
|
||||||
import nl.andrewl.railsignalapi.dao.SegmentRepository;
|
import nl.andrewl.railsignalapi.dao.SegmentRepository;
|
||||||
|
@ -24,6 +25,7 @@ import java.util.List;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
public class SegmentService {
|
public class SegmentService {
|
||||||
private final SegmentRepository segmentRepository;
|
private final SegmentRepository segmentRepository;
|
||||||
private final RailSystemRepository railSystemRepository;
|
private final RailSystemRepository railSystemRepository;
|
||||||
|
|
Loading…
Reference in New Issue