Added UI for creating link tokens, and improved component search api.
This commit is contained in:
parent
ed3f6bd6b9
commit
e45b942f34
|
@ -48,6 +48,27 @@ export function getComponent(rs, id) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches through the rail system's components.
|
||||||
|
* @param {RailSystem} rs
|
||||||
|
* @param {string|null} searchQuery
|
||||||
|
* @return {Promise<Object>}
|
||||||
|
*/
|
||||||
|
export function searchComponents(rs, searchQuery) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const params = {
|
||||||
|
page: 0,
|
||||||
|
size: 25
|
||||||
|
};
|
||||||
|
if (searchQuery) params.q = searchQuery;
|
||||||
|
axios.get(`${API_URL}/rs/${rs.id}/c/search`, {params: params})
|
||||||
|
.then(response => {
|
||||||
|
resolve(response.data);
|
||||||
|
})
|
||||||
|
.catch(reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function createComponent(rs, data) {
|
export function createComponent(rs, data) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
axios.post(`${API_URL}/rs/${rs.id}/c`, data)
|
axios.post(`${API_URL}/rs/${rs.id}/c`, data)
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
import {API_URL} from "./constants";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A token that's used by components to provide real-time up and down links.
|
||||||
|
*/
|
||||||
|
export class LinkToken {
|
||||||
|
constructor(data) {
|
||||||
|
this.id = data.id;
|
||||||
|
this.label = data.label;
|
||||||
|
this.components = data.components;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the list of link tokens in a rail system.
|
||||||
|
* @param {RailSystem} rs
|
||||||
|
* @return {Promise<LinkToken[]>}
|
||||||
|
*/
|
||||||
|
export function getTokens(rs) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
axios.get(`${API_URL}/rs/${rs.id}/lt`)
|
||||||
|
.then(response => {
|
||||||
|
resolve(response.data.map(obj => new LinkToken(obj)));
|
||||||
|
})
|
||||||
|
.catch(reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new link token.
|
||||||
|
* @param {RailSystem} rs
|
||||||
|
* @param {LinkToken} data
|
||||||
|
* @return {Promise<string>} A promise that resolves to the token that was created.
|
||||||
|
*/
|
||||||
|
export function createLinkToken(rs, data) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
axios.post(`${API_URL}/rs/${rs.id}/lt`, data)
|
||||||
|
.then(response => {
|
||||||
|
resolve(response.data.token);
|
||||||
|
})
|
||||||
|
.catch(reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a link token.
|
||||||
|
* @param {RailSystem} rs
|
||||||
|
* @param {Number} tokenId
|
||||||
|
*/
|
||||||
|
export function deleteToken(rs, tokenId) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
axios.delete(`${API_URL}/rs/${rs.id}/lt/${tokenId}`)
|
||||||
|
.then(resolve)
|
||||||
|
.catch(reject);
|
||||||
|
});
|
||||||
|
}
|
|
@ -2,6 +2,12 @@ import axios from "axios";
|
||||||
import {API_URL} from "./constants";
|
import {API_URL} from "./constants";
|
||||||
import {refreshSomeComponents} from "./components";
|
import {refreshSomeComponents} from "./components";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the connections to a path node.
|
||||||
|
* @param {RailSystem} rs The rail system to which the node belongs.
|
||||||
|
* @param {Object} node The node to update.
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
export function updateConnections(rs, node) {
|
export function updateConnections(rs, node) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
axios.patch(
|
axios.patch(
|
||||||
|
@ -16,11 +22,25 @@ export function updateConnections(rs, node) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a connection to a path node.
|
||||||
|
* @param {RailSystem} rs
|
||||||
|
* @param {Object} node
|
||||||
|
* @param {Object} other
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
export function addConnection(rs, node, other) {
|
export function addConnection(rs, node, other) {
|
||||||
node.connectedNodes.push(other);
|
node.connectedNodes.push(other);
|
||||||
return updateConnections(rs, node);
|
return updateConnections(rs, node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a connection from a path node.
|
||||||
|
* @param {RailSystem} rs
|
||||||
|
* @param {Object} node
|
||||||
|
* @param {Object} other
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
export function removeConnection(rs, node, other) {
|
export function removeConnection(rs, node, other) {
|
||||||
const idx = node.connectedNodes.findIndex(n => n.id === other.id);
|
const idx = node.connectedNodes.findIndex(n => n.id === other.id);
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|
|
@ -1,11 +1,26 @@
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import {API_URL} from "./constants";
|
import {API_URL} from "./constants";
|
||||||
|
|
||||||
|
export class RailSystem {
|
||||||
|
constructor(data) {
|
||||||
|
this.id = data.id;
|
||||||
|
this.name = data.name;
|
||||||
|
this.segments = [];
|
||||||
|
this.components = [];
|
||||||
|
this.websocket = null;
|
||||||
|
this.selectedComponent = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function refreshRailSystems(rsStore) {
|
export function refreshRailSystems(rsStore) {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
axios.get(`${API_URL}/rs`)
|
axios.get(`${API_URL}/rs`)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
rsStore.railSystems = response.data;
|
const rsItems = response.data;
|
||||||
|
rsStore.railSystems.length = 0;
|
||||||
|
for (let i = 0; i < rsItems.length; i++) {
|
||||||
|
rsStore.railSystems.push(new RailSystem(rsItems[i]));
|
||||||
|
}
|
||||||
resolve();
|
resolve();
|
||||||
})
|
})
|
||||||
.catch(error => console.error(error));
|
.catch(error => console.error(error));
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import {WS_URL} from "./constants";
|
import {WS_URL} from "./constants";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Establishes a websocket connection to the given rail system.
|
||||||
|
* @param {RailSystem} rs
|
||||||
|
*/
|
||||||
export function establishWebsocketConnection(rs) {
|
export function establishWebsocketConnection(rs) {
|
||||||
if (rs.websocket) {
|
closeWebsocketConnection(rs);
|
||||||
rs.websocket.close();
|
|
||||||
}
|
|
||||||
rs.websocket = new WebSocket(`${WS_URL}/${rs.id}`);
|
rs.websocket = new WebSocket(`${WS_URL}/${rs.id}`);
|
||||||
rs.websocket.onopen = () => {
|
rs.websocket.onopen = () => {
|
||||||
console.log("Opened websocket connection to rail system " + rs.id);
|
console.log("Opened websocket connection to rail system " + rs.id);
|
||||||
|
@ -25,6 +27,10 @@ export function establishWebsocketConnection(rs) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the websocket connection to a rail system, if possible.
|
||||||
|
* @param {RailSystem} rs
|
||||||
|
*/
|
||||||
export function closeWebsocketConnection(rs) {
|
export function closeWebsocketConnection(rs) {
|
||||||
if (rs.websocket) {
|
if (rs.websocket) {
|
||||||
rs.websocket.close();
|
rs.websocket.close();
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
id="railSystemSelect"
|
id="railSystemSelect"
|
||||||
v-model="rsStore.selectedRailSystem"
|
v-model="rsStore.selectedRailSystem"
|
||||||
class="form-select form-select-sm"
|
class="form-select form-select-sm"
|
||||||
|
@change="rsStore.onSelectedRailSystemChanged()"
|
||||||
>
|
>
|
||||||
<option v-for="rs in rsStore.railSystems" :key="rs.id" :value="rs">
|
<option v-for="rs in rsStore.railSystems" :key="rs.id" :value="rs">
|
||||||
{{rs.name}}
|
{{rs.name}}
|
||||||
|
|
|
@ -0,0 +1,115 @@
|
||||||
|
<template>
|
||||||
|
<div class="modal fade" tabindex="-1" id="createLinkTokenModal">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Create Link Token</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>
|
||||||
|
Create a <em>link token</em> to link components in this rail system
|
||||||
|
to actual devices in your system, so your world can talk to this
|
||||||
|
system. Each link token should have a unique label that can be used
|
||||||
|
to identify it, and a list of components that it's linked to.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Note that for security purposes, the raw token that's generated is
|
||||||
|
only shown once, and is never available again. If you lose the
|
||||||
|
token, you must create a new one instead and delete the old one.
|
||||||
|
</p>
|
||||||
|
<form>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="createLinkTokenLabel" class="form-label">Label</label>
|
||||||
|
<input
|
||||||
|
id="createLinkTokenLabel"
|
||||||
|
class="form-control"
|
||||||
|
type="text"
|
||||||
|
v-model="formData.label"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label" for="createLinkTokenComponentSelect">Select Components to Link</label>
|
||||||
|
<ComponentSelector id="createLinkTokenComponentSelect" :railSystem="railSystem" v-model="components"/>
|
||||||
|
</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 v-if="token !== null" class="alert alert-success mt-2">
|
||||||
|
Created token: {{token}}
|
||||||
|
<br>
|
||||||
|
<small>Copy this token now; it will not be shown again.</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" @click="reset()">Close</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-primary"
|
||||||
|
@click="formSubmitted()"
|
||||||
|
v-if="token == null"
|
||||||
|
>
|
||||||
|
Add
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {createLinkToken} from "../../api/linkTokens";
|
||||||
|
import {RailSystem} from "../../api/railSystems";
|
||||||
|
import ComponentSelector from "./util/ComponentSelector.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "CreateLinkTokenModal",
|
||||||
|
components: {ComponentSelector},
|
||||||
|
props: {
|
||||||
|
railSystem: {
|
||||||
|
type: RailSystem,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
formData: {
|
||||||
|
label: "",
|
||||||
|
componentIds: []
|
||||||
|
},
|
||||||
|
components: [],
|
||||||
|
warnings: [],
|
||||||
|
token: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
formSubmitted() {
|
||||||
|
this.formData.componentIds = this.components.map(c => c.id);
|
||||||
|
createLinkToken(this.railSystem, this.formData)
|
||||||
|
.then(token => {
|
||||||
|
this.token = token;
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
this.warnings.length = 0;
|
||||||
|
this.warnings.push("Couldn't create token: " + error.response.data.message);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
reset() {
|
||||||
|
// TODO: Fix this!! Reset doesn't work.
|
||||||
|
this.formData.label = "";
|
||||||
|
this.formData.componentIds = [];
|
||||||
|
this.components = [];
|
||||||
|
this.warnings = [];
|
||||||
|
this.token = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<h3>Rail System: <em>{{railSystem.name}}</em></h3>
|
<h3>Rail System: <em>{{railSystem.name}}</em></h3>
|
||||||
<SegmentsView :segments="railSystem.segments" v-if="railSystem.segments"/>
|
<SegmentsView :segments="railSystem.segments" v-if="railSystem.segments"/>
|
||||||
<div class="dropdown">
|
<div class="dropdown d-inline-block me-2">
|
||||||
<button class="btn btn-success btn-sm dropdown-toggle" type="button" id="railSystemAddComponentsToggle" data-bs-toggle="dropdown" aria-expanded="false">
|
<button class="btn btn-success btn-sm dropdown-toggle" type="button" id="railSystemAddComponentsToggle" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
Add Component
|
Add Component
|
||||||
</button>
|
</button>
|
||||||
|
@ -48,6 +48,17 @@
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="createLinkTokenAllowed()" class="d-inline-block">
|
||||||
|
<button
|
||||||
|
class="btn btn-success btn-sm"
|
||||||
|
type="button"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#createLinkTokenModal"
|
||||||
|
>
|
||||||
|
Create Link Token
|
||||||
|
</button>
|
||||||
|
<CreateLinkTokenModal :railSystem="railSystem" />
|
||||||
|
</div>
|
||||||
<AddSegmentModal />
|
<AddSegmentModal />
|
||||||
<AddSignalModal v-if="addSignalAllowed()" />
|
<AddSignalModal v-if="addSignalAllowed()" />
|
||||||
<AddSegmentBoundaryModal v-if="addSegmentBoundaryAllowed()" />
|
<AddSegmentBoundaryModal v-if="addSegmentBoundaryAllowed()" />
|
||||||
|
@ -60,10 +71,13 @@ import AddSegmentModal from "./AddSegmentModal.vue";
|
||||||
import AddSignalModal from "./component/AddSignalModal.vue";
|
import AddSignalModal from "./component/AddSignalModal.vue";
|
||||||
import AddSegmentBoundaryModal from "./component/AddSegmentBoundaryModal.vue";
|
import AddSegmentBoundaryModal from "./component/AddSegmentBoundaryModal.vue";
|
||||||
import AddSwitchModal from "./component/AddSwitchModal.vue";
|
import AddSwitchModal from "./component/AddSwitchModal.vue";
|
||||||
|
import CreateLinkTokenModal from "./CreateLinkTokenModal.vue";
|
||||||
|
import {RailSystem} from "../../api/railSystems";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "RailSystemPropertiesView",
|
name: "RailSystemPropertiesView",
|
||||||
components: {
|
components: {
|
||||||
|
CreateLinkTokenModal,
|
||||||
AddSwitchModal,
|
AddSwitchModal,
|
||||||
AddSegmentBoundaryModal,
|
AddSegmentBoundaryModal,
|
||||||
AddSignalModal,
|
AddSignalModal,
|
||||||
|
@ -72,19 +86,22 @@ export default {
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
railSystem: {
|
railSystem: {
|
||||||
type: Object,
|
type: RailSystem,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
addSignalAllowed() {
|
addSignalAllowed() {
|
||||||
return this.railSystem.segments && this.railSystem.segments.length > 0
|
return this.railSystem.segments && this.railSystem.segments.length > 0;
|
||||||
},
|
},
|
||||||
addSegmentBoundaryAllowed() {
|
addSegmentBoundaryAllowed() {
|
||||||
return this.railSystem.segments && this.railSystem.segments.length > 1
|
return this.railSystem.segments && this.railSystem.segments.length > 1;
|
||||||
},
|
},
|
||||||
addSwitchAllowed() {
|
addSwitchAllowed() {
|
||||||
return this.railSystem.components && this.railSystem.components.length > 1
|
return this.railSystem.components && this.railSystem.components.length > 1;
|
||||||
|
},
|
||||||
|
createLinkTokenAllowed() {
|
||||||
|
return this.railSystem.components && this.railSystem.components.length > 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,106 @@
|
||||||
|
<template>
|
||||||
|
<div class="border p-2">
|
||||||
|
<input type="text" class="form-control mb-2" placeholder="Search for components" v-model="searchQuery" @keyup="refreshComponents()" />
|
||||||
|
<ul class="list-group list-group-flush" style="overflow: auto; max-height: 200px">
|
||||||
|
<li
|
||||||
|
class="list-group-item"
|
||||||
|
v-for="component in possibleComponents"
|
||||||
|
:key="component.id"
|
||||||
|
>
|
||||||
|
{{component.name}}
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="badge btn btn-sm btn-success float-end"
|
||||||
|
@click="selectComponent(component)"
|
||||||
|
v-if="!isComponentSelected(component)"
|
||||||
|
>
|
||||||
|
Select
|
||||||
|
</button>
|
||||||
|
<span v-if="isComponentSelected(component)" class="badge bg-secondary float-end">Selected</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div v-if="selectedComponents.length > 0" class="mt-2">
|
||||||
|
<span
|
||||||
|
class="badge bg-secondary me-1 mb-1"
|
||||||
|
v-for="component in selectedComponents"
|
||||||
|
:key="component.id"
|
||||||
|
>
|
||||||
|
{{component.name}}
|
||||||
|
<button class="badge rounded-pill bg-danger" @click="deselectComponent(component)">
|
||||||
|
X
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="btn btn-sm btn-secondary"
|
||||||
|
type="button"
|
||||||
|
v-if="selectedComponents.length > 1"
|
||||||
|
@click="selectedComponents.length = 0"
|
||||||
|
>
|
||||||
|
Clear All
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {RailSystem} from "../../../api/railSystems";
|
||||||
|
import {searchComponents} from "../../../api/components";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ComponentSelector",
|
||||||
|
props: {
|
||||||
|
railSystem: {
|
||||||
|
type: RailSystem,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
modelValue: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ["update:modelValue"],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
searchQuery: null,
|
||||||
|
selectedComponents: [],
|
||||||
|
possibleComponents: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.refreshComponents();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
refreshComponents() {
|
||||||
|
searchComponents(this.railSystem, this.searchQuery)
|
||||||
|
.then(page => this.possibleComponents = page.content);
|
||||||
|
},
|
||||||
|
isComponentSelected(component) {
|
||||||
|
return this.selectedComponents.some(c => c.id === component.id);
|
||||||
|
},
|
||||||
|
selectComponent(component) {
|
||||||
|
if (this.isComponentSelected(component)) return;
|
||||||
|
this.selectedComponents.push(component);
|
||||||
|
this.selectedComponents.sort((a, b) => {
|
||||||
|
const nameA = a.name.toUpperCase();
|
||||||
|
const nameB = b.name.toUpperCase();
|
||||||
|
if (nameA < nameB) return -1;
|
||||||
|
if (nameA > nameB) return 1;
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
this.$emit("update:modelValue", this.selectedComponents);
|
||||||
|
},
|
||||||
|
deselectComponent(component) {
|
||||||
|
const idx = this.selectedComponents.findIndex(c => c.id === component.id);
|
||||||
|
if (idx > -1) {
|
||||||
|
this.selectedComponents.splice(idx, 1);
|
||||||
|
this.$emit("update:modelValue", this.selectedComponents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -1,22 +1,11 @@
|
||||||
import { createApp } from 'vue';
|
import {createApp} from 'vue';
|
||||||
import { createPinia } from 'pinia';
|
import {createPinia} from 'pinia';
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import "bootstrap/dist/css/bootstrap.min.css";
|
import "bootstrap/dist/css/bootstrap.min.css";
|
||||||
import "bootstrap";
|
import "bootstrap";
|
||||||
import {useRailSystemsStore} from "./stores/railSystemsStore";
|
|
||||||
|
|
||||||
|
|
||||||
const pinia = createPinia();
|
const pinia = createPinia();
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
app.use(pinia);
|
app.use(pinia);
|
||||||
|
|
||||||
// Configure rail system updates.
|
|
||||||
const rsStore = useRailSystemsStore();
|
|
||||||
rsStore.$subscribe(mutation => {
|
|
||||||
const evt = mutation.events;
|
|
||||||
if (evt.key === "selectedRailSystem" && evt.newValue !== null) {
|
|
||||||
rsStore.onSelectedRailSystemChanged();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
|
|
|
@ -5,14 +5,12 @@ import {closeWebsocketConnection, establishWebsocketConnection} from "../api/web
|
||||||
|
|
||||||
export const useRailSystemsStore = defineStore('RailSystemsStore', {
|
export const useRailSystemsStore = defineStore('RailSystemsStore', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
|
/**
|
||||||
|
* @type {RailSystem[]}
|
||||||
|
*/
|
||||||
railSystems: [],
|
railSystems: [],
|
||||||
/**
|
/**
|
||||||
* @type {{
|
* @type {RailSystem | null}
|
||||||
* segments: [Object],
|
|
||||||
* components: [Object],
|
|
||||||
* selectedComponent: Object | null,
|
|
||||||
* websocket: WebSocket | null
|
|
||||||
* } | null}
|
|
||||||
*/
|
*/
|
||||||
selectedRailSystem: null
|
selectedRailSystem: null
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -29,11 +29,12 @@ public class ComponentsApiController {
|
||||||
|
|
||||||
@GetMapping(path = "/search")
|
@GetMapping(path = "/search")
|
||||||
public Page<SimpleComponentResponse> searchComponents(
|
public Page<SimpleComponentResponse> searchComponents(
|
||||||
|
@PathVariable long rsId,
|
||||||
@RequestParam(name = "q", required = false) String searchQuery,
|
@RequestParam(name = "q", required = false) String searchQuery,
|
||||||
@PageableDefault(sort = "name")
|
@PageableDefault(sort = "name")
|
||||||
Pageable pageable
|
Pageable pageable
|
||||||
) {
|
) {
|
||||||
return componentService.search(searchQuery, pageable);
|
return componentService.search(rsId, searchQuery, pageable);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping(path = "/{cId}")
|
@GetMapping(path = "/{cId}")
|
||||||
|
|
|
@ -16,6 +16,8 @@ import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.web.server.ResponseStatusException;
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
|
|
||||||
|
import javax.persistence.criteria.Predicate;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -42,12 +44,14 @@ public class ComponentService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
public Page<SimpleComponentResponse> search(String searchQuery, Pageable pageable) {
|
public Page<SimpleComponentResponse> search(long rsId, String searchQuery, Pageable pageable) {
|
||||||
return componentRepository.findAll((root, query, cb) -> {
|
return componentRepository.findAll((root, query, cb) -> {
|
||||||
|
List<Predicate> predicates = new ArrayList<>(2);
|
||||||
|
predicates.add(cb.equal(root.get("railSystem").get("id"), rsId));
|
||||||
if (searchQuery != null && !searchQuery.isBlank()) {
|
if (searchQuery != null && !searchQuery.isBlank()) {
|
||||||
return cb.like(cb.lower(root.get("name")), '%' + searchQuery.toLowerCase() + '%');
|
predicates.add(cb.like(cb.lower(root.get("name")), '%' + searchQuery.toLowerCase() + '%'));
|
||||||
}
|
}
|
||||||
return cb.and();
|
return cb.and(predicates.toArray(new Predicate[0]));
|
||||||
}, pageable).map(SimpleComponentResponse::new);
|
}, pageable).map(SimpleComponentResponse::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue