Added improvements to advanced rail system components.
This commit is contained in:
parent
3c9554ebba
commit
cb8eed835a
|
@ -0,0 +1,166 @@
|
|||
<?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="1280"
|
||||
height="640"
|
||||
viewBox="0 0 338.66666 169.33334"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"
|
||||
sodipodi:docname="banner.svg"
|
||||
inkscape:export-filename="/home/andrew/Pictures/railsignalbanner.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96">
|
||||
<defs
|
||||
id="defs2">
|
||||
<inkscape:perspective
|
||||
sodipodi:type="inkscape:persp3d"
|
||||
inkscape:vp_x="-19.654761 : -18.142858 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_z="338.66666 : 84.66667 : 1"
|
||||
inkscape:persp3d-origin="169.33333 : 56.444447 : 1"
|
||||
id="perspective815" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.7"
|
||||
inkscape:cx="845.17179"
|
||||
inkscape:cy="205.3419"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
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,-127.66666)">
|
||||
<rect
|
||||
style="fill:#659a54;fill-opacity:1;stroke:none;stroke-width:12.70000076;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect870"
|
||||
width="338.66666"
|
||||
height="169.33333"
|
||||
x="0"
|
||||
y="127.66666" />
|
||||
<rect
|
||||
style="fill:#454e44;fill-opacity:1;stroke:none;stroke-width:12.70000076;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect911"
|
||||
width="338.66666"
|
||||
height="111.125"
|
||||
x="-1.110223e-16"
|
||||
y="185.875" />
|
||||
<g
|
||||
id="g852"
|
||||
transform="rotate(90,151.19047,228.96429)">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path835"
|
||||
d="m 122.84226,146.37648 h 92.98215"
|
||||
style="fill:none;stroke:#4a2b00;stroke-width:12.69999981;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
style="fill:none;stroke:#4a2b00;stroke-width:12.69999981;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 122.84226,212.9003 h 92.98215"
|
||||
id="path837"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path839"
|
||||
d="m 122.84226,246.1622 h 92.98215"
|
||||
style="fill:none;stroke:#4a2b00;stroke-width:12.69999981;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
style="fill:none;stroke:#4a2b00;stroke-width:12.69999981;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 122.84226,179.63839 h 92.98215"
|
||||
id="path841"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path843"
|
||||
d="m 122.84226,279.42411 h 92.98215"
|
||||
style="fill:none;stroke:#4a2b00;stroke-width:12.69999981;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path831"
|
||||
d="M 148.35565,291.70833 V 129.93452"
|
||||
style="fill:none;stroke:#989898;stroke-width:8.46666718;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
style="fill:none;stroke:#989898;stroke-width:8.46666718;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 190.31102,291.70833 V 129.93452"
|
||||
id="path833"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
<flowRoot
|
||||
xml:space="preserve"
|
||||
id="flowRoot913"
|
||||
style="font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:#102f07;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
transform="matrix(0.57599614,0,0,0.57599614,-10.203222,101.60397)"><flowRegion
|
||||
id="flowRegion915"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#102f07;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"><rect
|
||||
id="rect917"
|
||||
width="1102.8572"
|
||||
height="162.85715"
|
||||
x="51.42857"
|
||||
y="34.285713"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#102f07;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /></flowRegion><flowPara
|
||||
id="flowPara919"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:96px;font-family:'Ubuntu Mono';-inkscape-font-specification:'Ubuntu Mono Bold';stroke:#102f07;stroke-width:4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">Rail Signal</flowPara></flowRoot> <g
|
||||
id="g933"
|
||||
transform="translate(123.59822)">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path921"
|
||||
d="m 169.33333,245.97321 v 46.49107"
|
||||
style="fill:none;stroke:#000000;stroke-width:16.93333435;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<rect
|
||||
ry="2.6458333"
|
||||
rx="2.6458333"
|
||||
y="196.08035"
|
||||
x="152.13544"
|
||||
height="51.026794"
|
||||
width="34.395802"
|
||||
id="rect923"
|
||||
style="fill:#000000;fill-opacity:1;stroke:#bababa;stroke-width:4.23333359;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<circle
|
||||
r="6.0476193"
|
||||
cy="212.33333"
|
||||
cx="169.33333"
|
||||
id="path925"
|
||||
style="fill:#1cd500;fill-opacity:1;stroke:none;stroke-width:8.46666718;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<circle
|
||||
style="fill:#d50000;fill-opacity:1;stroke:none;stroke-width:8.46666718;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="circle927"
|
||||
cx="169.33333"
|
||||
cy="228.5863"
|
||||
r="6.0476193" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 7.2 KiB |
|
@ -98,6 +98,18 @@ export function removeComponent(rs, id) {
|
|||
});
|
||||
}
|
||||
|
||||
export function updateComponent(rs, component) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios.patch(`${API_URL}/rs/${rs.id}/c/${component.id}`, component)
|
||||
.then(() => {
|
||||
refreshComponents(rs)
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
export function updateSwitchConfiguration(rs, sw, configId) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios.post(
|
||||
|
|
|
@ -62,7 +62,7 @@ export function removeRailSystem(rsStore, id) {
|
|||
axios.delete(`${API_URL}/rs/${id}`)
|
||||
.then(() => {
|
||||
if (rsStore.selectedRailSystem !== null && rsStore.selectedRailSystem.id === id) {
|
||||
rsStore.selectRailSystem(null);
|
||||
rsStore.selectedRailSystem = null;
|
||||
}
|
||||
refreshRailSystems(rsStore)
|
||||
.then(resolve)
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
<template>
|
||||
<div class="row">
|
||||
<div class="col-md-8" id="railSystemMapCanvasContainer">
|
||||
<canvas id="railSystemMapCanvas" height="800">
|
||||
<canvas id="railSystemMapCanvas" height="600">
|
||||
Your browser doesn't support canvas.
|
||||
</canvas>
|
||||
</div>
|
||||
<q-scroll-area class="col-md-4" v-if="railSystem.selectedComponents.length > 0">
|
||||
<div class="row" v-for="component in railSystem.selectedComponents" :key="component.id">
|
||||
<div class="col full-width">
|
||||
<selected-component-view :component="component" :rail-system="railSystem" />
|
||||
<selected-component-view :component="component"/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
@ -178,11 +178,10 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { RailSystem } from "src/api/railSystems";
|
||||
import { draw, initMap } from "src/render/mapRenderer";
|
||||
import {RailSystem} from "src/api/railSystems";
|
||||
import {draw, initMap} from "src/render/mapRenderer";
|
||||
import SelectedComponentView from "components/rs/SelectedComponentView.vue";
|
||||
import { useQuasar } from "quasar";
|
||||
import { createComponent } from "src/api/components";
|
||||
import {useQuasar} from "quasar";
|
||||
import AddComponentDialog from "components/rs/add_component/AddComponentDialog.vue";
|
||||
|
||||
export default {
|
||||
|
|
|
@ -1,184 +1,37 @@
|
|||
<template>
|
||||
<div class="q-pa-md">
|
||||
<div class="text-h6">{{component.name}}</div>
|
||||
<q-list bordered>
|
||||
<q-item clickable>
|
||||
<q-item-section>
|
||||
<q-item-label>{{component.type}}</q-item-label>
|
||||
<q-item-label caption>Id: {{component.id}}</q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section v-if="component.online === true" top side>
|
||||
<q-chip color="positive" text-color="white">Online</q-chip>
|
||||
</q-item-section>
|
||||
<q-item-section v-if="component.online === false" top side>
|
||||
<q-chip color="negative" text-color="white">Offline</q-chip>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<q-item clickable>
|
||||
<q-item-section>
|
||||
<q-item-label>Position</q-item-label>
|
||||
<q-item-label caption>
|
||||
X: {{component.position.x}},
|
||||
Y: {{component.position.y}},
|
||||
Z: {{component.position.z}}
|
||||
</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<!-- Signal info -->
|
||||
<q-expansion-item
|
||||
id="signalInfo"
|
||||
label="Signal Information"
|
||||
v-if="component.type === 'SIGNAL'"
|
||||
:content-inset-level="0.5"
|
||||
switch-toggle-side
|
||||
expand-separator
|
||||
>
|
||||
<q-list>
|
||||
<segment-list-item v-for="segment in [component.segment]" :key="segment.id" :segment="segment" />
|
||||
</q-list>
|
||||
</q-expansion-item>
|
||||
|
||||
<!-- Path node info -->
|
||||
<q-expansion-item
|
||||
label="Connected Nodes"
|
||||
v-if="component.connectedNodes !== undefined && component.connectedNodes !== null"
|
||||
:content-inset-level="0.5"
|
||||
switch-toggle-side
|
||||
expand-separator
|
||||
class="q-gutter-md"
|
||||
>
|
||||
<q-list>
|
||||
<q-item
|
||||
v-for="node in component.connectedNodes"
|
||||
:key="node.id"
|
||||
clickable
|
||||
v-ripple
|
||||
@click="select(node)"
|
||||
>
|
||||
<q-item-section>
|
||||
<q-item-label>{{node.name}}</q-item-label>
|
||||
<q-item-label caption>Id: {{node.id}}</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-expansion-item>
|
||||
|
||||
<!-- Segment boundary info -->
|
||||
<q-expansion-item
|
||||
label="Connected Segments"
|
||||
v-if="component.type === 'SEGMENT_BOUNDARY'"
|
||||
:content-inset-level="0.5"
|
||||
switch-toggle-side
|
||||
expand-separator
|
||||
>
|
||||
<q-list>
|
||||
<segment-list-item v-for="segment in component.segments" :key="segment.id" :segment="segment"/>
|
||||
</q-list>
|
||||
</q-expansion-item>
|
||||
|
||||
<!-- Switch info -->
|
||||
<q-expansion-item
|
||||
label="Switch Information"
|
||||
v-if="component.type === 'SWITCH'"
|
||||
:content-inset-level="0.5"
|
||||
switch-toggle-side
|
||||
expand-separator
|
||||
>
|
||||
<q-list>
|
||||
<q-item
|
||||
v-for="config in component.possibleConfigurations"
|
||||
:key="config.id"
|
||||
>
|
||||
<q-item-section>
|
||||
<q-item-label class="q-gutter-sm">
|
||||
<q-chip
|
||||
v-for="node in config.nodes"
|
||||
:key="node.id"
|
||||
dense
|
||||
size="sm"
|
||||
:label="node.name"
|
||||
clickable
|
||||
@click="select(node)"
|
||||
/>
|
||||
</q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section v-if="component.activeConfiguration === null || component.activeConfiguration.id !== config.id" side>
|
||||
<q-btn dense size="sm" color="positive" @click="setActiveSwitchConfig(component, config.id)">Set Active</q-btn>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-expansion-item>
|
||||
|
||||
<q-item>
|
||||
<q-item-section side>
|
||||
<q-btn color="warning" label="Remove" size="sm" @click="remove(component)"/>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</div>
|
||||
<label-component-view
|
||||
:label="component"
|
||||
v-if="component.type === 'LABEL'"
|
||||
/>
|
||||
<signal-component-view
|
||||
:signal="component"
|
||||
v-if="component.type === 'SIGNAL'"
|
||||
/>
|
||||
<segment-boundary-component-view
|
||||
:segment-boundary="component"
|
||||
v-if="component.type === 'SEGMENT_BOUNDARY'"
|
||||
/>
|
||||
<switch-component-view
|
||||
:sw="component"
|
||||
v-if="component.type === 'SWITCH'"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { RailSystem } from "src/api/railSystems";
|
||||
import { useRailSystemsStore } from "stores/railSystemsStore";
|
||||
import SegmentListItem from "components/rs/SegmentListItem.vue";
|
||||
import { useQuasar } from "quasar";
|
||||
import { removeComponent, updateSwitchConfiguration } from "src/api/components";
|
||||
import SignalComponentView from "components/rs/component_views/SignalComponentView.vue";
|
||||
import SegmentBoundaryComponentView from "components/rs/component_views/SegmentBoundaryComponentView.vue";
|
||||
import LabelComponentView from "components/rs/component_views/LabelComponentView.vue";
|
||||
import SwitchComponentView from "components/rs/component_views/SwitchComponentView.vue";
|
||||
|
||||
export default {
|
||||
name: "SelectedComponentView",
|
||||
components: { SegmentListItem },
|
||||
setup() {
|
||||
const rsStore = useRailSystemsStore();
|
||||
const quasar = useQuasar();
|
||||
return {rsStore, quasar};
|
||||
},
|
||||
components: {
|
||||
SwitchComponentView,
|
||||
LabelComponentView, SegmentBoundaryComponentView, SignalComponentView },
|
||||
props: {
|
||||
component: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
railSystem: {
|
||||
type: RailSystem,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
select(component) {
|
||||
const c = this.rsStore.selectedRailSystem.components.find(cp => cp.id === component.id);
|
||||
if (c) {
|
||||
this.rsStore.selectedRailSystem.selectedComponents.length = 0;
|
||||
this.rsStore.selectedRailSystem.selectedComponents.push(c);
|
||||
}
|
||||
},
|
||||
remove(component) {
|
||||
this.quasar.dialog({
|
||||
title: "Confirm Removal",
|
||||
message: "Are you sure you want to remove this component? This cannot be undone.",
|
||||
cancel: true
|
||||
}).onOk(() => {
|
||||
removeComponent(this.railSystem, component.id)
|
||||
.then(() => {
|
||||
this.quasar.notify({
|
||||
color: "positive",
|
||||
message: "Component has been removed."
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
this.quasar.notify({
|
||||
color: "negative",
|
||||
message: "An error occurred: " + error.response.data.message
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
setActiveSwitchConfig(sw, configId) {
|
||||
updateSwitchConfiguration(this.railSystem, sw, configId);
|
||||
},
|
||||
isConfigActive(sw, config) {
|
||||
return sw.activeConfiguration !== null && sw.activeConfiguration.id === config.id;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
<template>
|
||||
<div class="q-pa-md">
|
||||
<div class="text-h6">{{component.name}}</div>
|
||||
<q-list bordered>
|
||||
<q-item clickable>
|
||||
<q-item-section>
|
||||
<q-item-label>{{component.type}}</q-item-label>
|
||||
<q-item-label caption>Id: {{component.id}}</q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section v-if="component.online === true" top side>
|
||||
<q-chip color="positive" text-color="white">Online</q-chip>
|
||||
</q-item-section>
|
||||
<q-item-section v-if="component.online === false" top side>
|
||||
<q-chip color="negative" text-color="white">Offline</q-chip>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<q-item clickable>
|
||||
<q-item-section>
|
||||
<q-item-label>Position</q-item-label>
|
||||
<q-item-label caption>
|
||||
X: {{component.position.x}},
|
||||
Y: {{component.position.y}},
|
||||
Z: {{component.position.z}}
|
||||
</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<q-separator/>
|
||||
<slot></slot>
|
||||
|
||||
<q-item>
|
||||
<q-item-section side>
|
||||
<q-btn color="negative" label="Remove" size="sm" @click="remove(component)"/>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {removeComponent} from "src/api/components";
|
||||
import {useQuasar} from "quasar";
|
||||
import {useRailSystemsStore} from "stores/railSystemsStore";
|
||||
|
||||
export default {
|
||||
name: "BaseComponentView",
|
||||
props: {
|
||||
component: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
setup() {
|
||||
const rsStore = useRailSystemsStore();
|
||||
const quasar = useQuasar();
|
||||
return {rsStore, quasar};
|
||||
},
|
||||
methods: {
|
||||
select(component) {
|
||||
const c = this.rsStore.selectedRailSystem.components.find(cp => cp.id === component.id);
|
||||
if (c) {
|
||||
this.rsStore.selectedRailSystem.selectedComponents.length = 0;
|
||||
this.rsStore.selectedRailSystem.selectedComponents.push(c);
|
||||
}
|
||||
},
|
||||
remove(component) {
|
||||
this.quasar.dialog({
|
||||
title: "Confirm Removal",
|
||||
message: "Are you sure you want to remove this component? This cannot be undone.",
|
||||
cancel: true
|
||||
}).onOk(() => {
|
||||
removeComponent(this.railSystem, component.id)
|
||||
.then(() => {
|
||||
this.quasar.notify({
|
||||
color: "positive",
|
||||
message: "Component has been removed."
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
this.quasar.notify({
|
||||
color: "negative",
|
||||
message: "An error occurred: " + error.response.data.message
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,28 @@
|
|||
<template>
|
||||
<base-component-view :component="label">
|
||||
<q-item-label header>Label</q-item-label>
|
||||
<q-item>
|
||||
<q-item-section>
|
||||
<q-item-label>{{label.text}}</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</base-component-view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseComponentView from "components/rs/component_views/BaseComponentView.vue";
|
||||
export default {
|
||||
name: "LabelComponentView",
|
||||
components: {BaseComponentView},
|
||||
props: {
|
||||
label: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,125 @@
|
|||
<template>
|
||||
<q-item-label header>Connected Path Nodes</q-item-label>
|
||||
<q-item>
|
||||
<q-item-section>
|
||||
<q-item-label class="q-gutter-sm">
|
||||
<q-chip
|
||||
v-for="node in pathNode.connectedNodes"
|
||||
:key="node.id"
|
||||
dense
|
||||
size="sm"
|
||||
:label="node.name"
|
||||
/>
|
||||
</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item dense v-if="editable">
|
||||
<q-btn size="sm" color="accent" label="Edit Connected Nodes" @click="showDialog"/>
|
||||
<q-dialog v-model="dialog.visible" style="min-width: 400px" @hide="reset">
|
||||
<q-card>
|
||||
<q-form @submit="onSubmit" @reset="reset">
|
||||
<q-card-section>
|
||||
<div class="text-h6">Edit Connected Nodes</div>
|
||||
<p>
|
||||
Update the nodes that this path node is connected to. These
|
||||
connections define how the system understands your rail network,
|
||||
and are used for routing and analytics.
|
||||
</p>
|
||||
<p v-if="pathNode.type ==='SEGMENT_BOUNDARY'">
|
||||
You're editing a <strong>segment boundary</strong> node. This
|
||||
means that you can only have at most <strong>2</strong> nodes
|
||||
connected to it.
|
||||
</p>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-select
|
||||
v-model="dialog.selectedNodes"
|
||||
:options="getEligibleNodes()"
|
||||
multiple
|
||||
:option-value="node => node"
|
||||
:option-label="node => node.name"
|
||||
use-chips
|
||||
stack-label
|
||||
label="Nodes"
|
||||
/>
|
||||
</q-card-section>
|
||||
<q-card-actions align="right" class="text-primary">
|
||||
<q-btn flat label="Cancel" type="reset" @click="dialog.visible = false"/>
|
||||
<q-btn flat label="Edit" type="submit"/>
|
||||
</q-card-actions>
|
||||
</q-form>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
</q-item>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {useRailSystemsStore} from "stores/railSystemsStore";
|
||||
import {useQuasar} from "quasar";
|
||||
import {updateComponent} from "src/api/components";
|
||||
|
||||
export default {
|
||||
name: "PathNodeItem",
|
||||
props: {
|
||||
pathNode: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
editable: {
|
||||
type: Boolean,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
setup() {
|
||||
const rsStore = useRailSystemsStore();
|
||||
const quasar = useQuasar();
|
||||
return {rsStore, quasar};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialog: {
|
||||
visible: false,
|
||||
selectedNodes: []
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
showDialog() {
|
||||
this.dialog.selectedNodes = this.pathNode.connectedNodes.slice();
|
||||
this.dialog.visible = true;
|
||||
},
|
||||
reset() {
|
||||
this.dialog.selectedNodes.length = 0;
|
||||
},
|
||||
onSubmit() {
|
||||
const data = {...this.pathNode};
|
||||
data.connectedNodes = this.dialog.selectedNodes;
|
||||
updateComponent(this.rsStore.selectedRailSystem, data)
|
||||
.then(() => {
|
||||
this.dialog.visible = false;
|
||||
this.quasar.notify({
|
||||
color: "positive",
|
||||
message: "Path node updated."
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error);
|
||||
this.quasar.notify({
|
||||
color: "negative",
|
||||
message: "An error occurred: " + error.response.data.message
|
||||
});
|
||||
});
|
||||
},
|
||||
getEligibleNodes() {
|
||||
return this.rsStore.selectedRailSystem.components.filter(c => {
|
||||
return (c.connectedNodes !== null && c.connectedNodes !== undefined) &&
|
||||
this.dialog.selectedNodes.findIndex(node => node.id === c.id) === -1;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,35 @@
|
|||
<template>
|
||||
<base-component-view :component="segmentBoundary">
|
||||
<q-item-label header>Connected Segments</q-item-label>
|
||||
<q-item>
|
||||
<q-item-section
|
||||
v-for="segment in segmentBoundary.segments"
|
||||
:key="segment.id"
|
||||
>
|
||||
<q-item-label>{{segment.name}}</q-item-label>
|
||||
<q-item-label caption>ID: {{segment.id}}</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-separator/>
|
||||
<path-node-item :path-node="segmentBoundary" :editable="true"/>
|
||||
</base-component-view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseComponentView from "components/rs/component_views/BaseComponentView.vue";
|
||||
import PathNodeItem from "components/rs/component_views/PathNodeItem.vue";
|
||||
export default {
|
||||
name: "SegmentBoundaryComponentView",
|
||||
components: {PathNodeItem, BaseComponentView},
|
||||
props: {
|
||||
segmentBoundary: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,25 @@
|
|||
<template>
|
||||
<base-component-view :component="signal">
|
||||
<q-item-label header>Connected to Segment</q-item-label>
|
||||
<segment-list-item :segment="signal.segment"/>
|
||||
</base-component-view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseComponentView from "components/rs/component_views/BaseComponentView.vue";
|
||||
import SegmentListItem from "components/rs/SegmentListItem.vue";
|
||||
export default {
|
||||
name: "SignalComponentView",
|
||||
components: {SegmentListItem, BaseComponentView},
|
||||
props: {
|
||||
signal: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,200 @@
|
|||
<template>
|
||||
<base-component-view :component="sw">
|
||||
<q-item-label header>Switch Configurations</q-item-label>
|
||||
<q-list>
|
||||
<q-item
|
||||
v-for="config in sw.possibleConfigurations"
|
||||
:key="config.id"
|
||||
dense
|
||||
>
|
||||
<q-item-section>
|
||||
<q-item-label class="q-gutter-sm">
|
||||
<q-chip
|
||||
v-for="node in config.nodes"
|
||||
:key="node.id"
|
||||
dense
|
||||
size="sm"
|
||||
:label="node.name"
|
||||
clickable
|
||||
/>
|
||||
</q-item-label>
|
||||
<q-item-label caption>Configuration #{{config.id}}</q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section v-if="!isConfigActive(config)" side top>
|
||||
<q-btn dense size="sm" color="positive" @click="setActiveSwitchConfig(config.id)">Set Active</q-btn>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item>
|
||||
<q-btn size="sm" color="accent" label="Edit Configurations" @click="showDialog"/>
|
||||
<q-dialog v-model="dialog.visible" style="min-width: 400px" @hide="reset">
|
||||
<q-card>
|
||||
<q-form @submit="onSubmit" @reset="reset">
|
||||
<q-card-section>
|
||||
<div class="text-h6">Edit Switch Configurations</div>
|
||||
<p>
|
||||
Edit the possible configurations for this switch.
|
||||
</p>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<div class="row">
|
||||
<q-list>
|
||||
<q-item
|
||||
v-for="config in dialog.configurations"
|
||||
:key="config.key"
|
||||
>
|
||||
<q-item-section>
|
||||
<q-item-label>
|
||||
<q-chip
|
||||
v-for="node in config.nodes"
|
||||
:key="node.id"
|
||||
:label="node.name"
|
||||
/>
|
||||
</q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section side>
|
||||
<q-btn size="12px" flat dense round icon="delete" @click="removeConfig(config)"/>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<q-select
|
||||
v-model="dialog.pathNode1"
|
||||
:options="getEligibleSwitchNodes(dialog.pathNode2)"
|
||||
:option-value="node => node"
|
||||
:option-label="node => node.name"
|
||||
label="First Path Node"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<q-select
|
||||
v-model="dialog.pathNode2"
|
||||
:options="getEligibleSwitchNodes(dialog.pathNode1)"
|
||||
:option-value="node => node"
|
||||
:option-label="node => node.name"
|
||||
label="Second Path Node"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<q-btn label="Add Configuration" @click="addConfiguration" v-if="canAddConfig(dialog.pathNode1, dialog.pathNode2)"/>
|
||||
</div>
|
||||
</q-card-section>
|
||||
<q-card-actions align="right" class="text-primary">
|
||||
<q-btn flat label="Cancel" type="reset" @click="dialog.visible = false"/>
|
||||
<q-btn flat label="Edit" type="submit"/>
|
||||
</q-card-actions>
|
||||
</q-form>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
</q-item>
|
||||
</q-list>
|
||||
<q-separator/>
|
||||
<path-node-item :path-node="sw"/>
|
||||
</base-component-view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseComponentView from "components/rs/component_views/BaseComponentView.vue";
|
||||
import {updateComponent, updateSwitchConfiguration} from "src/api/components";
|
||||
import PathNodeItem from "components/rs/component_views/PathNodeItem.vue";
|
||||
import {useRailSystemsStore} from "stores/railSystemsStore";
|
||||
import {useQuasar} from "quasar";
|
||||
export default {
|
||||
name: "SwitchComponentView",
|
||||
components: {PathNodeItem, BaseComponentView},
|
||||
props: {
|
||||
sw: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
setup() {
|
||||
const rsStore = useRailSystemsStore();
|
||||
const quasar = useQuasar();
|
||||
return {rsStore, quasar};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialog: {
|
||||
visible: false,
|
||||
configurations: [],
|
||||
pathNode1: null,
|
||||
pathNode2: null
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setActiveSwitchConfig(configId) {
|
||||
updateSwitchConfiguration(this.railSystem, this.sw, configId);
|
||||
},
|
||||
isConfigActive(config) {
|
||||
return this.sw.activeConfiguration !== null && this.sw.activeConfiguration.id === config.id;
|
||||
},
|
||||
showDialog() {
|
||||
this.dialog.configurations = this.sw.possibleConfigurations.slice();
|
||||
this.dialog.configurations.forEach(cfg => {
|
||||
cfg.key = cfg.nodes[0].id + '_' + cfg.nodes[1].id
|
||||
});
|
||||
this.dialog.visible = true;
|
||||
},
|
||||
reset() {
|
||||
this.dialog.pathNode1 = null;
|
||||
this.dialog.pathNode2 = null;
|
||||
this.dialog.configurations.length = 0;
|
||||
},
|
||||
onSubmit() {
|
||||
const data = {...this.sw};
|
||||
data.possibleConfigurations = this.dialog.configurations;
|
||||
data.activeConfiguration = null;
|
||||
updateComponent(this.rsStore.selectedRailSystem, data)
|
||||
.then(() => {
|
||||
this.dialog.visible = false;
|
||||
this.quasar.notify({
|
||||
color: "positive",
|
||||
message: "Switch configurations updated."
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error);
|
||||
this.quasar.notify({
|
||||
color: "negative",
|
||||
message: "An error occurred: " + error.response.data.message
|
||||
});
|
||||
});
|
||||
},
|
||||
getEligibleSwitchNodes(exclude) {
|
||||
return this.rsStore.selectedRailSystem.components.filter(c => {
|
||||
return (c.connectedNodes !== undefined && c.connectedNodes !== null) &&
|
||||
(exclude === null || c.id !== exclude.id);
|
||||
});
|
||||
},
|
||||
canAddConfig(n1, n2) {
|
||||
return this.dialog.configurations.length < 2 &&
|
||||
n1 !== null && n2 !== null &&
|
||||
n1.id !== n2.id &&
|
||||
!this.dialog.configurations.some(config => {
|
||||
return config.nodes.every(node => {
|
||||
return node.id === n1.id || node.id === n2.id;
|
||||
});
|
||||
});
|
||||
},
|
||||
addConfiguration() {
|
||||
this.dialog.configurations.push({
|
||||
nodes: [this.dialog.pathNode1, this.dialog.pathNode2],
|
||||
key: this.dialog.pathNode1.id + '_' + this.dialog.pathNode2.id
|
||||
});
|
||||
},
|
||||
removeConfig(config) {
|
||||
const idx = this.dialog.configurations.findIndex(cfg => cfg.key === config.key);
|
||||
if (idx === -1) return;
|
||||
this.dialog.configurations.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -26,22 +26,12 @@
|
|||
bordered
|
||||
>
|
||||
<rail-systems-list :rail-systems="rsStore.railSystems" />
|
||||
<q-item
|
||||
clickable
|
||||
v-ripple
|
||||
:to="'/'"
|
||||
@click="rsStore.selectRailSystem(null)"
|
||||
>
|
||||
<q-item clickable v-ripple :to="'/'">
|
||||
<q-item-section>
|
||||
<q-item-label>Home</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item
|
||||
clickable
|
||||
v-ripple
|
||||
:to="'/about'"
|
||||
@click="rsStore.selectRailSystem(null)"
|
||||
>
|
||||
<q-item clickable v-ripple :to="'/about'">
|
||||
<q-item-section>
|
||||
<q-item-label>About</q-item-label>
|
||||
</q-item-section>
|
||||
|
@ -68,7 +58,6 @@ export default defineComponent({
|
|||
setup () {
|
||||
const rsStore = useRailSystemsStore()
|
||||
const leftDrawerOpen = ref(false)
|
||||
|
||||
return {
|
||||
rsStore,
|
||||
leftDrawerOpen,
|
||||
|
|
|
@ -71,8 +71,11 @@ function drawDebugInfo(ctx) {
|
|||
"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}`
|
||||
`Hovered components: ${hoveredElements.length}`
|
||||
]
|
||||
for (let i = 0; i < hoveredElements.length; i++) {
|
||||
lines.push(" " + hoveredElements[i].name);
|
||||
}
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
ctx.fillText(lines[i], 10, 20 + (i * 15));
|
||||
}
|
||||
|
|
|
@ -1,10 +1,4 @@
|
|||
import { defineStore } from "pinia";
|
||||
import { refreshSegments } from "../api/segments";
|
||||
import { refreshComponents } from "../api/components";
|
||||
import { closeWebsocketConnection, establishWebsocketConnection } from "../api/websocket";
|
||||
import { refreshRailSystems } from "src/api/railSystems";
|
||||
import { refreshLinkTokens } from "src/api/linkTokens";
|
||||
import { refreshSettings } from "src/api/settings";
|
||||
import {defineStore} from "pinia";
|
||||
|
||||
export const useRailSystemsStore = defineStore('RailSystemsStore', {
|
||||
state: () => ({
|
||||
|
@ -16,45 +10,5 @@ export const useRailSystemsStore = defineStore('RailSystemsStore', {
|
|||
* @type {RailSystem | null}
|
||||
*/
|
||||
selectedRailSystem: null
|
||||
}),
|
||||
actions: {
|
||||
/**
|
||||
* Updates the selected rail system.
|
||||
* @param rsId {Number | null} The new rail system id.
|
||||
* @returns {Promise} A promise that resolves when the new rail system is
|
||||
* fully loaded and ready.
|
||||
*/
|
||||
selectRailSystem(rsId) {
|
||||
// Close any existing websocket connections prior to refreshing.
|
||||
const wsClosePromises = [];
|
||||
if (this.selectedRailSystem !== null) {
|
||||
wsClosePromises.push(closeWebsocketConnection(this.selectedRailSystem));
|
||||
}
|
||||
if (rsId === null) return Promise.all(wsClosePromises);
|
||||
return new Promise(resolve => {
|
||||
Promise.all(wsClosePromises).then(() => {
|
||||
refreshRailSystems(this).then(() => {
|
||||
const rs = this.railSystems.find(r => r.id === rsId);
|
||||
console.log(rs);
|
||||
const updatePromises = [];
|
||||
updatePromises.push(refreshSegments(rs));
|
||||
updatePromises.push(refreshComponents(rs));
|
||||
updatePromises.push(refreshLinkTokens(rs));
|
||||
updatePromises.push(refreshSettings(rs));
|
||||
updatePromises.push(establishWebsocketConnection(rs));
|
||||
Promise.all(updatePromises).then(() => {
|
||||
this.selectedRailSystem = rs;
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
rsId() {
|
||||
if (this.selectedRailSystem === null) return null;
|
||||
return this.selectedRailSystem.id;
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue