Added modals, icon, and better formatting.
|
@ -8,8 +8,9 @@
|
|||
"name": "railsignal-app",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.11.5",
|
||||
"axios": "^0.27.2",
|
||||
"bootstrap": "^4.6.1",
|
||||
"bootstrap": "^5.1.3",
|
||||
"pinia": "^2.0.14",
|
||||
"three": "^0.140.0",
|
||||
"vue": "^3.2.33",
|
||||
|
@ -73,6 +74,15 @@
|
|||
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
|
||||
"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": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-2.3.2.tgz",
|
||||
|
@ -287,16 +297,15 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/bootstrap": {
|
||||
"version": "4.6.1",
|
||||
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.1.tgz",
|
||||
"integrity": "sha512-0dj+VgI9Ecom+rvvpNZ4MUZJz8dcX7WCX+eTID9+/8HgOkv3dsRzi8BGeZJCQU6flWQVYxwTQnEZFrmJSEO7og==",
|
||||
"version": "5.1.3",
|
||||
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.1.3.tgz",
|
||||
"integrity": "sha512-fcQztozJ8jToQWXxVuEyXWW+dSo8AiXWKwiSSrKWsRB/Qt+Ewwza+JWoLKiTuQLaEPhdNAJ7+Dosc9DOIqNy7Q==",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/bootstrap"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"jquery": "1.9.1 - 3",
|
||||
"popper.js": "^1.16.1"
|
||||
"@popperjs/core": "^2.10.2"
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
|
@ -1262,12 +1271,6 @@
|
|||
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
|
||||
"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": {
|
||||
"version": "4.1.0",
|
||||
"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": {
|
||||
"version": "8.4.13",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.13.tgz",
|
||||
|
@ -1978,6 +1970,11 @@
|
|||
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
|
||||
"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": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-2.3.2.tgz",
|
||||
|
@ -2162,9 +2159,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"bootstrap": {
|
||||
"version": "4.6.1",
|
||||
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.1.tgz",
|
||||
"integrity": "sha512-0dj+VgI9Ecom+rvvpNZ4MUZJz8dcX7WCX+eTID9+/8HgOkv3dsRzi8BGeZJCQU6flWQVYxwTQnEZFrmJSEO7og==",
|
||||
"version": "5.1.3",
|
||||
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.1.3.tgz",
|
||||
"integrity": "sha512-fcQztozJ8jToQWXxVuEyXWW+dSo8AiXWKwiSSrKWsRB/Qt+Ewwza+JWoLKiTuQLaEPhdNAJ7+Dosc9DOIqNy7Q==",
|
||||
"requires": {}
|
||||
},
|
||||
"brace-expansion": {
|
||||
|
@ -2781,12 +2778,6 @@
|
|||
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
|
||||
"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": {
|
||||
"version": "4.1.0",
|
||||
"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": {
|
||||
"version": "8.4.13",
|
||||
"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"
|
||||
},
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.11.5",
|
||||
"axios": "^0.27.2",
|
||||
"bootstrap": "^5.1.3",
|
||||
"pinia": "^2.0.14",
|
||||
"three": "^0.140.0",
|
||||
"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>
|
||||
<header>
|
||||
<h1>RailSignal</h1>
|
||||
</header>
|
||||
<RailSystemsManager />
|
||||
|
||||
<AppNavbar />
|
||||
<RailSystem v-if="rsStore.selectedRailSystem !== null" :railSystem="rsStore.selectedRailSystem"/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import RailSystem from "./components/RailSystem.vue";
|
||||
import RailSystemsManager from "./components/RailSystemsManager.vue";
|
||||
import {useRailSystemsStore} from "./stores/railSystemsStore";
|
||||
import AppNavbar from "./components/AppNavbar.vue";
|
||||
import RailSystem from "./components/RailSystem.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
RailSystem,
|
||||
RailSystemsManager
|
||||
AppNavbar,
|
||||
RailSystem
|
||||
},
|
||||
setup() {
|
||||
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>
|
||||
<h2>{{railSystem.name}}</h2>
|
||||
<div>
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-8 p-0">
|
||||
<MapView :railSystem="railSystem" v-if="railSystem.segments && railSystem.components" />
|
||||
<ComponentView v-if="railSystem.selectedComponent" :component="railSystem.selectedComponent" :railSystem="railSystem"/>
|
||||
</div>
|
||||
<SegmentsView />
|
||||
<AddSignal v-if="railSystem.segments && railSystem.segments.length > 0" />
|
||||
<AddSegmentBoundary v-if="railSystem.segments && railSystem.segments.length > 0" />
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MapView from './railsystem/MapView.vue'
|
||||
import ComponentView from './railsystem/component/ComponentView.vue'
|
||||
import SegmentsView from "./railsystem/SegmentsView.vue";
|
||||
import AddSignal from "./railsystem/component/AddSignal.vue";
|
||||
import AddSegmentBoundary from "./railsystem/component/AddSegmentBoundary.vue";
|
||||
import RailSystemPropertiesView from "./railsystem/RailSystemPropertiesView.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
AddSignal,
|
||||
AddSegmentBoundary,
|
||||
SegmentsView,
|
||||
RailSystemPropertiesView,
|
||||
MapView,
|
||||
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>
|
||||
import {initMap} from "./mapRenderer.js";
|
||||
import {initMap, draw} from "./mapRenderer.js";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
|
@ -15,18 +15,33 @@ export default {
|
|||
updated() {
|
||||
// Also, re-initialize any time this view is updated.
|
||||
initMap(this.railSystem);
|
||||
},
|
||||
watch: {
|
||||
railSystem: {
|
||||
handler() {
|
||||
draw();
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<canvas id="railSystemMapCanvas" width="1000" height="600">
|
||||
<div class="canvas-container" id="railSystemMapCanvasContainer">
|
||||
<canvas id="railSystemMapCanvas">
|
||||
Your browser doesn't support canvas!
|
||||
</canvas>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.canvas-container {
|
||||
width: 100%;
|
||||
height: 800px;
|
||||
}
|
||||
canvas {
|
||||
border: 1px solid black;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</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>
|
||||
<h3>Segments</h3>
|
||||
<ul>
|
||||
<li v-for="segment in rsStore.selectedRailSystem.segments" :key="segment.id">
|
||||
{{segment.name}} <button @click.prevent="rsStore.removeSegment(segment.id)">Remove</button>
|
||||
<h5>Segments</h5>
|
||||
<ul class="list-group overflow-auto mb-2" style="max-height: 200px;">
|
||||
<li
|
||||
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>
|
||||
</ul>
|
||||
<AddSegment />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AddSegment from "./AddSegment.vue";
|
||||
import {useRailSystemsStore} from "../../stores/railSystemsStore";
|
||||
|
||||
export default {
|
||||
name: "SegmentsView.vue",
|
||||
components: {
|
||||
AddSegment
|
||||
},
|
||||
setup() {
|
||||
const rsStore = useRailSystemsStore();
|
||||
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>
|
||||
<div class="rs-component">
|
||||
<div>
|
||||
<h3>{{component.name}}</h3>
|
||||
<p>
|
||||
Id: {{component.id}}
|
||||
</p>
|
||||
<p>
|
||||
Position: (x = {{component.position.x}}, y = {{component.position.y}}, z = {{component.position.z}})
|
||||
</p>
|
||||
<p>
|
||||
Type: {{component.type}}
|
||||
</p>
|
||||
<p>
|
||||
Online: {{component.online}}
|
||||
</p>
|
||||
<small class="text-muted">{{component.type}}</small>
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Id</th><td>{{component.id}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Position</th>
|
||||
<td>
|
||||
<table class="table table-borderless m-0 p-0">
|
||||
<tbody>
|
||||
<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" />
|
||||
<SegmentBoundaryNodeComponentView v-if="component.type === 'SEGMENT_BOUNDARY'" :node="component" />
|
||||
<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>
|
||||
<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>
|
||||
|
||||
<script>
|
||||
|
@ -25,9 +44,11 @@ import SignalComponentView from "./SignalComponentView.vue";
|
|||
import PathNodeComponentView from "./PathNodeComponentView.vue";
|
||||
import SegmentBoundaryNodeComponentView from "./SegmentBoundaryNodeComponentView.vue";
|
||||
import {useRailSystemsStore} from "../../../stores/railSystemsStore";
|
||||
import ConfirmModal from "../../ConfirmModal.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ConfirmModal,
|
||||
SegmentBoundaryNodeComponentView,
|
||||
SignalComponentView,
|
||||
PathNodeComponentView
|
||||
|
@ -47,13 +68,15 @@ export default {
|
|||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
removeComponent() {
|
||||
this.$refs.removeConfirm.showConfirm()
|
||||
.then(() => this.rsStore.removeComponent(this.component.id));
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.rs-component {
|
||||
width: 20%;
|
||||
border: 1px solid black;
|
||||
}
|
||||
</style>
|
|
@ -1,22 +1,39 @@
|
|||
<template>
|
||||
<h5>Connected Nodes</h5>
|
||||
<ul v-if="pathNode.connectedNodes.length > 0">
|
||||
<li v-for="node in pathNode.connectedNodes" :key="node.id">
|
||||
{{node.id}} | {{node.name}}
|
||||
<button @click="rsStore.removeConnection(pathNode, node)">Remove</button>
|
||||
</li>
|
||||
</ul>
|
||||
<table class="table" v-if="pathNode.connectedNodes.length > 0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
</tr>
|
||||
</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">
|
||||
There are no connected nodes.
|
||||
</p>
|
||||
<form @submit.prevent="rsStore.addConnection(pathNode, formData.nodeToAdd)">
|
||||
<label for="pathNodeAddConnection">Add Connection</label>
|
||||
<select id="pathNodeAddConnection" v-model="formData.nodeToAdd">
|
||||
<form
|
||||
@submit.prevent="rsStore.addConnection(pathNode, 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">
|
||||
{{node.id}} | {{node.name}} | {{node.type}}
|
||||
{{node.name}}
|
||||
</option>
|
||||
</select>
|
||||
<button type="submit">Add</button>
|
||||
<button type="submit" class="btn btn-sm btn-success">Add Connection</button>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -1,10 +1,17 @@
|
|||
<template>
|
||||
<h5>Segments</h5>
|
||||
<ul>
|
||||
<li v-for="segment in node.segments" :key="segment.id">
|
||||
{{segment.id}} | {{segment.name}}
|
||||
</li>
|
||||
</ul>
|
||||
<h5>Segments Connected</h5>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="segment in node.segments" :key="segment.id">
|
||||
<td>{{segment.name}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
<template>
|
||||
<p>
|
||||
Connected segment: {{signal.segment.id}} {{signal.segment.name}}
|
||||
</p>
|
||||
<h5>Signal Properties</h5>
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Connected to</th>
|
||||
<td>{{signal.segment.name}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
|
|
@ -11,22 +11,27 @@ export function drawComponent(ctx, worldTx, component) {
|
|||
const s = getScaleFactor();
|
||||
tx.scaleSelf(1/s, 1/s, 1/s);
|
||||
tx.scaleSelf(20, 20, 20);
|
||||
|
||||
ctx.setTransform(tx.translate(0.75, -0.75));
|
||||
drawOnlineIndicator(ctx, component);
|
||||
|
||||
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") {
|
||||
drawSignal(ctx, component);
|
||||
} else if (component.type === "SEGMENT_BOUNDARY") {
|
||||
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.fill();
|
||||
ctx.fillStyle = "rgb(0, 255, 0)";
|
||||
circle(ctx, 0, -0.2, 0.1);
|
||||
circle(ctx, 0, -0.2, 0.15);
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
|
@ -50,6 +55,50 @@ function drawSegmentBoundary(ctx, segmentBoundary) {
|
|||
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) {
|
||||
ctx.lineWidth = 0.1;
|
||||
if (component.online) {
|
||||
|
@ -70,11 +119,7 @@ function drawOnlineIndicator(ctx, component) {
|
|||
}
|
||||
|
||||
export function drawConnectedNodes(ctx, worldTx, component) {
|
||||
// const tx = DOMMatrix.fromMatrix(worldTx);
|
||||
const s = getScaleFactor();
|
||||
// tx.scaleSelf(1/s, 1/s, 1/s);
|
||||
// tx.scaleSelf(20, 20, 20);
|
||||
// ctx.setTransform(tx);
|
||||
ctx.lineWidth = 5 / s;
|
||||
ctx.strokeStyle = "black";
|
||||
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 HOVER_RADIUS = 10;
|
||||
|
||||
let mapContainerDiv = null;
|
||||
let mapCanvas = null;
|
||||
let railSystem = null;
|
||||
|
||||
|
@ -24,6 +25,7 @@ export function initMap(rs) {
|
|||
console.log("Initializing map for rail system: " + rs.name);
|
||||
hoveredElements.length = 0;
|
||||
mapCanvas = document.getElementById("railSystemMapCanvas");
|
||||
mapContainerDiv = document.getElementById("railSystemMapCanvasContainer");
|
||||
mapCanvas.removeEventListener("wheel", onMouseWheel);
|
||||
mapCanvas.addEventListener("wheel", onMouseWheel);
|
||||
mapCanvas.removeEventListener("mousedown", onMouseDown);
|
||||
|
@ -43,6 +45,12 @@ export function draw() {
|
|||
return;
|
||||
}
|
||||
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 height = mapCanvas.height;
|
||||
ctx.resetTransform();
|
||||
|
@ -51,6 +59,44 @@ export function draw() {
|
|||
const worldTx = getWorldTransform();
|
||||
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++) {
|
||||
const c = railSystem.components[i];
|
||||
if (c.connectedNodes !== undefined && c.connectedNodes !== null) {
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import { createApp } from 'vue';
|
||||
import { createPinia } from 'pinia';
|
||||
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();
|
||||
app.use(pinia);
|
||||
|
||||
|
|
|
@ -26,9 +26,10 @@ export const useRailSystemsStore = defineStore('RailSystemsStore', {
|
|||
createRailSystem(name) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios.post(this.apiUrl + "/rs", {name: name})
|
||||
.then(() => {
|
||||
.then(response => {
|
||||
const newId = response.data.id;
|
||||
this.refreshRailSystems()
|
||||
.then(() => resolve())
|
||||
.then(() => resolve(this.railSystems.find(rs => rs.id === newId)))
|
||||
.catch(error => reject(error));
|
||||
})
|
||||
.catch(error => reject(error));
|
||||
|
@ -71,9 +72,15 @@ export const useRailSystemsStore = defineStore('RailSystemsStore', {
|
|||
},
|
||||
addSegment(name) {
|
||||
const rs = this.selectedRailSystem;
|
||||
return new Promise((resolve, reject) => {
|
||||
axios.post(`${this.apiUrl}/rs/${rs.id}/s`, {name: name})
|
||||
.then(() => this.refreshSegments(rs))
|
||||
.catch(error => console.log(error));
|
||||
.then(() => {
|
||||
this.refreshSegments(rs)
|
||||
.then(() => resolve())
|
||||
.catch(error => reject(error));
|
||||
})
|
||||
.catch(error => reject(error));
|
||||
});
|
||||
},
|
||||
removeSegment(id) {
|
||||
const rs = this.selectedRailSystem;
|
||||
|
@ -83,9 +90,15 @@ export const useRailSystemsStore = defineStore('RailSystemsStore', {
|
|||
},
|
||||
addComponent(data) {
|
||||
const rs = this.selectedRailSystem;
|
||||
return new Promise((resolve, reject) => {
|
||||
axios.post(`${this.apiUrl}/rs/${rs.id}/c`, data)
|
||||
.then(() => this.refreshAllComponents(rs))
|
||||
.catch(error => console.log(error));
|
||||
.then(() => {
|
||||
this.refreshAllComponents(rs)
|
||||
.then(() => resolve())
|
||||
.catch(error => reject(error));
|
||||
})
|
||||
.catch(error => reject(error));
|
||||
});
|
||||
},
|
||||
removeComponent(id) {
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
|
|
|
@ -65,8 +65,11 @@ public class ComponentService {
|
|||
pos.setY(data.get("position").get("y").asDouble());
|
||||
pos.setZ(data.get("position").get("z").asDouble());
|
||||
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.");
|
||||
}
|
||||
|
||||
|
@ -104,6 +107,9 @@ public class ComponentService {
|
|||
}
|
||||
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;
|
||||
}
|
||||
|
||||
|
|