perf(ui): use computed ref when possible
[e-mobility-charging-stations-simulator.git] / ui / web / src / views / ChargingStationsView.vue
1 <template>
2 <Container id="charging-stations-container">
3 <Container id="buttons-container">
4 <Container
5 v-show="Array.isArray(uiServerConfigurations) && uiServerConfigurations.length > 1"
6 id="ui-server-container"
7 >
8 <select
9 id="ui-server-selector"
10 v-model="state.uiServerIndex"
11 @change="
12 () => {
13 if (
14 getFromLocalStorage<number>('uiServerConfigurationIndex', 0) !== state.uiServerIndex
15 ) {
16 app?.appContext.config.globalProperties.$uiClient.setConfiguration(
17 app?.appContext.config.globalProperties.$configuration.uiServer[
18 state.uiServerIndex
19 ]
20 )
21 initializeWSEventListeners()
22 app?.appContext.config.globalProperties.$uiClient.registerWSEventListener(
23 'open',
24 () => {
25 setToLocalStorage<number>('uiServerConfigurationIndex', state.uiServerIndex)
26 clearToggleButtons()
27 $router.currentRoute.value.name !== 'charging-stations' &&
28 $router.push({ name: 'charging-stations' })
29 },
30 { once: true }
31 )
32 app?.appContext.config.globalProperties.$uiClient.registerWSEventListener(
33 'error',
34 () => {
35 state.uiServerIndex = getFromLocalStorage<number>(
36 'uiServerConfigurationIndex',
37 0
38 )
39 app?.appContext.config.globalProperties.$uiClient.setConfiguration(
40 app?.appContext.config.globalProperties.$configuration.uiServer[
41 getFromLocalStorage<number>('uiServerConfigurationIndex', 0)
42 ]
43 )
44 initializeWSEventListeners()
45 },
46 { once: true }
47 )
48 }
49 }
50 "
51 >
52 <option
53 v-for="uiServerConfiguration in uiServerConfigurations"
54 :value="uiServerConfiguration.index"
55 >
56 {{
57 uiServerConfiguration.configuration.name ?? uiServerConfiguration.configuration.host
58 }}
59 </option>
60 </select>
61 </Container>
62 <ToggleButton
63 :id="'simulator'"
64 :key="state.renderSimulator"
65 :status="state.simulatorState?.started"
66 :on="() => startSimulator()"
67 :off="() => stopSimulator()"
68 :class="simulatorButtonClass"
69 >
70 {{ simulatorButtonMessage }}
71 </ToggleButton>
72 <ToggleButton
73 :id="'add-charging-stations'"
74 :key="state.renderAddChargingStations"
75 :shared="true"
76 :on="
77 () => {
78 $router.push({ name: 'add-charging-stations' })
79 }
80 "
81 :off="
82 () => {
83 $router.push({ name: 'charging-stations' })
84 }
85 "
86 @clicked="
87 () => {
88 state.renderChargingStations = randomUUID()
89 }
90 "
91 >
92 Add Charging Stations
93 </ToggleButton>
94 <ReloadButton
95 id="reload-button"
96 :loading="state.loading"
97 @click="loadChargingStations(() => (state.renderChargingStations = randomUUID()))"
98 />
99 </Container>
100 <CSTable
101 v-show="
102 Array.isArray(app?.appContext.config.globalProperties.$chargingStations) &&
103 app.appContext.config.globalProperties.$chargingStations.length > 0
104 "
105 :key="state.renderChargingStations"
106 :charging-stations="app?.appContext.config.globalProperties.$chargingStations"
107 @need-refresh="
108 () => {
109 state.renderAddChargingStations = randomUUID()
110 state.renderChargingStations = randomUUID()
111 }
112 "
113 />
114 </Container>
115 </template>
116
117 <script setup lang="ts">
118 import { computed, getCurrentInstance, onMounted, ref } from 'vue'
119 import { useToast } from 'vue-toast-notification'
120 import CSTable from '@/components/charging-stations/CSTable.vue'
121 import type { ResponsePayload, SimulatorState, UIServerConfigurationSection } from '@/types'
122 import Container from '@/components/Container.vue'
123 import ReloadButton from '@/components/buttons/ReloadButton.vue'
124 import {
125 deleteFromLocalStorage,
126 getFromLocalStorage,
127 getLocalStorage,
128 randomUUID,
129 setToLocalStorage
130 } from '@/composables'
131 import ToggleButton from '@/components/buttons/ToggleButton.vue'
132
133 const state = ref<{
134 renderSimulator: `${string}-${string}-${string}-${string}-${string}`
135 renderAddChargingStations: `${string}-${string}-${string}-${string}-${string}`
136 renderChargingStations: `${string}-${string}-${string}-${string}-${string}`
137 loading: boolean
138 simulatorState?: SimulatorState
139 uiServerIndex: number
140 }>({
141 renderSimulator: randomUUID(),
142 renderAddChargingStations: randomUUID(),
143 renderChargingStations: randomUUID(),
144 loading: false,
145 uiServerIndex: getFromLocalStorage<number>('uiServerConfigurationIndex', 0)
146 })
147
148 const simulatorButtonClass = computed<string>(() =>
149 state.value.simulatorState?.started === true ? 'simulator-stop-button' : 'simulator-start-button'
150 )
151 const simulatorButtonMessage = computed<string>(
152 () =>
153 `${state.value.simulatorState?.started === true ? 'Stop' : 'Start'} Simulator ${state.value.simulatorState?.version != null ? `(${state.value.simulatorState.version})` : ''}`
154 )
155
156 const app = getCurrentInstance()
157
158 const clearToggleButtons = (): void => {
159 for (const key in getLocalStorage()) {
160 if (key.includes('toggle-button')) {
161 deleteFromLocalStorage(key)
162 }
163 }
164 }
165
166 const clearChargingStations = (): void => {
167 app!.appContext.config.globalProperties.$chargingStations = []
168 state.value.renderChargingStations = randomUUID()
169 }
170
171 const uiClient = app?.appContext.config.globalProperties.$uiClient
172
173 const getSimulatorState = (): void => {
174 uiClient
175 .simulatorState()
176 .then((response: ResponsePayload) => {
177 state.value.simulatorState = response.state as SimulatorState
178 })
179 .catch((error: Error) => {
180 $toast.error('Error at fetching simulator state')
181 console.error('Error at fetching simulator state:', error)
182 })
183 .finally(() => {
184 state.value.renderSimulator = randomUUID()
185 })
186 }
187
188 const initializeWSEventListeners = () => {
189 app?.appContext.config.globalProperties.$uiClient.registerWSEventListener('open', () => {
190 getSimulatorState()
191 uiClient
192 .listTemplates()
193 .then((response: ResponsePayload) => {
194 if (app != null) {
195 app.appContext.config.globalProperties.$templates = response.templates
196 }
197 })
198 .catch((error: Error) => {
199 if (app != null) {
200 app.appContext.config.globalProperties.$templates = []
201 }
202 $toast.error('Error at fetching charging station templates')
203 console.error('Error at fetching charging station templates:', error)
204 })
205 .finally(() => {
206 state.value.renderAddChargingStations = randomUUID()
207 })
208 loadChargingStations(() => {
209 state.value.renderChargingStations = randomUUID()
210 })
211 })
212 app?.appContext.config.globalProperties.$uiClient.registerWSEventListener(
213 'error',
214 clearChargingStations
215 )
216 app?.appContext.config.globalProperties.$uiClient.registerWSEventListener(
217 'close',
218 clearChargingStations
219 )
220 }
221
222 onMounted(() => {
223 initializeWSEventListeners()
224 })
225
226 const uiServerConfigurations: { index: number; configuration: UIServerConfigurationSection }[] =
227 app?.appContext.config.globalProperties.$configuration.uiServer.map(
228 (configuration: UIServerConfigurationSection, index: number) => ({
229 index,
230 configuration
231 })
232 )
233
234 const $toast = useToast()
235
236 const loadChargingStations = (renderCallback?: () => void): void => {
237 if (state.value.loading === false) {
238 state.value.loading = true
239 uiClient
240 .listChargingStations()
241 .then((response: ResponsePayload) => {
242 if (app != null) {
243 app.appContext.config.globalProperties.$chargingStations = response.chargingStations
244 }
245 })
246 .catch((error: Error) => {
247 if (app != null) {
248 app.appContext.config.globalProperties.$chargingStations = []
249 }
250 $toast.error('Error at fetching charging stations')
251 console.error('Error at fetching charging stations:', error)
252 })
253 .finally(() => {
254 if (renderCallback != null) {
255 renderCallback()
256 }
257 state.value.loading = false
258 })
259 }
260 }
261
262 const startSimulator = (): void => {
263 uiClient
264 .startSimulator()
265 .then(() => {
266 $toast.success('Simulator successfully started')
267 })
268 .catch((error: Error) => {
269 $toast.error('Error at starting simulator')
270 console.error('Error at starting simulator:', error)
271 })
272 .finally(() => {
273 getSimulatorState()
274 })
275 }
276 const stopSimulator = (): void => {
277 uiClient
278 .stopSimulator()
279 .then(() => {
280 if (app != null) {
281 app.appContext.config.globalProperties.$chargingStations = []
282 }
283 $toast.success('Simulator successfully stopped')
284 })
285 .catch((error: Error) => {
286 $toast.error('Error at stopping simulator')
287 console.error('Error at stopping simulator:', error)
288 })
289 .finally(() => {
290 getSimulatorState()
291 })
292 }
293 </script>
294
295 <style>
296 #charging-stations-container {
297 height: fit-content;
298 width: 100%;
299 display: flex;
300 flex-direction: column;
301 }
302
303 #ui-server-container {
304 display: flex;
305 justify-content: center;
306 border-style: outset;
307 }
308
309 #ui-server-selector {
310 width: 100%;
311 background-color: rgb(239, 239, 239);
312 font: small-caption;
313 text-align: center;
314 }
315
316 #ui-server-selector:hover {
317 background-color: rgb(229, 229, 229);
318 }
319
320 #buttons-container {
321 display: flex;
322 flex-direction: row;
323 }
324
325 .simulator-start-button {
326 color: ivory;
327 background-color: green;
328 }
329
330 .simulator-start-button:hover {
331 background-color: rgb(0, 98, 0);
332 }
333
334 .simulator-stop-button {
335 color: ivory;
336 background-color: red;
337 }
338
339 .simulator-stop-button:hover {
340 background-color: rgb(225, 0, 0);
341 }
342
343 #action-button {
344 flex: none;
345 }
346
347 #reload-button {
348 color: ivory;
349 background-color: blue;
350 font-size: 2rem;
351 }
352
353 #reload-button:hover {
354 background-color: rgb(0, 0, 225);
355 }
356
357 #reload-button:active {
358 background-color: darkblue;
359 }
360
361 #action {
362 min-width: max-content;
363 color: ivory;
364 background-color: black;
365 padding: 0.8%;
366 }
367 </style>