Added modals, icon, and better formatting.
|
@ -4,7 +4,7 @@
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>RailSignal</title>
|
<title>Rail Signal</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|
|
@ -8,8 +8,9 @@
|
||||||
"name": "railsignal-app",
|
"name": "railsignal-app",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@popperjs/core": "^2.11.5",
|
||||||
"axios": "^0.27.2",
|
"axios": "^0.27.2",
|
||||||
"bootstrap": "^4.6.1",
|
"bootstrap": "^5.1.3",
|
||||||
"pinia": "^2.0.14",
|
"pinia": "^2.0.14",
|
||||||
"three": "^0.140.0",
|
"three": "^0.140.0",
|
||||||
"vue": "^3.2.33",
|
"vue": "^3.2.33",
|
||||||
|
@ -73,6 +74,15 @@
|
||||||
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
|
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@popperjs/core": {
|
||||||
|
"version": "2.11.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.5.tgz",
|
||||||
|
"integrity": "sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/popperjs"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@vitejs/plugin-vue": {
|
"node_modules/@vitejs/plugin-vue": {
|
||||||
"version": "2.3.2",
|
"version": "2.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-2.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-2.3.2.tgz",
|
||||||
|
@ -287,16 +297,15 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/bootstrap": {
|
"node_modules/bootstrap": {
|
||||||
"version": "4.6.1",
|
"version": "5.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.1.3.tgz",
|
||||||
"integrity": "sha512-0dj+VgI9Ecom+rvvpNZ4MUZJz8dcX7WCX+eTID9+/8HgOkv3dsRzi8BGeZJCQU6flWQVYxwTQnEZFrmJSEO7og==",
|
"integrity": "sha512-fcQztozJ8jToQWXxVuEyXWW+dSo8AiXWKwiSSrKWsRB/Qt+Ewwza+JWoLKiTuQLaEPhdNAJ7+Dosc9DOIqNy7Q==",
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
"url": "https://opencollective.com/bootstrap"
|
"url": "https://opencollective.com/bootstrap"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"jquery": "1.9.1 - 3",
|
"@popperjs/core": "^2.10.2"
|
||||||
"popper.js": "^1.16.1"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/brace-expansion": {
|
"node_modules/brace-expansion": {
|
||||||
|
@ -1262,12 +1271,6 @@
|
||||||
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
|
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/jquery": {
|
|
||||||
"version": "3.6.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz",
|
|
||||||
"integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==",
|
|
||||||
"peer": true
|
|
||||||
},
|
|
||||||
"node_modules/js-yaml": {
|
"node_modules/js-yaml": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||||
|
@ -1520,17 +1523,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/popper.js": {
|
|
||||||
"version": "1.16.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
|
|
||||||
"integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==",
|
|
||||||
"deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1",
|
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/popperjs"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.4.13",
|
"version": "8.4.13",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.13.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.13.tgz",
|
||||||
|
@ -1978,6 +1970,11 @@
|
||||||
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
|
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@popperjs/core": {
|
||||||
|
"version": "2.11.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.5.tgz",
|
||||||
|
"integrity": "sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw=="
|
||||||
|
},
|
||||||
"@vitejs/plugin-vue": {
|
"@vitejs/plugin-vue": {
|
||||||
"version": "2.3.2",
|
"version": "2.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-2.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-2.3.2.tgz",
|
||||||
|
@ -2162,9 +2159,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"bootstrap": {
|
"bootstrap": {
|
||||||
"version": "4.6.1",
|
"version": "5.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.1.3.tgz",
|
||||||
"integrity": "sha512-0dj+VgI9Ecom+rvvpNZ4MUZJz8dcX7WCX+eTID9+/8HgOkv3dsRzi8BGeZJCQU6flWQVYxwTQnEZFrmJSEO7og==",
|
"integrity": "sha512-fcQztozJ8jToQWXxVuEyXWW+dSo8AiXWKwiSSrKWsRB/Qt+Ewwza+JWoLKiTuQLaEPhdNAJ7+Dosc9DOIqNy7Q==",
|
||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
"brace-expansion": {
|
"brace-expansion": {
|
||||||
|
@ -2781,12 +2778,6 @@
|
||||||
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
|
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"jquery": {
|
|
||||||
"version": "3.6.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz",
|
|
||||||
"integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==",
|
|
||||||
"peer": true
|
|
||||||
},
|
|
||||||
"js-yaml": {
|
"js-yaml": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||||
|
@ -2967,12 +2958,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"popper.js": {
|
|
||||||
"version": "1.16.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
|
|
||||||
"integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==",
|
|
||||||
"peer": true
|
|
||||||
},
|
|
||||||
"postcss": {
|
"postcss": {
|
||||||
"version": "8.4.13",
|
"version": "8.4.13",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.13.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.13.tgz",
|
||||||
|
|
|
@ -8,7 +8,9 @@
|
||||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore"
|
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@popperjs/core": "^2.11.5",
|
||||||
"axios": "^0.27.2",
|
"axios": "^0.27.2",
|
||||||
|
"bootstrap": "^5.1.3",
|
||||||
"pinia": "^2.0.14",
|
"pinia": "^2.0.14",
|
||||||
"three": "^0.140.0",
|
"three": "^0.140.0",
|
||||||
"vue": "^3.2.33",
|
"vue": "^3.2.33",
|
||||||
|
|
After Width: | Height: | Size: 9.6 KiB |
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 8.5 KiB |
After Width: | Height: | Size: 422 B |
After Width: | Height: | Size: 840 B |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 15 KiB |
|
@ -0,0 +1 @@
|
||||||
|
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
|
|
@ -1,21 +1,17 @@
|
||||||
<template>
|
<template>
|
||||||
<header>
|
<AppNavbar />
|
||||||
<h1>RailSignal</h1>
|
|
||||||
</header>
|
|
||||||
<RailSystemsManager />
|
|
||||||
|
|
||||||
<RailSystem v-if="rsStore.selectedRailSystem !== null" :railSystem="rsStore.selectedRailSystem"/>
|
<RailSystem v-if="rsStore.selectedRailSystem !== null" :railSystem="rsStore.selectedRailSystem"/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import RailSystem from "./components/RailSystem.vue";
|
|
||||||
import RailSystemsManager from "./components/RailSystemsManager.vue";
|
|
||||||
import {useRailSystemsStore} from "./stores/railSystemsStore";
|
import {useRailSystemsStore} from "./stores/railSystemsStore";
|
||||||
|
import AppNavbar from "./components/AppNavbar.vue";
|
||||||
|
import RailSystem from "./components/RailSystem.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
RailSystem,
|
AppNavbar,
|
||||||
RailSystemsManager
|
RailSystem
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const rsStore = useRailSystemsStore();
|
const rsStore = useRailSystemsStore();
|
||||||
|
|
After Width: | Height: | Size: 6.7 KiB |
|
@ -0,0 +1,90 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="256"
|
||||||
|
height="256"
|
||||||
|
viewBox="0 0 67.733332 67.733335"
|
||||||
|
version="1.1"
|
||||||
|
id="svg8"
|
||||||
|
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"
|
||||||
|
sodipodi:docname="icon.svg"
|
||||||
|
inkscape:export-filename="/home/andrew/Programming/github-andrewlalis/RailSignalAPI/railsignal-app/src/assets/icon.png"
|
||||||
|
inkscape:export-xdpi="96"
|
||||||
|
inkscape:export-ydpi="96">
|
||||||
|
<defs
|
||||||
|
id="defs2" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="1.979899"
|
||||||
|
inkscape:cx="164.88542"
|
||||||
|
inkscape:cy="100.52253"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="false"
|
||||||
|
units="px"
|
||||||
|
inkscape:pagecheckerboard="true"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1009"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1" />
|
||||||
|
<metadata
|
||||||
|
id="metadata5">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(0,-229.26665)">
|
||||||
|
<rect
|
||||||
|
id="rect3713"
|
||||||
|
width="52.916668"
|
||||||
|
height="67.73333"
|
||||||
|
x="7.4083328"
|
||||||
|
y="229.26665"
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332"
|
||||||
|
rx="13.229167" />
|
||||||
|
<rect
|
||||||
|
rx="12.30785"
|
||||||
|
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.24762905"
|
||||||
|
y="231.24725"
|
||||||
|
x="9.250967"
|
||||||
|
height="63.772137"
|
||||||
|
width="49.2314"
|
||||||
|
id="rect4520" />
|
||||||
|
<circle
|
||||||
|
style="fill:#00d900;fill-opacity:1;stroke:none;stroke-width:0.65214598"
|
||||||
|
id="path4522"
|
||||||
|
cx="33.866665"
|
||||||
|
cy="248.70078"
|
||||||
|
r="10.364463" />
|
||||||
|
<circle
|
||||||
|
r="10.364463"
|
||||||
|
cy="277.56586"
|
||||||
|
cx="33.866665"
|
||||||
|
id="circle4524"
|
||||||
|
style="fill:#e71e00;fill-opacity:1;stroke:none;stroke-width:0.65214598" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.6 KiB |
|
@ -0,0 +1,91 @@
|
||||||
|
<template>
|
||||||
|
<nav class="navbar navbar-expand-md navbar-light bg-light">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<span class="navbar-brand h1 mb-0">
|
||||||
|
<img src="@/assets/icon.svg" height="24"/>
|
||||||
|
Rail Signal
|
||||||
|
</span>
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||||
|
<ul class="navbar-nav me-auto mb-2, mb-md-0">
|
||||||
|
<li class="nav-item me-2 mb-2 mb-md-0">
|
||||||
|
<select
|
||||||
|
id="railSystemSelect"
|
||||||
|
v-model="rsStore.selectedRailSystem"
|
||||||
|
class="form-select form-select-sm"
|
||||||
|
>
|
||||||
|
<option v-for="rs in rsStore.railSystems" :key="rs.id" :value="rs">
|
||||||
|
{{rs.name}}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item me-2" v-if="rsStore.selectedRailSystem !== null">
|
||||||
|
<button
|
||||||
|
@click="removeRailSystem()"
|
||||||
|
class="btn btn-danger btn-sm"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Remove this Rail System
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-success btn-sm"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#addRailSystemModal"
|
||||||
|
>
|
||||||
|
Add Rail System
|
||||||
|
</button>
|
||||||
|
<AddRailSystem />
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<ConfirmModal
|
||||||
|
ref="confirmModal"
|
||||||
|
:id="'removeRailSystemModal'"
|
||||||
|
:title="'Confirm Rail System Removal'"
|
||||||
|
:message="'Are you sure you want to remove this rail system? This CANNOT be undone. All data will be permanently lost.'"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {useRailSystemsStore} from "../stores/railSystemsStore";
|
||||||
|
import AddRailSystemModal from "./railsystem/AddRailSystemModal.vue";
|
||||||
|
import ConfirmModal from "./ConfirmModal.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "AppNavbar",
|
||||||
|
components: {AddRailSystem: AddRailSystemModal, ConfirmModal},
|
||||||
|
setup() {
|
||||||
|
const rsStore = useRailSystemsStore();
|
||||||
|
rsStore.$subscribe(mutation => {
|
||||||
|
const evt = mutation.events;
|
||||||
|
if (evt.key === "selectedRailSystem" && evt.newValue !== null) {
|
||||||
|
rsStore.fetchSelectedRailSystemData();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
rsStore
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.rsStore.refreshRailSystems();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
removeRailSystem() {
|
||||||
|
this.$refs.confirmModal.showConfirm()
|
||||||
|
.then(() => this.rsStore.removeRailSystem(this.rsStore.selectedRailSystem));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,77 @@
|
||||||
|
<template>
|
||||||
|
<div class="modal fade" tabindex="-1" :id="id">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">{{title}}</h5>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>{{message}}</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" :id="id + '_cancel'">Cancel</button>
|
||||||
|
<button type="button" class="btn btn-primary" :id="id + '_yes'">Yes</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {Modal} from "bootstrap";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ConfirmModal",
|
||||||
|
props: {
|
||||||
|
id: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: "Are you sure you want to continue?"
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: "Confirm"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
expose: ['showConfirm'],
|
||||||
|
methods: {
|
||||||
|
showConfirm() {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
console.log(this.id);
|
||||||
|
console.log(this.title);
|
||||||
|
const modalElement = document.getElementById(this.id);
|
||||||
|
const modal = new Modal(modalElement);
|
||||||
|
console.log(modal);
|
||||||
|
|
||||||
|
function onDismiss() {
|
||||||
|
modal.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onYes() {
|
||||||
|
modalElement.addEventListener("hidden.bs.modal", function onSuccess() {
|
||||||
|
modalElement.removeEventListener("hidden.bs.modal", onSuccess);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
modal.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancelButton = document.getElementById(this.id + "_cancel");
|
||||||
|
cancelButton.removeEventListener("click", onDismiss);
|
||||||
|
cancelButton.addEventListener("click", onDismiss)
|
||||||
|
const yesButton = document.getElementById(this.id + "_yes");
|
||||||
|
yesButton.removeEventListener("click", onYes);
|
||||||
|
yesButton.addEventListener("click", onYes);
|
||||||
|
modal.show(modalElement);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
</style>
|
|
@ -1,26 +1,25 @@
|
||||||
<template>
|
<template>
|
||||||
<h2>{{railSystem.name}}</h2>
|
<div class="container-fluid">
|
||||||
<div>
|
<div class="row">
|
||||||
<MapView :railSystem="railSystem" v-if="railSystem.segments && railSystem.components" />
|
<div class="col-md-8 p-0">
|
||||||
<ComponentView v-if="railSystem.selectedComponent" :component="railSystem.selectedComponent" :railSystem="railSystem"/>
|
<MapView :railSystem="railSystem" v-if="railSystem.segments && railSystem.components" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<ComponentView v-if="railSystem.selectedComponent" :component="railSystem.selectedComponent" :railSystem="railSystem"/>
|
||||||
|
<RailSystemPropertiesView v-if="!railSystem.selectedComponent" :railSystem="railSystem"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<SegmentsView />
|
|
||||||
<AddSignal v-if="railSystem.segments && railSystem.segments.length > 0" />
|
|
||||||
<AddSegmentBoundary v-if="railSystem.segments && railSystem.segments.length > 0" />
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import MapView from './railsystem/MapView.vue'
|
import MapView from './railsystem/MapView.vue'
|
||||||
import ComponentView from './railsystem/component/ComponentView.vue'
|
import ComponentView from './railsystem/component/ComponentView.vue'
|
||||||
import SegmentsView from "./railsystem/SegmentsView.vue";
|
import RailSystemPropertiesView from "./railsystem/RailSystemPropertiesView.vue";
|
||||||
import AddSignal from "./railsystem/component/AddSignal.vue";
|
|
||||||
import AddSegmentBoundary from "./railsystem/component/AddSegmentBoundary.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
AddSignal,
|
RailSystemPropertiesView,
|
||||||
AddSegmentBoundary,
|
|
||||||
SegmentsView,
|
|
||||||
MapView,
|
MapView,
|
||||||
ComponentView
|
ComponentView
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,62 +0,0 @@
|
||||||
<template>
|
|
||||||
<select v-model="rsStore.selectedRailSystem">
|
|
||||||
<option v-for="rs in rsStore.railSystems" :key="rs.id" :value="rs">
|
|
||||||
{{rs.name}}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
<p v-if="rsStore.railSystems.length === 0">
|
|
||||||
There are no rail systems.
|
|
||||||
</p>
|
|
||||||
<button v-if="rsStore.selectedRailSystem !== null" @click="rsStore.removeRailSystem(rsStore.selectedRailSystem)">
|
|
||||||
Remove this Rail System
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<h3>Create a New Rail System</h3>
|
|
||||||
<form>
|
|
||||||
<label for="rsNameInput">Name</label>
|
|
||||||
<input id="rsNameInput" type="text" v-model="formData.rsName"/>
|
|
||||||
<button type="submit" @click.prevent="formSubmitted">Submit</button>
|
|
||||||
</form>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import {useRailSystemsStore} from "../stores/railSystemsStore";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "RailSystemsManager.vue",
|
|
||||||
setup() {
|
|
||||||
const rsStore = useRailSystemsStore();
|
|
||||||
rsStore.$subscribe(mutation => {
|
|
||||||
const evt = mutation.events;
|
|
||||||
if (evt.key === "selectedRailSystem" && evt.newValue !== null) {
|
|
||||||
rsStore.fetchSelectedRailSystemData();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
rsStore
|
|
||||||
};
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
formData: {
|
|
||||||
rsName: ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
formSubmitted() {
|
|
||||||
this.rsStore.createRailSystem(this.formData.rsName)
|
|
||||||
.then(() => {
|
|
||||||
this.formData.rsName = "";
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.rsStore.refreshRailSystems();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
|
|
||||||
</style>
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
<template>
|
||||||
|
<div class="modal fade" tabindex="-1" id="addRailSystemModal">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Add New Rail System</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form>
|
||||||
|
<label for="addRailSystemNameInput" class="form-label">Name</label>
|
||||||
|
<input
|
||||||
|
id="addRailSystemNameInput"
|
||||||
|
class="form-control"
|
||||||
|
type="text"
|
||||||
|
v-model="formData.rsName"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
<div v-if="warnings.length > 0">
|
||||||
|
<div v-for="msg in warnings" :key="msg" class="alert alert-danger mt-2">
|
||||||
|
{{msg}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||||
|
<button type="button" class="btn btn-primary" @click="formSubmitted()">Add</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {useRailSystemsStore} from "../../stores/railSystemsStore";
|
||||||
|
import {Modal} from "bootstrap";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "AddRailSystem",
|
||||||
|
setup() {
|
||||||
|
const rsStore = useRailSystemsStore();
|
||||||
|
return {
|
||||||
|
rsStore
|
||||||
|
};
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
formData: {
|
||||||
|
name: ""
|
||||||
|
},
|
||||||
|
warnings: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
formSubmitted() {
|
||||||
|
this.rsStore.createRailSystem(this.formData.rsName)
|
||||||
|
.then(rs => {
|
||||||
|
this.formData.rsName = "";
|
||||||
|
const modal = Modal.getInstance(document.getElementById("addRailSystemModal"));
|
||||||
|
modal.hide();
|
||||||
|
this.rsStore.selectedRailSystem = rs;
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.log(error);
|
||||||
|
this.warnings.length = 0;
|
||||||
|
this.warnings.push("Couldn't add the rail system: " + error.response.data.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -1,33 +0,0 @@
|
||||||
<template>
|
|
||||||
<h4>Add Segment</h4>
|
|
||||||
<form @submit.prevent="rsStore.addSegment(this.formData.segmentName)">
|
|
||||||
<label for="addSegmentName">Name</label>
|
|
||||||
<input type="text" v-model="formData.segmentName" />
|
|
||||||
<button type="submit">Add</button>
|
|
||||||
</form>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import {useRailSystemsStore} from "../../stores/railSystemsStore";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "AddSegment",
|
|
||||||
setup() {
|
|
||||||
const rsStore = useRailSystemsStore();
|
|
||||||
return {
|
|
||||||
rsStore
|
|
||||||
};
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
formData: {
|
|
||||||
segmentName: ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
|
@ -0,0 +1,86 @@
|
||||||
|
<template>
|
||||||
|
<div class="modal fade" tabindex="-1" id="addSegmentModal">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Add Segment</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>
|
||||||
|
Add a new segment to this rail system. A <em>segment</em> is the
|
||||||
|
basic organizational unit of any rail system. It is a section of
|
||||||
|
the network that signals can monitor, and <em>segment boundary nodes</em>
|
||||||
|
define the extent of the segment, and monitor trains entering and
|
||||||
|
leaving the segment.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
You can think of a segment as a single, secure block of of the rail
|
||||||
|
network that only one train may pass through at once. For example,
|
||||||
|
a junction or station siding.
|
||||||
|
</p>
|
||||||
|
<form>
|
||||||
|
<label for="addSegmentName" class="form-label">Name</label>
|
||||||
|
<input
|
||||||
|
id="addSegmentName"
|
||||||
|
class="form-control"
|
||||||
|
type="text"
|
||||||
|
v-model="formData.name"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
<div v-if="warnings.length > 0">
|
||||||
|
<div v-for="msg in warnings" :key="msg" class="alert alert-danger mt-2">
|
||||||
|
{{msg}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||||
|
<button type="button" class="btn btn-primary" @click="formSubmitted()">Add</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {useRailSystemsStore} from "../../stores/railSystemsStore";
|
||||||
|
import {Modal} from "bootstrap";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "AddSegmentModal",
|
||||||
|
setup() {
|
||||||
|
const rsStore = useRailSystemsStore();
|
||||||
|
return {
|
||||||
|
rsStore
|
||||||
|
};
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
formData: {
|
||||||
|
name: ""
|
||||||
|
},
|
||||||
|
warnings: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
formSubmitted() {
|
||||||
|
const modal = Modal.getInstance(document.getElementById("addSegmentModal"));
|
||||||
|
this.rsStore.addSegment(this.formData.name)
|
||||||
|
.then(() => {
|
||||||
|
this.formData.name = "";
|
||||||
|
modal.hide();
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
this.warnings.length = 0;
|
||||||
|
this.warnings.push("Couldn't add the segment: " + error.response.data.message)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import {initMap} from "./mapRenderer.js";
|
import {initMap, draw} from "./mapRenderer.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
|
@ -15,18 +15,33 @@ export default {
|
||||||
updated() {
|
updated() {
|
||||||
// Also, re-initialize any time this view is updated.
|
// Also, re-initialize any time this view is updated.
|
||||||
initMap(this.railSystem);
|
initMap(this.railSystem);
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
railSystem: {
|
||||||
|
handler() {
|
||||||
|
draw();
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<canvas id="railSystemMapCanvas" width="1000" height="600">
|
<div class="canvas-container" id="railSystemMapCanvasContainer">
|
||||||
|
<canvas id="railSystemMapCanvas">
|
||||||
Your browser doesn't support canvas!
|
Your browser doesn't support canvas!
|
||||||
</canvas>
|
</canvas>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.canvas-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 800px;
|
||||||
|
}
|
||||||
canvas {
|
canvas {
|
||||||
border: 1px solid black;
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
|
@ -0,0 +1,73 @@
|
||||||
|
<template>
|
||||||
|
<h3>Rail System: <em>{{railSystem.name}}</em></h3>
|
||||||
|
<SegmentsView />
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-success btn-sm me-2"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#addSegmentModal"
|
||||||
|
>
|
||||||
|
Add Segment
|
||||||
|
</button>
|
||||||
|
<AddSegmentModal />
|
||||||
|
<span v-if="railSystem.segments && railSystem.segments.length > 0">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-success btn-sm me-2"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#addSignalModal"
|
||||||
|
>
|
||||||
|
Add Signal
|
||||||
|
</button>
|
||||||
|
<AddSignalModal />
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-success btn-sm me-2"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#addSegmentBoundaryModal"
|
||||||
|
>
|
||||||
|
Add Segment Boundary
|
||||||
|
</button>
|
||||||
|
<AddSegmentBoundaryModal />
|
||||||
|
</span>
|
||||||
|
<span v-if="railSystem.components && railSystem.components.length > 1">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-success btn-sm"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#addSwitchModal"
|
||||||
|
>
|
||||||
|
Add Switch
|
||||||
|
</button>
|
||||||
|
<AddSwitchModal />
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import SegmentsView from "./SegmentsView.vue";
|
||||||
|
import AddSegmentModal from "./AddSegmentModal.vue";
|
||||||
|
import AddSignalModal from "./component/AddSignalModal.vue";
|
||||||
|
import AddSegmentBoundaryModal from "./component/AddSegmentBoundaryModal.vue";
|
||||||
|
import AddSwitchModal from "./component/AddSwitchModal.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "RailSystemPropertiesView",
|
||||||
|
components: {
|
||||||
|
AddSwitchModal,
|
||||||
|
AddSegmentBoundaryModal,
|
||||||
|
AddSignalModal,
|
||||||
|
AddSegmentModal,
|
||||||
|
SegmentsView
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
railSystem: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -1,22 +1,22 @@
|
||||||
<template>
|
<template>
|
||||||
<h3>Segments</h3>
|
<h5>Segments</h5>
|
||||||
<ul>
|
<ul class="list-group overflow-auto mb-2" style="max-height: 200px;">
|
||||||
<li v-for="segment in rsStore.selectedRailSystem.segments" :key="segment.id">
|
<li
|
||||||
{{segment.name}} <button @click.prevent="rsStore.removeSegment(segment.id)">Remove</button>
|
v-for="segment in rsStore.selectedRailSystem.segments"
|
||||||
|
:key="segment.id"
|
||||||
|
class="list-group-item"
|
||||||
|
>
|
||||||
|
{{segment.name}}
|
||||||
|
<button @click.prevent="rsStore.removeSegment(segment.id)" class="btn btn-sm btn-danger float-end">Remove</button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<AddSegment />
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import AddSegment from "./AddSegment.vue";
|
|
||||||
import {useRailSystemsStore} from "../../stores/railSystemsStore";
|
import {useRailSystemsStore} from "../../stores/railSystemsStore";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "SegmentsView.vue",
|
name: "SegmentsView.vue",
|
||||||
components: {
|
|
||||||
AddSegment
|
|
||||||
},
|
|
||||||
setup() {
|
setup() {
|
||||||
const rsStore = useRailSystemsStore();
|
const rsStore = useRailSystemsStore();
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,76 +0,0 @@
|
||||||
<template>
|
|
||||||
<h4>Add Segment Boundary</h4>
|
|
||||||
<form @submit.prevent="submit()">
|
|
||||||
<div>
|
|
||||||
<label for="addSBX">X</label>
|
|
||||||
<input type="number" id="addSBX" v-model="formData.position.x" required/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label for="addSBY">Y</label>
|
|
||||||
<input type="number" id="addSBY" v-model="formData.position.y" required/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label for="addSBZ">Z</label>
|
|
||||||
<input type="number" id="addSBZ" v-model="formData.position.z" required/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label for="addSBName">Name</label>
|
|
||||||
<input type="text" id="addSBName" v-model="formData.name"/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label for="addSBSegmentA">Segment A</label>
|
|
||||||
<select id="addSBSegmentA" v-model="formData.segmentA">
|
|
||||||
<option v-for="segment in rsStore.selectedRailSystem.segments" :key="segment.id" :value="segment">
|
|
||||||
{{segment.id}} | {{segment.name}}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
<label for="addSBSegmentB">Segment B</label>
|
|
||||||
<select id="addSBSegmentB" v-model="formData.segmentB">
|
|
||||||
<option v-for="segment in rsStore.selectedRailSystem.segments" :key="segment.id" :value="segment">
|
|
||||||
{{segment.id}} | {{segment.name}}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<button type="submit">Submit</button>
|
|
||||||
</form>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import {useRailSystemsStore} from "../../../stores/railSystemsStore";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "AddSegmentBoundary.vue",
|
|
||||||
setup() {
|
|
||||||
const rsStore = useRailSystemsStore();
|
|
||||||
return {
|
|
||||||
rsStore
|
|
||||||
};
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
formData: {
|
|
||||||
name: "",
|
|
||||||
position: {
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
z: 0
|
|
||||||
},
|
|
||||||
segmentA: null,
|
|
||||||
segmentB: null,
|
|
||||||
segments: [],
|
|
||||||
type: "SEGMENT_BOUNDARY"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
submit() {
|
|
||||||
this.formData.segments = [this.formData.segmentA, this.formData.segmentB];
|
|
||||||
this.rsStore.addComponent(this.formData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
|
@ -0,0 +1,112 @@
|
||||||
|
<template>
|
||||||
|
<div class="modal fade" tabindex="-1" id="addSegmentBoundaryModal">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Add Segment Boundary</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>
|
||||||
|
A <em>segment boundary</em> is a component that defines a link
|
||||||
|
between one segment and another. This component can be used to
|
||||||
|
monitor trains entering and exiting the connected segments. Usually
|
||||||
|
used in conjunction with signals for classic railway signalling
|
||||||
|
systems.
|
||||||
|
</p>
|
||||||
|
<form>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="addSignalName" class="form-label">Name</label>
|
||||||
|
<input class="form-control" type="text" id="addSignalName" v-model="formData.name" required/>
|
||||||
|
</div>
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<label for="addSignalX" class="input-group-text">X</label>
|
||||||
|
<input class="form-control" type="number" id="addSignalX" v-model="formData.position.x" required/>
|
||||||
|
<label for="addSignalY" class="input-group-text">Y</label>
|
||||||
|
<input class="form-control" type="number" id="addSignalY" v-model="formData.position.y" required/>
|
||||||
|
<label for="addSignalZ" class="input-group-text">Z</label>
|
||||||
|
<input class="form-control" type="number" id="addSignalZ" v-model="formData.position.z" required/>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="addSegmentBoundarySegmentA" class="form-label">Segment A</label>
|
||||||
|
<select id="addSegmentBoundarySegmentA" class="form-select" v-model="formData.segmentA">
|
||||||
|
<option v-for="segment in rsStore.selectedRailSystem.segments" :key="segment.id" :value="segment">
|
||||||
|
{{segment.name}}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="addSegmentBoundarySegmentA" class="form-label">Segment B</label>
|
||||||
|
<select id="addSegmentBoundarySegmentA" class="form-select" v-model="formData.segmentB">
|
||||||
|
<option v-for="segment in rsStore.selectedRailSystem.segments" :key="segment.id" :value="segment">
|
||||||
|
{{segment.name}}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<div v-if="warnings.length > 0">
|
||||||
|
<div v-for="msg in warnings" :key="msg" class="alert alert-danger mt-2">
|
||||||
|
{{msg}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||||
|
<button type="button" class="btn btn-primary" @click="formSubmitted()">Add</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {useRailSystemsStore} from "../../../stores/railSystemsStore";
|
||||||
|
import {Modal} from "bootstrap";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "AddSegmentBoundaryModal",
|
||||||
|
setup() {
|
||||||
|
const rsStore = useRailSystemsStore();
|
||||||
|
return {
|
||||||
|
rsStore
|
||||||
|
};
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
formData: {
|
||||||
|
name: "",
|
||||||
|
position: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
z: 0
|
||||||
|
},
|
||||||
|
segmentA: null,
|
||||||
|
segmentB: null,
|
||||||
|
segments: [],
|
||||||
|
type: "SEGMENT_BOUNDARY"
|
||||||
|
},
|
||||||
|
warnings: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
formSubmitted() {
|
||||||
|
const modal = Modal.getInstance(document.getElementById("addSegmentBoundaryModal"));
|
||||||
|
this.formData.segments = [this.formData.segmentA, this.formData.segmentB];
|
||||||
|
this.rsStore.addComponent(this.formData)
|
||||||
|
.then(() => {
|
||||||
|
modal.hide();
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
this.warnings.length = 0;
|
||||||
|
this.warnings.push("Couldn't add the segment boundary: " + error.response.data.message)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -1,62 +0,0 @@
|
||||||
<template>
|
|
||||||
<h4>Add Signal</h4>
|
|
||||||
<form @submit.prevent="rsStore.addComponent(formData)">
|
|
||||||
<div>
|
|
||||||
<label for="addSignalX">X</label>
|
|
||||||
<input type="number" id="addSignalX" v-model="formData.position.x" required/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label for="addSignalY">Y</label>
|
|
||||||
<input type="number" id="addSignalY" v-model="formData.position.y" required/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label for="addSignalZ">Z</label>
|
|
||||||
<input type="number" id="addSignalZ" v-model="formData.position.z" required/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label for="addSignalName">Name</label>
|
|
||||||
<input type="text" id="addSignalName" v-model="formData.name"/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label for="addSignalSegment">Segment</label>
|
|
||||||
<select id="addSignalSegment" v-model="formData.segment">
|
|
||||||
<option v-for="segment in rsStore.selectedRailSystem.segments" :key="segment.id" :value="segment">
|
|
||||||
{{segment.id}} | {{segment.name}}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<button type="submit">Submit</button>
|
|
||||||
</form>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import {useRailSystemsStore} from "../../../stores/railSystemsStore";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: "AddSignal",
|
|
||||||
setup() {
|
|
||||||
const rsStore = useRailSystemsStore();
|
|
||||||
return {
|
|
||||||
rsStore
|
|
||||||
};
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
formData: {
|
|
||||||
name: "",
|
|
||||||
position: {
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
z: 0
|
|
||||||
},
|
|
||||||
segment: null,
|
|
||||||
type: "SIGNAL"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
|
@ -0,0 +1,98 @@
|
||||||
|
<template>
|
||||||
|
<div class="modal fade" tabindex="-1" id="addSignalModal">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Add Signal</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>
|
||||||
|
A <em>signal</em> is a component that relays information about your
|
||||||
|
rail system to in-world devices. Classically, rail signals show a
|
||||||
|
lamp indicator to tell information about the segment of the network
|
||||||
|
they're attached to.
|
||||||
|
</p>
|
||||||
|
<form>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="addSignalName" class="form-label">Name</label>
|
||||||
|
<input class="form-control" type="text" id="addSignalName" v-model="formData.name" required/>
|
||||||
|
</div>
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<label for="addSignalX" class="input-group-text">X</label>
|
||||||
|
<input class="form-control" type="number" id="addSignalX" v-model="formData.position.x" required/>
|
||||||
|
<label for="addSignalY" class="input-group-text">Y</label>
|
||||||
|
<input class="form-control" type="number" id="addSignalY" v-model="formData.position.y" required/>
|
||||||
|
<label for="addSignalZ" class="input-group-text">Z</label>
|
||||||
|
<input class="form-control" type="number" id="addSignalZ" v-model="formData.position.z" required/>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="addSignalSegment" class="form-label">Segment</label>
|
||||||
|
<select id="addSignalSegment" class="form-select" v-model="formData.segment">
|
||||||
|
<option v-for="segment in rsStore.selectedRailSystem.segments" :key="segment.id" :value="segment">
|
||||||
|
{{segment.name}}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<div v-if="warnings.length > 0">
|
||||||
|
<div v-for="msg in warnings" :key="msg" class="alert alert-danger mt-2">
|
||||||
|
{{msg}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||||
|
<button type="button" class="btn btn-primary" @click="formSubmitted()">Add</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {useRailSystemsStore} from "../../../stores/railSystemsStore";
|
||||||
|
import {Modal} from "bootstrap";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "AddSignalModal",
|
||||||
|
setup() {
|
||||||
|
const rsStore = useRailSystemsStore();
|
||||||
|
return {
|
||||||
|
rsStore
|
||||||
|
};
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
formData: {
|
||||||
|
name: "",
|
||||||
|
position: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
z: 0
|
||||||
|
},
|
||||||
|
segment: null,
|
||||||
|
type: "SIGNAL"
|
||||||
|
},
|
||||||
|
warnings: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
formSubmitted() {
|
||||||
|
const modal = Modal.getInstance(document.getElementById("addSignalModal"));
|
||||||
|
this.rsStore.addComponent(this.formData)
|
||||||
|
.then(() => {
|
||||||
|
modal.hide();
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
this.warnings.length = 0;
|
||||||
|
this.warnings.push("Couldn't add the signal: " + error.response.data.message)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,122 @@
|
||||||
|
<template>
|
||||||
|
<div class="modal fade" tabindex="-1" id="addSwitchModal">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Add Switch</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="addSwitchName" class="form-label">Name</label>
|
||||||
|
<input class="form-control" type="text" id="addSwitchName" v-model="formData.name" required/>
|
||||||
|
</div>
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<label for="addSwitchX" class="input-group-text">X</label>
|
||||||
|
<input class="form-control" type="number" id="addSwitchX" v-model="formData.position.x" required/>
|
||||||
|
<label for="addSwitchY" class="input-group-text">Y</label>
|
||||||
|
<input class="form-control" type="number" id="addSwitchY" v-model="formData.position.y" required/>
|
||||||
|
<label for="addSwitchZ" class="input-group-text">Z</label>
|
||||||
|
<input class="form-control" type="number" id="addSwitchZ" v-model="formData.position.z" required/>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<ul class="list-group overflow-auto" style="height: 200px;">
|
||||||
|
<li class="list-group-item" v-for="(config, idx) in formData.possibleConfigurations" :key="idx">
|
||||||
|
{{getConfigString(config)}}
|
||||||
|
<button class="btn btn-sm btn-secondary" @click="formData.possibleConfigurations.splice(idx, 1)">Remove</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="addSwitchConfigs" class="form-label">Segment</label>
|
||||||
|
<select id="addSwitchConfigs" class="form-select" multiple v-model="formData.possibleConfigQueue">
|
||||||
|
<option v-for="node in getEligibleNodes()" :key="node.id" :value="node">
|
||||||
|
{{node.name}}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<button type="button" class="btn btn-sm btn-success" @click="addPossibleConfig()">Add</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<div v-if="warnings.length > 0">
|
||||||
|
<div v-for="msg in warnings" :key="msg" class="alert alert-danger mt-2">
|
||||||
|
{{msg}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||||
|
<button type="button" class="btn btn-primary" @click="formSubmitted()">Add</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {useRailSystemsStore} from "../../../stores/railSystemsStore";
|
||||||
|
import {Modal} from "bootstrap";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "AddSwitchModal",
|
||||||
|
setup() {
|
||||||
|
const rsStore = useRailSystemsStore();
|
||||||
|
return {
|
||||||
|
rsStore
|
||||||
|
};
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
formData: {
|
||||||
|
name: "",
|
||||||
|
position: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
z: 0
|
||||||
|
},
|
||||||
|
possibleConfigurations: [],
|
||||||
|
possibleConfigQueue: [],
|
||||||
|
type: "SWITCH"
|
||||||
|
},
|
||||||
|
warnings: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
formSubmitted() {
|
||||||
|
const modal = Modal.getInstance(document.getElementById("addSwitchModal"));
|
||||||
|
this.rsStore.addComponent(this.formData)
|
||||||
|
.then(() => {
|
||||||
|
modal.hide();
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
this.warnings.length = 0;
|
||||||
|
this.warnings.push("Couldn't add the signal: " + error.response.data.message)
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getConfigString(config) {
|
||||||
|
return config.nodes.map(n => n.name).join(", ");
|
||||||
|
},
|
||||||
|
getEligibleNodes() {
|
||||||
|
return this.rsStore.selectedRailSystem.components.filter(c => {
|
||||||
|
if (c.connectedNodes === undefined || c.connectedNodes === null) return false;
|
||||||
|
for (let i = 0; i < this.formData.possibleConfigurations.length; i++) {
|
||||||
|
const config = this.formData.possibleConfigurations[i];
|
||||||
|
for (const node in config.nodes) {
|
||||||
|
if (node.id === c.id) return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
addPossibleConfig() {
|
||||||
|
if (this.formData.possibleConfigQueue.length < 2) return;
|
||||||
|
this.formData.possibleConfigurations.push({nodes: this.formData.possibleConfigQueue});
|
||||||
|
this.formData.possibleConfigQueue = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -1,23 +1,42 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="rs-component">
|
<div>
|
||||||
<h3>{{component.name}}</h3>
|
<h3>{{component.name}}</h3>
|
||||||
<p>
|
<small class="text-muted">{{component.type}}</small>
|
||||||
Id: {{component.id}}
|
<table class="table">
|
||||||
</p>
|
<tbody>
|
||||||
<p>
|
<tr>
|
||||||
Position: (x = {{component.position.x}}, y = {{component.position.y}}, z = {{component.position.z}})
|
<th>Id</th><td>{{component.id}}</td>
|
||||||
</p>
|
</tr>
|
||||||
<p>
|
<tr>
|
||||||
Type: {{component.type}}
|
<th>Position</th>
|
||||||
</p>
|
<td>
|
||||||
<p>
|
<table class="table table-borderless m-0 p-0">
|
||||||
Online: {{component.online}}
|
<tbody>
|
||||||
</p>
|
<tr>
|
||||||
|
<td class="p-0">X = {{component.position.x}}</td>
|
||||||
|
<td class="p-0">Y = {{component.position.y}}</td>
|
||||||
|
<td class="p-0">Z = {{component.position.z}}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Online</th><td>{{component.online}}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
<SignalComponentView v-if="component.type === 'SIGNAL'" :signal="component" />
|
<SignalComponentView v-if="component.type === 'SIGNAL'" :signal="component" />
|
||||||
<SegmentBoundaryNodeComponentView v-if="component.type === 'SEGMENT_BOUNDARY'" :node="component" />
|
<SegmentBoundaryNodeComponentView v-if="component.type === 'SEGMENT_BOUNDARY'" :node="component" />
|
||||||
<PathNodeComponentView v-if="component.connectedNodes" :pathNode="component" :railSystem="railSystem" />
|
<PathNodeComponentView v-if="component.connectedNodes" :pathNode="component" :railSystem="railSystem" />
|
||||||
<button @click="rsStore.removeComponent(component.id)">Remove</button>
|
<button @click="removeComponent()" class="btn btn-sm btn-danger">Remove</button>
|
||||||
</div>
|
</div>
|
||||||
|
<ConfirmModal
|
||||||
|
ref="removeConfirm"
|
||||||
|
:id="'removeComponentModal'"
|
||||||
|
:title="'Remove Component'"
|
||||||
|
:message="'Are you sure you want to remove this component? It, and all associated data, will be permanently deleted.'"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -25,9 +44,11 @@ import SignalComponentView from "./SignalComponentView.vue";
|
||||||
import PathNodeComponentView from "./PathNodeComponentView.vue";
|
import PathNodeComponentView from "./PathNodeComponentView.vue";
|
||||||
import SegmentBoundaryNodeComponentView from "./SegmentBoundaryNodeComponentView.vue";
|
import SegmentBoundaryNodeComponentView from "./SegmentBoundaryNodeComponentView.vue";
|
||||||
import {useRailSystemsStore} from "../../../stores/railSystemsStore";
|
import {useRailSystemsStore} from "../../../stores/railSystemsStore";
|
||||||
|
import ConfirmModal from "../../ConfirmModal.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
ConfirmModal,
|
||||||
SegmentBoundaryNodeComponentView,
|
SegmentBoundaryNodeComponentView,
|
||||||
SignalComponentView,
|
SignalComponentView,
|
||||||
PathNodeComponentView
|
PathNodeComponentView
|
||||||
|
@ -47,13 +68,15 @@ export default {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
removeComponent() {
|
||||||
|
this.$refs.removeConfirm.showConfirm()
|
||||||
|
.then(() => this.rsStore.removeComponent(this.component.id));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.rs-component {
|
|
||||||
width: 20%;
|
|
||||||
border: 1px solid black;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
|
@ -1,22 +1,39 @@
|
||||||
<template>
|
<template>
|
||||||
<h5>Connected Nodes</h5>
|
<h5>Connected Nodes</h5>
|
||||||
<ul v-if="pathNode.connectedNodes.length > 0">
|
<table class="table" v-if="pathNode.connectedNodes.length > 0">
|
||||||
<li v-for="node in pathNode.connectedNodes" :key="node.id">
|
<thead>
|
||||||
{{node.id}} | {{node.name}}
|
<tr>
|
||||||
<button @click="rsStore.removeConnection(pathNode, node)">Remove</button>
|
<th>Name</th>
|
||||||
</li>
|
</tr>
|
||||||
</ul>
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="node in pathNode.connectedNodes" :key="node.id">
|
||||||
|
<td>{{node.name}}</td>
|
||||||
|
<td>
|
||||||
|
<button
|
||||||
|
@click="rsStore.removeConnection(pathNode, node)"
|
||||||
|
class="btn btn-sm btn-danger"
|
||||||
|
>
|
||||||
|
Remove
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
<p v-if="pathNode.connectedNodes.length === 0">
|
<p v-if="pathNode.connectedNodes.length === 0">
|
||||||
There are no connected nodes.
|
There are no connected nodes.
|
||||||
</p>
|
</p>
|
||||||
<form @submit.prevent="rsStore.addConnection(pathNode, formData.nodeToAdd)">
|
<form
|
||||||
<label for="pathNodeAddConnection">Add Connection</label>
|
@submit.prevent="rsStore.addConnection(pathNode, formData.nodeToAdd)"
|
||||||
<select id="pathNodeAddConnection" v-model="formData.nodeToAdd">
|
v-if="getEligibleConnections().length > 0"
|
||||||
|
class="input-group mb-3"
|
||||||
|
>
|
||||||
|
<select v-model="formData.nodeToAdd" class="form-select form-select-sm">
|
||||||
<option v-for="node in this.getEligibleConnections()" :key="node.id" :value="node">
|
<option v-for="node in this.getEligibleConnections()" :key="node.id" :value="node">
|
||||||
{{node.id}} | {{node.name}} | {{node.type}}
|
{{node.name}}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
<button type="submit">Add</button>
|
<button type="submit" class="btn btn-sm btn-success">Add Connection</button>
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,17 @@
|
||||||
<template>
|
<template>
|
||||||
<h5>Segments</h5>
|
<h5>Segments Connected</h5>
|
||||||
<ul>
|
<table class="table">
|
||||||
<li v-for="segment in node.segments" :key="segment.id">
|
<thead>
|
||||||
{{segment.id}} | {{segment.name}}
|
<tr>
|
||||||
</li>
|
<th>Name</th>
|
||||||
</ul>
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="segment in node.segments" :key="segment.id">
|
||||||
|
<td>{{segment.name}}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
<template>
|
<template>
|
||||||
<p>
|
<h5>Signal Properties</h5>
|
||||||
Connected segment: {{signal.segment.id}} {{signal.segment.name}}
|
<table class="table">
|
||||||
</p>
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>Connected to</th>
|
||||||
|
<td>{{signal.segment.name}}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
|
@ -11,22 +11,27 @@ export function drawComponent(ctx, worldTx, component) {
|
||||||
const s = getScaleFactor();
|
const s = getScaleFactor();
|
||||||
tx.scaleSelf(1/s, 1/s, 1/s);
|
tx.scaleSelf(1/s, 1/s, 1/s);
|
||||||
tx.scaleSelf(20, 20, 20);
|
tx.scaleSelf(20, 20, 20);
|
||||||
|
|
||||||
ctx.setTransform(tx.translate(0.75, -0.75));
|
|
||||||
drawOnlineIndicator(ctx, component);
|
|
||||||
|
|
||||||
ctx.setTransform(tx);
|
ctx.setTransform(tx);
|
||||||
|
|
||||||
// Draw hovered status.
|
|
||||||
if (isComponentHovered(component)) {
|
|
||||||
ctx.fillStyle = `rgba(255, 255, 0, 32)`;
|
|
||||||
circle(ctx, 0, 0, 0.75);
|
|
||||||
ctx.fill();
|
|
||||||
}
|
|
||||||
if (component.type === "SIGNAL") {
|
if (component.type === "SIGNAL") {
|
||||||
drawSignal(ctx, component);
|
drawSignal(ctx, component);
|
||||||
} else if (component.type === "SEGMENT_BOUNDARY") {
|
} else if (component.type === "SEGMENT_BOUNDARY") {
|
||||||
drawSegmentBoundary(ctx, component);
|
drawSegmentBoundary(ctx, component);
|
||||||
|
} else if (component.type === "SWITCH") {
|
||||||
|
drawSwitch(ctx, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.setTransform(tx.translate(0.75, -0.75));
|
||||||
|
if (component.online !== undefined && component.online !== null) {
|
||||||
|
drawOnlineIndicator(ctx, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.setTransform(tx);
|
||||||
|
// Draw hovered status.
|
||||||
|
if (isComponentHovered(component)) {
|
||||||
|
ctx.fillStyle = `rgba(255, 255, 0, 0.5)`;
|
||||||
|
circle(ctx, 0, 0, 0.75);
|
||||||
|
ctx.fill();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +40,7 @@ function drawSignal(ctx, signal) {
|
||||||
ctx.fillStyle = "black";
|
ctx.fillStyle = "black";
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
ctx.fillStyle = "rgb(0, 255, 0)";
|
ctx.fillStyle = "rgb(0, 255, 0)";
|
||||||
circle(ctx, 0, -0.2, 0.1);
|
circle(ctx, 0, -0.2, 0.15);
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,6 +55,50 @@ function drawSegmentBoundary(ctx, segmentBoundary) {
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function drawSwitch(ctx, sw) {
|
||||||
|
const colors = [
|
||||||
|
`rgba(61, 148, 66, 0.25)`,
|
||||||
|
`rgba(59, 22, 135, 0.25)`,
|
||||||
|
`rgba(145, 17, 90, 0.25)`,
|
||||||
|
`rgba(191, 49, 10, 0.25)`
|
||||||
|
];
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
for (let i = 0; i < sw.possibleConfigurations.length; i++) {
|
||||||
|
const config = sw.possibleConfigurations[i];
|
||||||
|
ctx.strokeStyle = colors[i];
|
||||||
|
for (let j = 0; j < config.nodes.length; j++) {
|
||||||
|
const node = config.nodes[j];
|
||||||
|
const diff = {
|
||||||
|
x: sw.position.x - node.position.x,
|
||||||
|
y: sw.position.z - node.position.z,
|
||||||
|
};
|
||||||
|
const mag = Math.sqrt(Math.pow(diff.x, 2) + Math.pow(diff.y, 2));
|
||||||
|
diff.x = 2 * -diff.x / mag;
|
||||||
|
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)`;
|
||||||
|
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 drawOnlineIndicator(ctx, component) {
|
function drawOnlineIndicator(ctx, component) {
|
||||||
ctx.lineWidth = 0.1;
|
ctx.lineWidth = 0.1;
|
||||||
if (component.online) {
|
if (component.online) {
|
||||||
|
@ -70,11 +119,7 @@ function drawOnlineIndicator(ctx, component) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function drawConnectedNodes(ctx, worldTx, component) {
|
export function drawConnectedNodes(ctx, worldTx, component) {
|
||||||
// const tx = DOMMatrix.fromMatrix(worldTx);
|
|
||||||
const s = getScaleFactor();
|
const s = getScaleFactor();
|
||||||
// tx.scaleSelf(1/s, 1/s, 1/s);
|
|
||||||
// tx.scaleSelf(20, 20, 20);
|
|
||||||
// ctx.setTransform(tx);
|
|
||||||
ctx.lineWidth = 5 / s;
|
ctx.lineWidth = 5 / s;
|
||||||
ctx.strokeStyle = "black";
|
ctx.strokeStyle = "black";
|
||||||
for (let i = 0; i < component.connectedNodes.length; i++) {
|
for (let i = 0; i < component.connectedNodes.length; i++) {
|
||||||
|
|
|
@ -9,6 +9,7 @@ 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,
|
||||||
const SCALE_INDEX_NORMAL = 7;
|
const SCALE_INDEX_NORMAL = 7;
|
||||||
const HOVER_RADIUS = 10;
|
const HOVER_RADIUS = 10;
|
||||||
|
|
||||||
|
let mapContainerDiv = null;
|
||||||
let mapCanvas = null;
|
let mapCanvas = null;
|
||||||
let railSystem = null;
|
let railSystem = null;
|
||||||
|
|
||||||
|
@ -24,6 +25,7 @@ export function initMap(rs) {
|
||||||
console.log("Initializing map for rail system: " + rs.name);
|
console.log("Initializing map for rail system: " + rs.name);
|
||||||
hoveredElements.length = 0;
|
hoveredElements.length = 0;
|
||||||
mapCanvas = document.getElementById("railSystemMapCanvas");
|
mapCanvas = document.getElementById("railSystemMapCanvas");
|
||||||
|
mapContainerDiv = document.getElementById("railSystemMapCanvasContainer");
|
||||||
mapCanvas.removeEventListener("wheel", onMouseWheel);
|
mapCanvas.removeEventListener("wheel", onMouseWheel);
|
||||||
mapCanvas.addEventListener("wheel", onMouseWheel);
|
mapCanvas.addEventListener("wheel", onMouseWheel);
|
||||||
mapCanvas.removeEventListener("mousedown", onMouseDown);
|
mapCanvas.removeEventListener("mousedown", onMouseDown);
|
||||||
|
@ -43,6 +45,12 @@ export function draw() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const ctx = mapCanvas.getContext("2d");
|
const ctx = mapCanvas.getContext("2d");
|
||||||
|
if (mapCanvas.width !== mapContainerDiv.clientWidth) {
|
||||||
|
mapCanvas.width = mapContainerDiv.clientWidth;
|
||||||
|
}
|
||||||
|
if (mapCanvas.height !== mapContainerDiv.clientHeight) {
|
||||||
|
mapCanvas.height = mapContainerDiv.clientHeight;
|
||||||
|
}
|
||||||
const width = mapCanvas.width;
|
const width = mapCanvas.width;
|
||||||
const height = mapCanvas.height;
|
const height = mapCanvas.height;
|
||||||
ctx.resetTransform();
|
ctx.resetTransform();
|
||||||
|
@ -51,6 +59,44 @@ export function draw() {
|
||||||
const worldTx = getWorldTransform();
|
const worldTx = getWorldTransform();
|
||||||
ctx.setTransform(worldTx);
|
ctx.setTransform(worldTx);
|
||||||
|
|
||||||
|
// Draw segments!
|
||||||
|
const segmentPoints = new Map();
|
||||||
|
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++) {
|
for (let i = 0; i < railSystem.components.length; i++) {
|
||||||
const c = railSystem.components[i];
|
const c = railSystem.components[i];
|
||||||
if (c.connectedNodes !== undefined && c.connectedNodes !== null) {
|
if (c.connectedNodes !== undefined && c.connectedNodes !== null) {
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import { createApp } from 'vue';
|
import { createApp } from 'vue';
|
||||||
import { createPinia } from 'pinia';
|
import { createPinia } from 'pinia';
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
|
import "bootstrap/dist/css/bootstrap.min.css";
|
||||||
|
import "bootstrap";
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App);
|
||||||
const pinia = createPinia();
|
const pinia = createPinia();
|
||||||
app.use(pinia);
|
app.use(pinia);
|
||||||
|
|
||||||
|
|
|
@ -26,9 +26,10 @@ export const useRailSystemsStore = defineStore('RailSystemsStore', {
|
||||||
createRailSystem(name) {
|
createRailSystem(name) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
axios.post(this.apiUrl + "/rs", {name: name})
|
axios.post(this.apiUrl + "/rs", {name: name})
|
||||||
.then(() => {
|
.then(response => {
|
||||||
|
const newId = response.data.id;
|
||||||
this.refreshRailSystems()
|
this.refreshRailSystems()
|
||||||
.then(() => resolve())
|
.then(() => resolve(this.railSystems.find(rs => rs.id === newId)))
|
||||||
.catch(error => reject(error));
|
.catch(error => reject(error));
|
||||||
})
|
})
|
||||||
.catch(error => reject(error));
|
.catch(error => reject(error));
|
||||||
|
@ -71,9 +72,15 @@ export const useRailSystemsStore = defineStore('RailSystemsStore', {
|
||||||
},
|
},
|
||||||
addSegment(name) {
|
addSegment(name) {
|
||||||
const rs = this.selectedRailSystem;
|
const rs = this.selectedRailSystem;
|
||||||
axios.post(`${this.apiUrl}/rs/${rs.id}/s`, {name: name})
|
return new Promise((resolve, reject) => {
|
||||||
.then(() => this.refreshSegments(rs))
|
axios.post(`${this.apiUrl}/rs/${rs.id}/s`, {name: name})
|
||||||
.catch(error => console.log(error));
|
.then(() => {
|
||||||
|
this.refreshSegments(rs)
|
||||||
|
.then(() => resolve())
|
||||||
|
.catch(error => reject(error));
|
||||||
|
})
|
||||||
|
.catch(error => reject(error));
|
||||||
|
});
|
||||||
},
|
},
|
||||||
removeSegment(id) {
|
removeSegment(id) {
|
||||||
const rs = this.selectedRailSystem;
|
const rs = this.selectedRailSystem;
|
||||||
|
@ -83,9 +90,15 @@ export const useRailSystemsStore = defineStore('RailSystemsStore', {
|
||||||
},
|
},
|
||||||
addComponent(data) {
|
addComponent(data) {
|
||||||
const rs = this.selectedRailSystem;
|
const rs = this.selectedRailSystem;
|
||||||
axios.post(`${this.apiUrl}/rs/${rs.id}/c`, data)
|
return new Promise((resolve, reject) => {
|
||||||
.then(() => this.refreshAllComponents(rs))
|
axios.post(`${this.apiUrl}/rs/${rs.id}/c`, data)
|
||||||
.catch(error => console.log(error));
|
.then(() => {
|
||||||
|
this.refreshAllComponents(rs)
|
||||||
|
.then(() => resolve())
|
||||||
|
.catch(error => reject(error));
|
||||||
|
})
|
||||||
|
.catch(error => reject(error));
|
||||||
|
});
|
||||||
},
|
},
|
||||||
removeComponent(id) {
|
removeComponent(id) {
|
||||||
const rs = this.selectedRailSystem;
|
const rs = this.selectedRailSystem;
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
package nl.andrewl.railsignalapi.dao;
|
||||||
|
|
||||||
|
import nl.andrewl.railsignalapi.model.Label;
|
||||||
|
import nl.andrewl.railsignalapi.model.RailSystem;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface LabelRepository extends JpaRepository<Label, Long> {
|
||||||
|
List<Label> findAllByRailSystem(RailSystem rs);
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package nl.andrewl.railsignalapi.model;
|
||||||
|
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import javax.persistence.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple label element that allows text to be placed in the rail system
|
||||||
|
* model.
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||||
|
@Getter
|
||||||
|
public class Label {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@ManyToOne(optional = false, fetch = FetchType.LAZY)
|
||||||
|
private RailSystem railSystem;
|
||||||
|
|
||||||
|
@Column(nullable = false, length = 63)
|
||||||
|
private String text;
|
||||||
|
|
||||||
|
public Label(RailSystem rs, String text) {
|
||||||
|
this.railSystem = rs;
|
||||||
|
this.text = text;
|
||||||
|
}
|
||||||
|
}
|
|
@ -35,9 +35,10 @@ public abstract class Component {
|
||||||
private Position position;
|
private Position position;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A human-readable name for the component.
|
* A human-readable name for the component. This must be unique among all
|
||||||
|
* components in the rail system.
|
||||||
*/
|
*/
|
||||||
@Column
|
@Column(nullable = false)
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -65,8 +65,11 @@ public class ComponentService {
|
||||||
pos.setY(data.get("position").get("y").asDouble());
|
pos.setY(data.get("position").get("y").asDouble());
|
||||||
pos.setZ(data.get("position").get("z").asDouble());
|
pos.setZ(data.get("position").get("z").asDouble());
|
||||||
String name = data.get("name").asText();
|
String name = data.get("name").asText();
|
||||||
|
if (name == null || name.isBlank()) {
|
||||||
|
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Missing required name.");
|
||||||
|
}
|
||||||
|
|
||||||
if (name != null && componentRepository.existsByNameAndRailSystem(name, rs)) {
|
if (componentRepository.existsByNameAndRailSystem(name, rs)) {
|
||||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Component with that name already exists.");
|
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Component with that name already exists.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,6 +107,9 @@ public class ComponentService {
|
||||||
}
|
}
|
||||||
s.getPossibleConfigurations().add(new SwitchConfiguration(s, pathNodes));
|
s.getPossibleConfigurations().add(new SwitchConfiguration(s, pathNodes));
|
||||||
}
|
}
|
||||||
|
if (s.getPossibleConfigurations().size() < 2) {
|
||||||
|
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "At least two switch configurations are needed.");
|
||||||
|
}
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|