added stuff
This commit is contained in:
parent
e608e2ba8c
commit
3ac886feeb
|
@ -9,6 +9,7 @@
|
|||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"axios": "^0.27.2",
|
||||
"bootstrap": "^4.6.1",
|
||||
"pinia": "^2.0.14",
|
||||
"three": "^0.140.0",
|
||||
"vue": "^3.2.33",
|
||||
|
@ -285,6 +286,19 @@
|
|||
"integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=",
|
||||
"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==",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/bootstrap"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"jquery": "1.9.1 - 3",
|
||||
"popper.js": "^1.16.1"
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
|
@ -1248,6 +1262,12 @@
|
|||
"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",
|
||||
|
@ -1500,6 +1520,17 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"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",
|
||||
|
@ -2130,6 +2161,12 @@
|
|||
"integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=",
|
||||
"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==",
|
||||
"requires": {}
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
|
@ -2744,6 +2781,12 @@
|
|||
"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",
|
||||
|
@ -2924,6 +2967,12 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"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",
|
||||
|
|
|
@ -4,10 +4,7 @@
|
|||
</header>
|
||||
<RailSystemsManager />
|
||||
|
||||
<RailSystem
|
||||
v-if="rsStore.selectedRailSystem !== null"
|
||||
:railSystem="rsStore.selectedRailSystem"
|
||||
/>
|
||||
<RailSystem v-if="rsStore.selectedRailSystem !== null" :railSystem="rsStore.selectedRailSystem"/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
|
|
@ -1,28 +1,34 @@
|
|||
<template>
|
||||
<h2>{{railSystem.name}}</h2>
|
||||
<RsMap :railSystem="railSystem" />
|
||||
<RsComponent v-if="selectedComponent !== null" :component="selectedComponent" />
|
||||
<div>
|
||||
<MapView :railSystem="railSystem" v-if="railSystem.segments && railSystem.components" />
|
||||
<ComponentView v-if="railSystem.selectedComponent" :component="railSystem.selectedComponent"/>
|
||||
</div>
|
||||
<SegmentsView />
|
||||
<AddSignal v-if="railSystem.segments && railSystem.segments.length > 0" />
|
||||
<AddSegmentBoundary v-if="railSystem.segments && railSystem.segments.length > 0" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import RsMap from './railsystem/MapView.vue'
|
||||
import RsComponent from './railsystem/Component.vue'
|
||||
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";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
RsMap,
|
||||
RsComponent
|
||||
AddSignal,
|
||||
AddSegmentBoundary,
|
||||
SegmentsView,
|
||||
MapView,
|
||||
ComponentView
|
||||
},
|
||||
props: {
|
||||
railSystem: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedComponent: null
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -26,6 +26,12 @@ 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
|
||||
};
|
||||
|
|
|
@ -1,14 +1,23 @@
|
|||
<template>
|
||||
<form>
|
||||
<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>Add</button>
|
||||
<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: {
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
<script>
|
||||
export default {
|
||||
props: {
|
||||
component: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="rs-component">
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.rs-component {
|
||||
width: 20%;
|
||||
border: 1px solid black;
|
||||
}
|
||||
</style>
|
|
@ -1,4 +1,6 @@
|
|||
<script>
|
||||
import {initMap} from "./mapRenderer.js";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
railSystem: {
|
||||
|
@ -6,14 +8,19 @@ export default {
|
|||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
mounted() {
|
||||
// The first time this map is mounted, initialize the map.
|
||||
initMap(this.railSystem);
|
||||
},
|
||||
updated() {
|
||||
// Also, re-initialize any time this view is updated.
|
||||
initMap(this.railSystem);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<canvas>
|
||||
<canvas id="railSystemMapCanvas" width="1000" height="600">
|
||||
Your browser doesn't support canvas!
|
||||
</canvas>
|
||||
</template>
|
||||
|
@ -21,6 +28,5 @@ export default {
|
|||
<style>
|
||||
canvas {
|
||||
border: 1px solid black;
|
||||
width: 70%;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,31 @@
|
|||
<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>
|
||||
</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 {
|
||||
rsStore
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,76 @@
|
|||
<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,62 @@
|
|||
<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,55 @@
|
|||
<template>
|
||||
<div class="rs-component">
|
||||
<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>
|
||||
<SignalComponentView v-if="component.type === 'SIGNAL'" :signal="component" />
|
||||
<SegmentBoundaryNodeComponentView v-if="component.type === 'SEGMENT_BOUNDARY'" :node="component" />
|
||||
<PathNodeComponentView v-if="component.connectedNodes" :pathNode="component" />
|
||||
<button @click="rsStore.removeComponent(component.id)">Remove</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {useRailSystemsStore} from "../../../stores/railSystemsStore";
|
||||
import SignalComponentView from "./SignalComponentView.vue";
|
||||
import PathNodeComponentView from "./PathNodeComponentView.vue";
|
||||
import SegmentBoundaryNodeComponentView from "./SegmentBoundaryNodeComponentView.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
SegmentBoundaryNodeComponentView,
|
||||
SignalComponentView,
|
||||
PathNodeComponentView
|
||||
},
|
||||
setup() {
|
||||
const rsStore = useRailSystemsStore();
|
||||
return {
|
||||
rsStore
|
||||
};
|
||||
},
|
||||
props: {
|
||||
component: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.rs-component {
|
||||
width: 20%;
|
||||
border: 1px solid black;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,27 @@
|
|||
<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}}
|
||||
</li>
|
||||
</ul>
|
||||
<p v-if="pathNode.connectedNodes.length === 0">
|
||||
There are no connected nodes.
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "PathNodeComponentView",
|
||||
props: {
|
||||
pathNode: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,24 @@
|
|||
<template>
|
||||
<h5>Segments</h5>
|
||||
<ul>
|
||||
<li v-for="segment in node.segments" :key="segment.id">
|
||||
{{segment.id}} | {{segment.name}}
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "SegmentBoundaryNodeComponentView",
|
||||
props: {
|
||||
node: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,21 @@
|
|||
<template>
|
||||
<p>
|
||||
Connected segment: {{signal.segment.id}} {{signal.segment.name}}
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "SignalComponentView",
|
||||
props: {
|
||||
signal: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,248 @@
|
|||
/*
|
||||
This component is responsible for the rendering of a RailSystem in a 2d map
|
||||
view.
|
||||
*/
|
||||
|
||||
const SCALE_VALUES = [0.01, 0.1, 0.25, 0.5, 1.0, 1.25, 1.5, 2.0, 3.0, 4.0, 6.0, 8.0, 10.0, 12.0, 16.0, 20.0, 30.0, 45.0, 60.0, 80.0, 100.0];
|
||||
const SCALE_INDEX_NORMAL = 7;
|
||||
const HOVER_RADIUS = 10;
|
||||
|
||||
let mapCanvas = null;
|
||||
let railSystem = null;
|
||||
|
||||
let mapScaleIndex = SCALE_INDEX_NORMAL;
|
||||
let mapTranslation = {x: 0, y: 0};
|
||||
let mapDragOrigin = null;
|
||||
let mapDragTranslation = null;
|
||||
let lastMousePoint = new DOMPoint(0, 0, 0, 0);
|
||||
const hoveredElements = [];
|
||||
|
||||
export function initMap(rs) {
|
||||
railSystem = rs;
|
||||
console.log("Initializing map for rail system: " + rs.name);
|
||||
hoveredElements.length = 0;
|
||||
mapCanvas = document.getElementById("railSystemMapCanvas");
|
||||
mapCanvas.removeEventListener("wheel", onMouseWheel);
|
||||
mapCanvas.addEventListener("wheel", onMouseWheel);
|
||||
mapCanvas.removeEventListener("mousedown", onMouseDown);
|
||||
mapCanvas.addEventListener("mousedown", onMouseDown);
|
||||
mapCanvas.removeEventListener("mouseup", onMouseUp);
|
||||
mapCanvas.addEventListener("mouseup", onMouseUp);
|
||||
mapCanvas.removeEventListener("mousemove", onMouseMove);
|
||||
mapCanvas.addEventListener("mousemove", onMouseMove);
|
||||
|
||||
// Do an initial draw.
|
||||
draw();
|
||||
}
|
||||
|
||||
export function draw() {
|
||||
if (!(mapCanvas && railSystem && railSystem.components)) {
|
||||
console.warn("Attempted to draw map without canvas or railSystem.");
|
||||
return;
|
||||
}
|
||||
const ctx = mapCanvas.getContext("2d");
|
||||
const width = mapCanvas.width;
|
||||
const height = mapCanvas.height;
|
||||
ctx.resetTransform();
|
||||
ctx.fillStyle = `rgb(240, 240, 240)`;
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
const worldTx = getWorldTransform();
|
||||
ctx.setTransform(worldTx);
|
||||
|
||||
for (let i = 0; i < railSystem.components.length; i++) {
|
||||
drawComponent(ctx, worldTx, railSystem.components[i]);
|
||||
}
|
||||
|
||||
// Draw debug info.
|
||||
ctx.resetTransform();
|
||||
ctx.fillStyle = "black";
|
||||
ctx.strokeStyle = "black";
|
||||
ctx.font = "10px Sans-Serif";
|
||||
const lastWorldPoint = mapPointToWorld(lastMousePoint);
|
||||
const lines = [
|
||||
"Scale factor: " + getScaleFactor(),
|
||||
`(x = ${lastWorldPoint.x.toFixed(2)}, y = ${lastWorldPoint.y.toFixed(2)}, z = ${lastWorldPoint.z.toFixed(2)})`,
|
||||
`Components: ${railSystem.components.length}`,
|
||||
`Hovered elements: ${hoveredElements.length}`
|
||||
]
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
ctx.fillText(lines[i], 10, 20 + (i * 15));
|
||||
}
|
||||
}
|
||||
|
||||
function drawComponent(ctx, worldTx, component) {
|
||||
const tx = DOMMatrix.fromMatrix(worldTx);
|
||||
tx.translateSelf(component.position.x, component.position.z, 0);
|
||||
const s = getScaleFactor();
|
||||
tx.scaleSelf(1/s, 1/s, 1/s);
|
||||
tx.scaleSelf(5, 5, 5);
|
||||
ctx.setTransform(tx);
|
||||
if (isComponentHovered(component)) {
|
||||
ctx.fillStyle = `rgba(255, 255, 0, 32)`;
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(0, 0, 1.8, 1.8, 0, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
}
|
||||
if (component.type === "SIGNAL") {
|
||||
drawSignal(ctx, component);
|
||||
} else if (component.type === "SEGMENT_BOUNDARY") {
|
||||
drawSegmentBoundary(ctx, component);
|
||||
}
|
||||
}
|
||||
|
||||
function drawSignal(ctx, signal) {
|
||||
roundedRect(ctx, -0.7, -1, 1.4, 2, 0.25);
|
||||
ctx.fillStyle = "black";
|
||||
ctx.fill();
|
||||
|
||||
// ctx.fillStyle = "green";
|
||||
// ctx.beginPath();
|
||||
// ctx.ellipse(0, 0, 0.8, 0.8, 0, 0, Math.PI * 2);
|
||||
// ctx.fill();
|
||||
//
|
||||
// ctx.strokeStyle = "black";
|
||||
// ctx.lineWidth = 0.5;
|
||||
// ctx.beginPath();
|
||||
// ctx.ellipse(0, 0, 1, 1, 0, 0, Math.PI * 2);
|
||||
// ctx.stroke();
|
||||
}
|
||||
|
||||
function drawSegmentBoundary(ctx, segmentBoundary) {
|
||||
ctx.fillStyle = "blue";
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(0, 0, 1, 1, 0, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
export function getScaleFactor() {
|
||||
return SCALE_VALUES[mapScaleIndex];
|
||||
}
|
||||
|
||||
function getWorldTransform() {
|
||||
const canvasRect = mapCanvas.getBoundingClientRect();
|
||||
const scale = getScaleFactor();
|
||||
const tx = new DOMMatrix();
|
||||
tx.translateSelf(canvasRect.width / 2, canvasRect.height / 2, 0);
|
||||
tx.scaleSelf(scale, scale, scale);
|
||||
tx.translateSelf(mapTranslation.x, mapTranslation.y, 0);
|
||||
if (mapDragOrigin !== null && mapDragTranslation !== null) {
|
||||
tx.translateSelf(mapDragTranslation.x, mapDragTranslation.y, 0);
|
||||
}
|
||||
return tx;
|
||||
}
|
||||
|
||||
function isComponentHovered(component) {
|
||||
for (let i = 0; i < hoveredElements.length; i++) {
|
||||
if (hoveredElements[i].id === component.id) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a point on the map coordinates to world coordinates.
|
||||
* @param {DOMPoint} p
|
||||
* @returns {DOMPoint}
|
||||
*/
|
||||
function mapPointToWorld(p) {
|
||||
return getWorldTransform().invertSelf().transformPoint(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a point in the world to map coordinates.
|
||||
* @param {DOMPoint} p
|
||||
* @returns {DOMPoint}
|
||||
*/
|
||||
function worldPointToMap(p) {
|
||||
return getWorldTransform().transformPoint(p);
|
||||
}
|
||||
|
||||
function roundedRect(ctx, x, y, w, h, r) {
|
||||
if (w < 2 * r) r = w / 2;
|
||||
if (h < 2 * r) r = h / 2;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x+r, y);
|
||||
ctx.arcTo(x+w, y, x+w, y+h, r);
|
||||
ctx.arcTo(x+w, y+h, x, y+h, r);
|
||||
ctx.arcTo(x, y+h, x, y, r);
|
||||
ctx.arcTo(x, y, x+w, y, r);
|
||||
ctx.closePath();
|
||||
}
|
||||
|
||||
/*
|
||||
EVENT HANDLING
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {WheelEvent} event
|
||||
*/
|
||||
function onMouseWheel(event) {
|
||||
const s = event.deltaY;
|
||||
if (s > 0) {
|
||||
mapScaleIndex = Math.max(0, mapScaleIndex - 1);
|
||||
} else if (s < 0) {
|
||||
mapScaleIndex = Math.min(SCALE_VALUES.length - 1, mapScaleIndex + 1);
|
||||
}
|
||||
draw();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MouseEvent} event
|
||||
*/
|
||||
function onMouseDown(event) {
|
||||
const p = getMousePoint(event);
|
||||
mapDragOrigin = {x: p.x, y: p.y};
|
||||
}
|
||||
|
||||
function onMouseUp() {
|
||||
if (mapDragTranslation !== null) {
|
||||
mapTranslation.x += mapDragTranslation.x;
|
||||
mapTranslation.y += mapDragTranslation.y;
|
||||
}
|
||||
if (hoveredElements.length === 1) {
|
||||
railSystem.selectedComponent = hoveredElements[0];
|
||||
} else {
|
||||
railSystem.selectedComponent = null;
|
||||
}
|
||||
mapDragOrigin = null;
|
||||
mapDragTranslation = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MouseEvent} event
|
||||
*/
|
||||
function onMouseMove(event) {
|
||||
const p = getMousePoint(event);
|
||||
lastMousePoint = p;
|
||||
if (mapDragOrigin !== null) {
|
||||
const scale = getScaleFactor();
|
||||
const dx = p.x - mapDragOrigin.x;
|
||||
const dy = p.y - mapDragOrigin.y;
|
||||
mapDragTranslation = {x: dx / scale, y: dy / scale};
|
||||
} else {
|
||||
hoveredElements.length = 0;
|
||||
// Populate with list of hovered elements.
|
||||
for (let i = 0; i < railSystem.components.length; i++) {
|
||||
const c = railSystem.components[i];
|
||||
const componentPoint = new DOMPoint(c.position.x, c.position.z, 0, 1);
|
||||
const mapComponentPoint = worldPointToMap(componentPoint);
|
||||
const dist2 = (p.x - mapComponentPoint.x) * (p.x - mapComponentPoint.x) + (p.y - mapComponentPoint.y) * (p.y - mapComponentPoint.y);
|
||||
if (dist2 < HOVER_RADIUS * HOVER_RADIUS) {
|
||||
hoveredElements.push(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
draw();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the point at which the user clicked on the map.
|
||||
* @param {MouseEvent} event
|
||||
* @returns {DOMPoint}
|
||||
*/
|
||||
function getMousePoint(event) {
|
||||
const rect = mapCanvas.getBoundingClientRect();
|
||||
const x = event.clientX - rect.left;
|
||||
const y = event.clientY - rect.top;
|
||||
return new DOMPoint(x, y, 0, 1);
|
||||
}
|
|
@ -4,13 +4,16 @@ import axios from "axios";
|
|||
export const useRailSystemsStore = defineStore('RailSystemsStore', {
|
||||
state: () => ({
|
||||
railSystems: [],
|
||||
/**
|
||||
* @type {{segments: [Object], components: [Object], selectedComponent: Object} | null}
|
||||
*/
|
||||
selectedRailSystem: null,
|
||||
selectedComponent: null
|
||||
apiUrl: import.meta.env.VITE_API_URL
|
||||
}),
|
||||
actions: {
|
||||
refreshRailSystems() {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios.get(import.meta.env.VITE_API_URL + "/rs")
|
||||
axios.get(this.apiUrl + "/rs")
|
||||
.then(response => {
|
||||
this.railSystems = response.data;
|
||||
resolve();
|
||||
|
@ -22,10 +25,7 @@ export const useRailSystemsStore = defineStore('RailSystemsStore', {
|
|||
},
|
||||
createRailSystem(name) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios.post(
|
||||
import.meta.env.VITE_API_URL + "/rs",
|
||||
{name: name}
|
||||
)
|
||||
axios.post(this.apiUrl + "/rs", {name: name})
|
||||
.then(() => {
|
||||
this.refreshRailSystems()
|
||||
.then(() => resolve())
|
||||
|
@ -36,13 +36,69 @@ export const useRailSystemsStore = defineStore('RailSystemsStore', {
|
|||
},
|
||||
removeRailSystem(rs) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios.delete(import.meta.env.VITE_API_URL + "/rs/" + rs.id)
|
||||
axios.delete(this.apiUrl + "/rs/" + rs.id)
|
||||
.then(() => {
|
||||
this.selectedRailSystem = null;
|
||||
this.refreshRailSystems()
|
||||
.then(() => resolve)
|
||||
.catch(error => reject(error));
|
||||
})
|
||||
})
|
||||
},
|
||||
refreshSegments(rs) {
|
||||
return new Promise(resolve => {
|
||||
axios.get(`${this.apiUrl}/rs/${rs.id}/s`)
|
||||
.then(response => {
|
||||
rs.segments = response.data;
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
},
|
||||
refreshComponents(rs) {
|
||||
return new Promise(resolve => {
|
||||
axios.get(`${this.apiUrl}/rs/${rs.id}/c`)
|
||||
.then(response => {
|
||||
rs.components = response.data;
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
},
|
||||
fetchSelectedRailSystemData() {
|
||||
if (!this.selectedRailSystem) return;
|
||||
this.refreshSegments(this.selectedRailSystem);
|
||||
this.refreshComponents(this.selectedRailSystem);
|
||||
},
|
||||
addSegment(name) {
|
||||
const rs = this.selectedRailSystem;
|
||||
axios.post(`${this.apiUrl}/rs/${rs.id}/s`, {name: name})
|
||||
.then(() => this.refreshSegments(rs))
|
||||
.catch(error => console.log(error));
|
||||
},
|
||||
removeSegment(id) {
|
||||
const rs = this.selectedRailSystem;
|
||||
axios.delete(`${this.apiUrl}/rs/${rs.id}/s/${id}`)
|
||||
.then(() => this.refreshSegments(rs))
|
||||
.catch(error => console.log(error));
|
||||
},
|
||||
addComponent(data) {
|
||||
const rs = this.selectedRailSystem;
|
||||
axios.post(`${this.apiUrl}/rs/${rs.id}/c`, data)
|
||||
.then(() => this.refreshComponents(rs))
|
||||
.catch(error => console.log(error));
|
||||
},
|
||||
removeComponent(id) {
|
||||
const rs = this.selectedRailSystem;
|
||||
axios.delete(`${this.apiUrl}/rs/${rs.id}/c/${id}`)
|
||||
.then(() => this.refreshComponents(rs))
|
||||
.catch(error => console.log(error));
|
||||
},
|
||||
fetchComponentData(component) {
|
||||
return new Promise(resolve => {
|
||||
const rs = this.selectedRailSystem;
|
||||
axios.get(`${this.apiUrl}/rs/${rs.id}/c/${component.id}`)
|
||||
.then(response => resolve(response.data))
|
||||
.catch(error => console.log(error));
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
|
@ -2,8 +2,8 @@ package nl.andrewl.railsignalapi.rest;
|
|||
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import nl.andrewl.railsignalapi.rest.dto.component.ComponentResponse;
|
||||
import nl.andrewl.railsignalapi.rest.dto.component.SimpleComponentResponse;
|
||||
import nl.andrewl.railsignalapi.rest.dto.PathNodeUpdatePayload;
|
||||
import nl.andrewl.railsignalapi.rest.dto.component.out.ComponentResponse;
|
||||
import nl.andrewl.railsignalapi.service.ComponentService;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
@ -17,7 +17,7 @@ public class ComponentsApiController {
|
|||
private final ComponentService componentService;
|
||||
|
||||
@GetMapping
|
||||
public List<SimpleComponentResponse> getAllComponents(@PathVariable long rsId) {
|
||||
public List<ComponentResponse> getAllComponents(@PathVariable long rsId) {
|
||||
return componentService.getComponents(rsId);
|
||||
}
|
||||
|
||||
|
@ -36,4 +36,9 @@ public class ComponentsApiController {
|
|||
componentService.removeComponent(rsId, cId);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
@PatchMapping(path = "/{cId}/connectedNodes")
|
||||
public ComponentResponse updateConnectedNodes(@PathVariable long rsId, @PathVariable long cId, @RequestBody PathNodeUpdatePayload payload) {
|
||||
return componentService.updatePath(rsId, cId, payload);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,10 @@ package nl.andrewl.railsignalapi.rest;
|
|||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import nl.andrewl.railsignalapi.rest.dto.FullSegmentResponse;
|
||||
import nl.andrewl.railsignalapi.rest.dto.SegmentPayload;
|
||||
import nl.andrewl.railsignalapi.rest.dto.SegmentResponse;
|
||||
import nl.andrewl.railsignalapi.service.SegmentService;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
@ -28,4 +30,10 @@ public class SegmentsApiController {
|
|||
public FullSegmentResponse createSegment(@PathVariable long rsId, @RequestBody SegmentPayload payload) {
|
||||
return segmentService.create(rsId, payload);
|
||||
}
|
||||
|
||||
@DeleteMapping(path = "/{sId}")
|
||||
public ResponseEntity<Void> removeSegment(@PathVariable long rsId, @PathVariable long sId) {
|
||||
segmentService.remove(rsId, sId);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package nl.andrewl.railsignalapi.rest.dto;
|
||||
|
||||
import nl.andrewl.railsignalapi.model.Segment;
|
||||
import nl.andrewl.railsignalapi.rest.dto.component.SegmentBoundaryNodeResponse;
|
||||
import nl.andrewl.railsignalapi.rest.dto.component.SignalResponse;
|
||||
import nl.andrewl.railsignalapi.rest.dto.component.out.SegmentBoundaryNodeResponse;
|
||||
import nl.andrewl.railsignalapi.rest.dto.component.out.SignalResponse;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
package nl.andrewl.railsignalapi.rest.dto;
|
||||
|
||||
public record PathNodeUpdatePayload (
|
||||
long[] connectedNodeIds
|
||||
) {}
|
|
@ -1,4 +1,4 @@
|
|||
package nl.andrewl.railsignalapi.rest;
|
||||
package nl.andrewl.railsignalapi.rest.dto;
|
||||
|
||||
public record SegmentPayload(String name) {
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package nl.andrewl.railsignalapi.rest.dto.component.in;
|
||||
|
||||
import nl.andrewl.railsignalapi.model.component.Position;
|
||||
|
||||
public abstract class ComponentPayload {
|
||||
public String name;
|
||||
public String type;
|
||||
public Position position;
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package nl.andrewl.railsignalapi.rest.dto.component.in;
|
||||
|
||||
public class SignalPayload extends ComponentPayload {
|
||||
public long segmentId;
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package nl.andrewl.railsignalapi.rest.dto.component;
|
||||
package nl.andrewl.railsignalapi.rest.dto.component.out;
|
||||
|
||||
import nl.andrewl.railsignalapi.model.component.*;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package nl.andrewl.railsignalapi.rest.dto.component;
|
||||
package nl.andrewl.railsignalapi.rest.dto.component.out;
|
||||
|
||||
import nl.andrewl.railsignalapi.model.component.PathNode;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package nl.andrewl.railsignalapi.rest.dto.component;
|
||||
package nl.andrewl.railsignalapi.rest.dto.component.out;
|
||||
|
||||
import nl.andrewl.railsignalapi.model.component.SegmentBoundaryNode;
|
||||
import nl.andrewl.railsignalapi.rest.dto.SegmentResponse;
|
|
@ -1,4 +1,4 @@
|
|||
package nl.andrewl.railsignalapi.rest.dto.component;
|
||||
package nl.andrewl.railsignalapi.rest.dto.component.out;
|
||||
|
||||
import nl.andrewl.railsignalapi.model.component.Signal;
|
||||
import nl.andrewl.railsignalapi.rest.dto.SegmentResponse;
|
|
@ -1,4 +1,4 @@
|
|||
package nl.andrewl.railsignalapi.rest.dto.component;
|
||||
package nl.andrewl.railsignalapi.rest.dto.component.out;
|
||||
|
||||
import nl.andrewl.railsignalapi.model.component.Component;
|
||||
import nl.andrewl.railsignalapi.model.component.Position;
|
|
@ -1,4 +1,4 @@
|
|||
package nl.andrewl.railsignalapi.rest.dto.component;
|
||||
package nl.andrewl.railsignalapi.rest.dto.component.out;
|
||||
|
||||
import nl.andrewl.railsignalapi.model.component.SwitchConfiguration;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package nl.andrewl.railsignalapi.rest.dto.component;
|
||||
package nl.andrewl.railsignalapi.rest.dto.component.out;
|
||||
|
||||
import nl.andrewl.railsignalapi.model.component.Switch;
|
||||
|
|
@ -11,8 +11,8 @@ import nl.andrewl.railsignalapi.dao.SwitchConfigurationRepository;
|
|||
import nl.andrewl.railsignalapi.model.RailSystem;
|
||||
import nl.andrewl.railsignalapi.model.Segment;
|
||||
import nl.andrewl.railsignalapi.model.component.*;
|
||||
import nl.andrewl.railsignalapi.rest.dto.component.ComponentResponse;
|
||||
import nl.andrewl.railsignalapi.rest.dto.component.SimpleComponentResponse;
|
||||
import nl.andrewl.railsignalapi.rest.dto.PathNodeUpdatePayload;
|
||||
import nl.andrewl.railsignalapi.rest.dto.component.out.ComponentResponse;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
@ -31,10 +31,10 @@ public class ComponentService {
|
|||
private final SwitchConfigurationRepository switchConfigurationRepository;
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public List<SimpleComponentResponse> getComponents(long rsId) {
|
||||
public List<ComponentResponse> getComponents(long rsId) {
|
||||
var rs = railSystemRepository.findById(rsId)
|
||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
|
||||
return componentRepository.findAllByRailSystem(rs).stream().map(SimpleComponentResponse::new).toList();
|
||||
return componentRepository.findAllByRailSystem(rs).stream().map(ComponentResponse::of).toList();
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
|
@ -123,7 +123,22 @@ public class ComponentService {
|
|||
}
|
||||
|
||||
@Transactional
|
||||
public void remove(long rsId, long componentId) {
|
||||
|
||||
public ComponentResponse updatePath(long rsId, long cId, PathNodeUpdatePayload payload) {
|
||||
var c = componentRepository.findByIdAndRailSystemId(cId, rsId)
|
||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
|
||||
if (!(c instanceof PathNode p)) throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Component is not a PathNode.");
|
||||
Set<PathNode> newNodes = new HashSet<>();
|
||||
for (var id : payload.connectedNodeIds()) {
|
||||
var c1 = componentRepository.findByIdAndRailSystemId(id, rsId);
|
||||
if (c1.isPresent() && c1.get() instanceof PathNode pn) {
|
||||
newNodes.add(pn);
|
||||
} else {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Component with id " + id + " is not a PathNode in the same rail system.");
|
||||
}
|
||||
}
|
||||
p.getConnectedNodes().retainAll(newNodes);
|
||||
p.getConnectedNodes().addAll(newNodes);
|
||||
p = componentRepository.save(p);
|
||||
return ComponentResponse.of(p);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import nl.andrewl.railsignalapi.dao.RailSystemRepository;
|
|||
import nl.andrewl.railsignalapi.dao.SegmentRepository;
|
||||
import nl.andrewl.railsignalapi.model.Segment;
|
||||
import nl.andrewl.railsignalapi.model.component.Component;
|
||||
import nl.andrewl.railsignalapi.rest.SegmentPayload;
|
||||
import nl.andrewl.railsignalapi.rest.dto.SegmentPayload;
|
||||
import nl.andrewl.railsignalapi.rest.dto.FullSegmentResponse;
|
||||
import nl.andrewl.railsignalapi.rest.dto.SegmentResponse;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
@ -53,5 +53,6 @@ public class SegmentService {
|
|||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
|
||||
componentRepository.deleteAll(segment.getSignals());
|
||||
componentRepository.deleteAll(segment.getBoundaryNodes());
|
||||
segmentRepository.delete(segment);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue