Compare commits
175 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 31f4a2e5b2 | |||
| 22514c3263 | |||
| 0df673467c | |||
| 6377e13809 | |||
| 13fa633db4 | |||
| dd9661551c | |||
| 495012a5ca | |||
| 3cfccb1bb4 | |||
| d2a8cdb2b0 | |||
| c33b5ef8c1 | |||
| 52d1771c21 | |||
| cac4345683 | |||
| 6fd9e21213 | |||
| 421add1ec1 | |||
| 4ac92198b7 | |||
| 5105229eef | |||
| 2ec02080c3 | |||
| 95b2a696e1 | |||
| 091e94e396 | |||
| 43c939bf01 | |||
| 8285d5c579 | |||
| 547248b478 | |||
| e1b9b37ac8 | |||
| c8ec28292b | |||
| 3daf800a89 | |||
| 69d9be0bb3 | |||
| f53f3a18fe | |||
| fac8fced3e | |||
| a3d9e68c8a | |||
| b09761de58 | |||
| 8ac2c68660 | |||
| 4177c6e5f4 | |||
| b8f135a454 | |||
| f0863b2459 | |||
| 55b4732992 | |||
| 7b3dcea89e | |||
| f4b0c39185 | |||
| 275d602f97 | |||
| c93514fdf0 | |||
| 0861d92e4b | |||
| bdfd73f4be | |||
| df86364c51 | |||
| 631bb20c61 | |||
| bed79ed2d0 | |||
| 2a07471e12 | |||
| cfe188d0dc | |||
| d9865be83e | |||
| 9155fd9f8d | |||
| 4674bf886e | |||
| 289fd310df | |||
| b04797052f | |||
| 7079f20791 | |||
| c244275aee | |||
| fbc9785341 | |||
| 9fd02c2336 | |||
| c031dd55c1 | |||
| b0870699a4 | |||
| a8da634b0e | |||
| 8920b1e5e8 | |||
| 4fa1c05831 | |||
| ecef2d5ee4 | |||
| 1749871d08 | |||
| 5545616706 | |||
| b35bb03868 | |||
| b9521918cb | |||
| 7ad17fc2c5 | |||
| a80144cb1c | |||
| 1227cdb94a | |||
| b33594fd6f | |||
| 9f8656e590 | |||
| 81cd165fe7 | |||
| 41e4b45599 | |||
| 462dd7dd7a | |||
| 9837ae97e1 | |||
| a818cd980b | |||
| 573ebc233b | |||
| aae47c6abd | |||
| 24c9b62162 | |||
| 481d43b6d8 | |||
| 4969a433cc | |||
| 8a2b453dc6 | |||
| 86d178ef56 | |||
| 7769477508 | |||
| 551b60c733 | |||
| 80a5b56785 | |||
| 6bd62f13a1 | |||
| 42591f6e76 | |||
| 4ca0f09e75 | |||
| 02c3629c00 | |||
| 9c4c806f0e | |||
| 58d6a97762 | |||
| cd71c78eb4 | |||
| 300e70dcfe | |||
| 09b31f7914 | |||
| c29c3c6abe | |||
| 916f6070ac | |||
| a74ab6eb2a | |||
| 985c699ced | |||
| 7e0e9146a5 | |||
| 30a0f05922 | |||
| a30e04ca96 | |||
| 1852d3e234 | |||
| a17bf6c03f | |||
| 766b08bc15 | |||
| cd1a4fa734 | |||
| 619ce97b52 | |||
| acbe761068 | |||
| 47d35f335f | |||
| 8fda8fa0df | |||
| 71d697eda5 | |||
| f2b1fc5369 | |||
| 4a9b142e16 | |||
| 08d8bf3c57 | |||
| 0ee90357aa | |||
| c8964dc20f | |||
| 6a62276d95 | |||
| b8550eed9a | |||
| 27b23ccc95 | |||
| b49517aded | |||
| ed2b8be4dc | |||
| 54c1dbbf15 | |||
| 0ac7ba51e5 | |||
| bdf85cd8ec | |||
| 63b268d9b9 | |||
| d73c8ef112 | |||
| 3d1c66b420 | |||
| b3f7108979 | |||
| feabfd29e0 | |||
| f17fedc976 | |||
| c83c75e014 | |||
| e57143f517 | |||
| fb45a783ee | |||
| 71476e9552 | |||
| 922a338143 | |||
| 231d36e877 | |||
| 27d6ac9f14 | |||
| a6029da2cc | |||
| a3f3790205 | |||
| ebfb24f729 | |||
| e521736618 | |||
| fc7662e431 | |||
| a459fdf178 | |||
| 4e7fba89ee | |||
| 6084e5876d | |||
| 44f548c7b7 | |||
| 59a5fbe5ac | |||
| c252213ed9 | |||
| fb56378f18 | |||
| e9635eae06 | |||
| 1fc98a8f99 | |||
| c9de1a48ce | |||
| fee9774f88 | |||
| 7c974e8d0e | |||
| c84fbbcf42 | |||
| 45af649505 | |||
| 6c1e00d002 | |||
| 69ff85cfb1 | |||
| bdc2ca784c | |||
| dbd73d448d | |||
| 26b1ec246d | |||
| 8190dfa2cb | |||
| 44df685606 | |||
| 785a42b849 | |||
| ccfcca8728 | |||
| d9a7ba122c | |||
| bf8d4a9ef4 | |||
| 6ea1e91d1d | |||
| 813b557455 | |||
| 834b14da69 | |||
| c809b2146d | |||
| 33b98ca313 | |||
| bcb9c63cb0 | |||
| 17d77a80d8 | |||
| 65b159f8fd | |||
| 44f6cf4232 |
@@ -1,6 +1,3 @@
|
|||||||
# This file was auto-generated by the Firebase CLI
|
|
||||||
# https://github.com/firebase/firebase-tools
|
|
||||||
|
|
||||||
name: Deploy to Firebase Hosting on merge
|
name: Deploy to Firebase Hosting on merge
|
||||||
'on':
|
'on':
|
||||||
push:
|
push:
|
||||||
@@ -11,10 +8,10 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- run: npm ci && npm run build
|
- run: yarn && yarn build
|
||||||
- uses: FirebaseExtended/action-hosting-deploy@v0
|
- uses: FirebaseExtended/action-hosting-deploy@v0
|
||||||
with:
|
with:
|
||||||
repoToken: '${{ secrets.GITHUB_TOKEN }}'
|
repoToken: '${{ secrets.GITHUB_TOKEN }}'
|
||||||
firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_STACJOWNIK_TD2 }}'
|
firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_STACJOWNIK_TD2 }}'
|
||||||
channelId: live
|
channelId: live
|
||||||
projectId: stacjownik-td2
|
projectId: stacjownik-td2
|
||||||
@@ -9,7 +9,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- run: npm ci && npm run build
|
- run: yarn && yarn build
|
||||||
- uses: FirebaseExtended/action-hosting-deploy@v0
|
- uses: FirebaseExtended/action-hosting-deploy@v0
|
||||||
with:
|
with:
|
||||||
repoToken: '${{ secrets.GITHUB_TOKEN }}'
|
repoToken: '${{ secrets.GITHUB_TOKEN }}'
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ node_modules
|
|||||||
|
|
||||||
# Env
|
# Env
|
||||||
.env
|
.env
|
||||||
|
.env.*
|
||||||
|
|
||||||
.fake
|
.fake
|
||||||
.ionide
|
.ionide
|
||||||
|
|
||||||
|
# api-mock
|
||||||
|
/api-mock/endpoints/
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { existsSync } from 'fs';
|
||||||
|
import { mkdir, writeFile } from 'fs/promises';
|
||||||
|
|
||||||
|
async function fetchJSONEndpointData(url, fileName) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(url);
|
||||||
|
const data = await res.json();
|
||||||
|
await writeFile(`./endpoints/${fileName}`, JSON.stringify(data));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
if (!existsSync('endpoints')) await mkdir('endpoints');
|
||||||
|
|
||||||
|
Promise.all(
|
||||||
|
['getActiveData', 'getDonators', 'getSceneries', 'getVehicles'].map((endpointName) =>
|
||||||
|
fetchJSONEndpointData(
|
||||||
|
`https://stacjownik.spythere.eu/api/${endpointName}`,
|
||||||
|
`${endpointName}.json`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).then(() => {
|
||||||
|
console.log('Endpoints downloaded!');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import express from 'express';
|
||||||
|
import path from 'path';
|
||||||
|
import { cwd } from 'process';
|
||||||
|
import cors from 'cors';
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
app.use(cors());
|
||||||
|
|
||||||
|
app.get('/api/getActiveData', (_, res) => {
|
||||||
|
res.sendFile(path.join(cwd(), 'endpoints', 'getActiveData.json'));
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/api/getSceneries', (_, res) => {
|
||||||
|
res.sendFile(path.join(cwd(), 'endpoints', 'getSceneries.json'));
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/api/getVehicles', (_, res) => {
|
||||||
|
res.sendFile(path.join(cwd(), 'endpoints', 'getVehicles.json'));
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/api/getDonators', (_, res) => {
|
||||||
|
res.sendFile(path.join(cwd(), 'endpoints', 'getDonators.json'));
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(3123, () => {
|
||||||
|
console.log('Mocking API server...');
|
||||||
|
});
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"name": "api-mock",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"type": "module",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"start": "node index.js",
|
||||||
|
"fetch": "node fetchEndpoints.js"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"express": "^4.18.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,481 @@
|
|||||||
|
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||||
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
|
accepts@~1.3.8:
|
||||||
|
version "1.3.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e"
|
||||||
|
integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==
|
||||||
|
dependencies:
|
||||||
|
mime-types "~2.1.34"
|
||||||
|
negotiator "0.6.3"
|
||||||
|
|
||||||
|
array-flatten@1.1.1:
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
|
||||||
|
integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==
|
||||||
|
|
||||||
|
body-parser@1.20.3:
|
||||||
|
version "1.20.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6"
|
||||||
|
integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==
|
||||||
|
dependencies:
|
||||||
|
bytes "3.1.2"
|
||||||
|
content-type "~1.0.5"
|
||||||
|
debug "2.6.9"
|
||||||
|
depd "2.0.0"
|
||||||
|
destroy "1.2.0"
|
||||||
|
http-errors "2.0.0"
|
||||||
|
iconv-lite "0.4.24"
|
||||||
|
on-finished "2.4.1"
|
||||||
|
qs "6.13.0"
|
||||||
|
raw-body "2.5.2"
|
||||||
|
type-is "~1.6.18"
|
||||||
|
unpipe "1.0.0"
|
||||||
|
|
||||||
|
bytes@3.1.2:
|
||||||
|
version "3.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5"
|
||||||
|
integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==
|
||||||
|
|
||||||
|
call-bind@^1.0.7:
|
||||||
|
version "1.0.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9"
|
||||||
|
integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==
|
||||||
|
dependencies:
|
||||||
|
es-define-property "^1.0.0"
|
||||||
|
es-errors "^1.3.0"
|
||||||
|
function-bind "^1.1.2"
|
||||||
|
get-intrinsic "^1.2.4"
|
||||||
|
set-function-length "^1.2.1"
|
||||||
|
|
||||||
|
content-disposition@0.5.4:
|
||||||
|
version "0.5.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe"
|
||||||
|
integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==
|
||||||
|
dependencies:
|
||||||
|
safe-buffer "5.2.1"
|
||||||
|
|
||||||
|
content-type@~1.0.4, content-type@~1.0.5:
|
||||||
|
version "1.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918"
|
||||||
|
integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==
|
||||||
|
|
||||||
|
cookie-signature@1.0.6:
|
||||||
|
version "1.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
|
||||||
|
integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==
|
||||||
|
|
||||||
|
cookie@0.6.0:
|
||||||
|
version "0.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051"
|
||||||
|
integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==
|
||||||
|
|
||||||
|
cors@^2.8.5:
|
||||||
|
version "2.8.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29"
|
||||||
|
integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==
|
||||||
|
dependencies:
|
||||||
|
object-assign "^4"
|
||||||
|
vary "^1"
|
||||||
|
|
||||||
|
debug@2.6.9:
|
||||||
|
version "2.6.9"
|
||||||
|
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||||
|
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
|
||||||
|
dependencies:
|
||||||
|
ms "2.0.0"
|
||||||
|
|
||||||
|
define-data-property@^1.1.4:
|
||||||
|
version "1.1.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e"
|
||||||
|
integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==
|
||||||
|
dependencies:
|
||||||
|
es-define-property "^1.0.0"
|
||||||
|
es-errors "^1.3.0"
|
||||||
|
gopd "^1.0.1"
|
||||||
|
|
||||||
|
depd@2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
|
||||||
|
integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
|
||||||
|
|
||||||
|
destroy@1.2.0:
|
||||||
|
version "1.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015"
|
||||||
|
integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==
|
||||||
|
|
||||||
|
ee-first@1.1.1:
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
|
||||||
|
integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
|
||||||
|
|
||||||
|
encodeurl@~1.0.2:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
|
||||||
|
integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
|
||||||
|
|
||||||
|
encodeurl@~2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58"
|
||||||
|
integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==
|
||||||
|
|
||||||
|
es-define-property@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845"
|
||||||
|
integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==
|
||||||
|
dependencies:
|
||||||
|
get-intrinsic "^1.2.4"
|
||||||
|
|
||||||
|
es-errors@^1.3.0:
|
||||||
|
version "1.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f"
|
||||||
|
integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
|
||||||
|
|
||||||
|
escape-html@~1.0.3:
|
||||||
|
version "1.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
|
||||||
|
integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==
|
||||||
|
|
||||||
|
etag@~1.8.1:
|
||||||
|
version "1.8.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
|
||||||
|
integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==
|
||||||
|
|
||||||
|
express@^4.18.3:
|
||||||
|
version "4.21.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/express/-/express-4.21.0.tgz#d57cb706d49623d4ac27833f1cbc466b668eb915"
|
||||||
|
integrity sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==
|
||||||
|
dependencies:
|
||||||
|
accepts "~1.3.8"
|
||||||
|
array-flatten "1.1.1"
|
||||||
|
body-parser "1.20.3"
|
||||||
|
content-disposition "0.5.4"
|
||||||
|
content-type "~1.0.4"
|
||||||
|
cookie "0.6.0"
|
||||||
|
cookie-signature "1.0.6"
|
||||||
|
debug "2.6.9"
|
||||||
|
depd "2.0.0"
|
||||||
|
encodeurl "~2.0.0"
|
||||||
|
escape-html "~1.0.3"
|
||||||
|
etag "~1.8.1"
|
||||||
|
finalhandler "1.3.1"
|
||||||
|
fresh "0.5.2"
|
||||||
|
http-errors "2.0.0"
|
||||||
|
merge-descriptors "1.0.3"
|
||||||
|
methods "~1.1.2"
|
||||||
|
on-finished "2.4.1"
|
||||||
|
parseurl "~1.3.3"
|
||||||
|
path-to-regexp "0.1.10"
|
||||||
|
proxy-addr "~2.0.7"
|
||||||
|
qs "6.13.0"
|
||||||
|
range-parser "~1.2.1"
|
||||||
|
safe-buffer "5.2.1"
|
||||||
|
send "0.19.0"
|
||||||
|
serve-static "1.16.2"
|
||||||
|
setprototypeof "1.2.0"
|
||||||
|
statuses "2.0.1"
|
||||||
|
type-is "~1.6.18"
|
||||||
|
utils-merge "1.0.1"
|
||||||
|
vary "~1.1.2"
|
||||||
|
|
||||||
|
finalhandler@1.3.1:
|
||||||
|
version "1.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.3.1.tgz#0c575f1d1d324ddd1da35ad7ece3df7d19088019"
|
||||||
|
integrity sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==
|
||||||
|
dependencies:
|
||||||
|
debug "2.6.9"
|
||||||
|
encodeurl "~2.0.0"
|
||||||
|
escape-html "~1.0.3"
|
||||||
|
on-finished "2.4.1"
|
||||||
|
parseurl "~1.3.3"
|
||||||
|
statuses "2.0.1"
|
||||||
|
unpipe "~1.0.0"
|
||||||
|
|
||||||
|
forwarded@0.2.0:
|
||||||
|
version "0.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
|
||||||
|
integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==
|
||||||
|
|
||||||
|
fresh@0.5.2:
|
||||||
|
version "0.5.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
|
||||||
|
integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==
|
||||||
|
|
||||||
|
function-bind@^1.1.2:
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
|
||||||
|
integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
|
||||||
|
|
||||||
|
get-intrinsic@^1.1.3, get-intrinsic@^1.2.4:
|
||||||
|
version "1.2.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd"
|
||||||
|
integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==
|
||||||
|
dependencies:
|
||||||
|
es-errors "^1.3.0"
|
||||||
|
function-bind "^1.1.2"
|
||||||
|
has-proto "^1.0.1"
|
||||||
|
has-symbols "^1.0.3"
|
||||||
|
hasown "^2.0.0"
|
||||||
|
|
||||||
|
gopd@^1.0.1:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c"
|
||||||
|
integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==
|
||||||
|
dependencies:
|
||||||
|
get-intrinsic "^1.1.3"
|
||||||
|
|
||||||
|
has-property-descriptors@^1.0.2:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854"
|
||||||
|
integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==
|
||||||
|
dependencies:
|
||||||
|
es-define-property "^1.0.0"
|
||||||
|
|
||||||
|
has-proto@^1.0.1:
|
||||||
|
version "1.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd"
|
||||||
|
integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==
|
||||||
|
|
||||||
|
has-symbols@^1.0.3:
|
||||||
|
version "1.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8"
|
||||||
|
integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
|
||||||
|
|
||||||
|
hasown@^2.0.0:
|
||||||
|
version "2.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003"
|
||||||
|
integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==
|
||||||
|
dependencies:
|
||||||
|
function-bind "^1.1.2"
|
||||||
|
|
||||||
|
http-errors@2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3"
|
||||||
|
integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==
|
||||||
|
dependencies:
|
||||||
|
depd "2.0.0"
|
||||||
|
inherits "2.0.4"
|
||||||
|
setprototypeof "1.2.0"
|
||||||
|
statuses "2.0.1"
|
||||||
|
toidentifier "1.0.1"
|
||||||
|
|
||||||
|
iconv-lite@0.4.24:
|
||||||
|
version "0.4.24"
|
||||||
|
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
|
||||||
|
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
|
||||||
|
dependencies:
|
||||||
|
safer-buffer ">= 2.1.2 < 3"
|
||||||
|
|
||||||
|
inherits@2.0.4:
|
||||||
|
version "2.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||||
|
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||||
|
|
||||||
|
ipaddr.js@1.9.1:
|
||||||
|
version "1.9.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
|
||||||
|
integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
|
||||||
|
|
||||||
|
media-typer@0.3.0:
|
||||||
|
version "0.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
|
||||||
|
integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==
|
||||||
|
|
||||||
|
merge-descriptors@1.0.3:
|
||||||
|
version "1.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5"
|
||||||
|
integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==
|
||||||
|
|
||||||
|
methods@~1.1.2:
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
|
||||||
|
integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==
|
||||||
|
|
||||||
|
mime-db@1.52.0:
|
||||||
|
version "1.52.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
|
||||||
|
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
|
||||||
|
|
||||||
|
mime-types@~2.1.24, mime-types@~2.1.34:
|
||||||
|
version "2.1.35"
|
||||||
|
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
|
||||||
|
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
|
||||||
|
dependencies:
|
||||||
|
mime-db "1.52.0"
|
||||||
|
|
||||||
|
mime@1.6.0:
|
||||||
|
version "1.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
|
||||||
|
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
|
||||||
|
|
||||||
|
ms@2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||||
|
integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==
|
||||||
|
|
||||||
|
ms@2.1.3:
|
||||||
|
version "2.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
|
||||||
|
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
|
||||||
|
|
||||||
|
negotiator@0.6.3:
|
||||||
|
version "0.6.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
|
||||||
|
integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
|
||||||
|
|
||||||
|
object-assign@^4:
|
||||||
|
version "4.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||||
|
integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
|
||||||
|
|
||||||
|
object-inspect@^1.13.1:
|
||||||
|
version "1.13.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.2.tgz#dea0088467fb991e67af4058147a24824a3043ff"
|
||||||
|
integrity sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==
|
||||||
|
|
||||||
|
on-finished@2.4.1:
|
||||||
|
version "2.4.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f"
|
||||||
|
integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==
|
||||||
|
dependencies:
|
||||||
|
ee-first "1.1.1"
|
||||||
|
|
||||||
|
parseurl@~1.3.3:
|
||||||
|
version "1.3.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
|
||||||
|
integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
|
||||||
|
|
||||||
|
path-to-regexp@0.1.10:
|
||||||
|
version "0.1.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.10.tgz#67e9108c5c0551b9e5326064387de4763c4d5f8b"
|
||||||
|
integrity sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==
|
||||||
|
|
||||||
|
proxy-addr@~2.0.7:
|
||||||
|
version "2.0.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025"
|
||||||
|
integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==
|
||||||
|
dependencies:
|
||||||
|
forwarded "0.2.0"
|
||||||
|
ipaddr.js "1.9.1"
|
||||||
|
|
||||||
|
qs@6.13.0:
|
||||||
|
version "6.13.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906"
|
||||||
|
integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==
|
||||||
|
dependencies:
|
||||||
|
side-channel "^1.0.6"
|
||||||
|
|
||||||
|
range-parser@~1.2.1:
|
||||||
|
version "1.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
|
||||||
|
integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
|
||||||
|
|
||||||
|
raw-body@2.5.2:
|
||||||
|
version "2.5.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a"
|
||||||
|
integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==
|
||||||
|
dependencies:
|
||||||
|
bytes "3.1.2"
|
||||||
|
http-errors "2.0.0"
|
||||||
|
iconv-lite "0.4.24"
|
||||||
|
unpipe "1.0.0"
|
||||||
|
|
||||||
|
safe-buffer@5.2.1:
|
||||||
|
version "5.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
|
||||||
|
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
|
||||||
|
|
||||||
|
"safer-buffer@>= 2.1.2 < 3":
|
||||||
|
version "2.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||||
|
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
||||||
|
|
||||||
|
send@0.19.0:
|
||||||
|
version "0.19.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8"
|
||||||
|
integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==
|
||||||
|
dependencies:
|
||||||
|
debug "2.6.9"
|
||||||
|
depd "2.0.0"
|
||||||
|
destroy "1.2.0"
|
||||||
|
encodeurl "~1.0.2"
|
||||||
|
escape-html "~1.0.3"
|
||||||
|
etag "~1.8.1"
|
||||||
|
fresh "0.5.2"
|
||||||
|
http-errors "2.0.0"
|
||||||
|
mime "1.6.0"
|
||||||
|
ms "2.1.3"
|
||||||
|
on-finished "2.4.1"
|
||||||
|
range-parser "~1.2.1"
|
||||||
|
statuses "2.0.1"
|
||||||
|
|
||||||
|
serve-static@1.16.2:
|
||||||
|
version "1.16.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.2.tgz#b6a5343da47f6bdd2673848bf45754941e803296"
|
||||||
|
integrity sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==
|
||||||
|
dependencies:
|
||||||
|
encodeurl "~2.0.0"
|
||||||
|
escape-html "~1.0.3"
|
||||||
|
parseurl "~1.3.3"
|
||||||
|
send "0.19.0"
|
||||||
|
|
||||||
|
set-function-length@^1.2.1:
|
||||||
|
version "1.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449"
|
||||||
|
integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==
|
||||||
|
dependencies:
|
||||||
|
define-data-property "^1.1.4"
|
||||||
|
es-errors "^1.3.0"
|
||||||
|
function-bind "^1.1.2"
|
||||||
|
get-intrinsic "^1.2.4"
|
||||||
|
gopd "^1.0.1"
|
||||||
|
has-property-descriptors "^1.0.2"
|
||||||
|
|
||||||
|
setprototypeof@1.2.0:
|
||||||
|
version "1.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424"
|
||||||
|
integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==
|
||||||
|
|
||||||
|
side-channel@^1.0.6:
|
||||||
|
version "1.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2"
|
||||||
|
integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==
|
||||||
|
dependencies:
|
||||||
|
call-bind "^1.0.7"
|
||||||
|
es-errors "^1.3.0"
|
||||||
|
get-intrinsic "^1.2.4"
|
||||||
|
object-inspect "^1.13.1"
|
||||||
|
|
||||||
|
statuses@2.0.1:
|
||||||
|
version "2.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63"
|
||||||
|
integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==
|
||||||
|
|
||||||
|
toidentifier@1.0.1:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
|
||||||
|
integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
|
||||||
|
|
||||||
|
type-is@~1.6.18:
|
||||||
|
version "1.6.18"
|
||||||
|
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
|
||||||
|
integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
|
||||||
|
dependencies:
|
||||||
|
media-typer "0.3.0"
|
||||||
|
mime-types "~2.1.24"
|
||||||
|
|
||||||
|
unpipe@1.0.0, unpipe@~1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
|
||||||
|
integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==
|
||||||
|
|
||||||
|
utils-merge@1.0.1:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
|
||||||
|
integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
|
||||||
|
|
||||||
|
vary@^1, vary@~1.1.2:
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
|
||||||
|
integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
|
||||||
@@ -19,7 +19,6 @@
|
|||||||
<link rel="manifest" href="/site.webmanifest" />
|
<link rel="manifest" href="/site.webmanifest" />
|
||||||
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5" />
|
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5" />
|
||||||
<meta name="msapplication-TileColor" content="#da532c" />
|
<meta name="msapplication-TileColor" content="#da532c" />
|
||||||
<meta name="theme-color" content="#222222" />
|
|
||||||
|
|
||||||
<link rel="icon" href="favicon.ico" />
|
<link rel="icon" href="favicon.ico" />
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
{
|
{
|
||||||
"name": "stacjownik",
|
"name": "stacjownik",
|
||||||
"version": "1.24.2",
|
"version": "1.28.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite --mode staging",
|
||||||
|
"dev:mock": "vite --mode development & yarn --cwd ./api-mock start",
|
||||||
|
"dev:fetch": "yarn --cwd ./api-mock fetch",
|
||||||
"build": "vue-tsc --noEmit && vite build",
|
"build": "vue-tsc --noEmit && vite build",
|
||||||
"deploy": "yarn build && firebase deploy --only hosting",
|
"deploy:prod": "yarn build && firebase deploy --only hosting",
|
||||||
|
"deploy:dev": "yarn build && firebase hosting:channel:deploy dev --expires 7d",
|
||||||
"preview": "yarn build && vite preview",
|
"preview": "yarn build && vite preview",
|
||||||
"type-check": "vue-tsc --noEmit -p tsconfig.app.json --composite false",
|
"type-check": "vue-tsc --noEmit -p tsconfig.app.json --composite false",
|
||||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
|
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
|
||||||
@@ -19,25 +23,20 @@
|
|||||||
"showdown": "^2.1.0",
|
"showdown": "^2.1.0",
|
||||||
"vue": "^3.3.4",
|
"vue": "^3.3.4",
|
||||||
"vue-i18n": "^9.4.1",
|
"vue-i18n": "^9.4.1",
|
||||||
"vue-router": "^4.2.4"
|
"vue-router": "^4.4.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rushstack/eslint-patch": "^1.3.3",
|
"@types/node": "^20.14.12",
|
||||||
"@types/node": "^20.6.2",
|
|
||||||
"@types/showdown": "^2.0.6",
|
"@types/showdown": "^2.0.6",
|
||||||
"@vite-pwa/assets-generator": "^0.2.4",
|
"@vite-pwa/assets-generator": "^0.2.4",
|
||||||
"@vitejs/plugin-vue": "^4.3.4",
|
"@vitejs/plugin-vue": "^5.1.0",
|
||||||
"@vue/eslint-config-prettier": "^8.0.0",
|
"@vue/tsconfig": "^0.5.1",
|
||||||
"@vue/eslint-config-typescript": "^12.0.0",
|
"axios": "^1.7.2",
|
||||||
"@vue/tsconfig": "^0.4.0",
|
"prettier": "^3.3.3",
|
||||||
"axios": "^1.5.0",
|
"typescript": "^5.5.4",
|
||||||
"eslint": "^8.49.0",
|
"vite": "^5.3.4",
|
||||||
"eslint-plugin-vue": "^9.17.0",
|
|
||||||
"prettier": "^3.0.3",
|
|
||||||
"typescript": "^5.2.2",
|
|
||||||
"vite": "^4.4.9",
|
|
||||||
"vite-plugin-pwa": "^0.20.0",
|
"vite-plugin-pwa": "^0.20.0",
|
||||||
"vue-tsc": "^1.8.11"
|
"vue-tsc": "^2.0.28"
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
"> 1%",
|
"> 1%",
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="144" height="144" viewBox="0 0 144 144" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M82.7143 61.3284L118.429 7L22 74.9104H68.4286L36.2857 137L122 61.3284H82.7143Z" fill="#FFF500"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 213 B |
@@ -0,0 +1,5 @@
|
|||||||
|
<svg width="144" height="144" viewBox="0 0 144 144" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect x="9.20437" y="2.4661" width="36.5457" height="57.8287" rx="16.7449" transform="matrix(0.869001 -0.494811 0.505207 0.862998 50.006 87.4256)" stroke="white" stroke-width="13.3959"/>
|
||||||
|
<rect x="9.20437" y="2.4661" width="36.5457" height="57.8287" rx="16.7449" transform="matrix(0.869001 -0.494811 0.505207 0.862998 14.9599 29.6039)" stroke="white" stroke-width="13.3959"/>
|
||||||
|
<path d="M65.1133 58.145L79.8524 84.3103" stroke="white" stroke-width="10.0469" stroke-linecap="round" stroke-linejoin="bevel"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 611 B |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 26 KiB |
@@ -13,7 +13,7 @@
|
|||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"theme_color": "#ffc014",
|
"theme_color": "#4d4d4d",
|
||||||
"background_color": "#4d4d4d",
|
"background_color": "#4d4d4d",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"start_url": "."
|
"start_url": "."
|
||||||
|
|||||||
@@ -6,13 +6,7 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<Tooltip />
|
<Tooltip />
|
||||||
|
|
||||||
<transition name="modal-anim">
|
|
||||||
<keep-alive>
|
|
||||||
<TrainModal />
|
|
||||||
</keep-alive>
|
|
||||||
</transition>
|
|
||||||
|
|
||||||
<AppHeader :current-lang="currentLang" @change-lang="changeLang" />
|
<AppHeader :current-lang="currentLang" @change-lang="changeLang" />
|
||||||
|
|
||||||
<main class="app_main">
|
<main class="app_main">
|
||||||
@@ -23,21 +17,12 @@
|
|||||||
</router-view>
|
</router-view>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer class="app_footer">
|
<AppFooter
|
||||||
©
|
:version="VERSION"
|
||||||
<a href="https://td2.info.pl/profile/?u=20777" target="_blank">Spythere</a>
|
:is-on-production-host="isOnProductionHost"
|
||||||
{{ new Date().getUTCFullYear() }} |
|
:is-update-card-open="isUpdateCardOpen"
|
||||||
<button class="btn--text" @click="() => (isUpdateCardOpen = true)">
|
@open-update-card="() => (isUpdateCardOpen = true)"
|
||||||
v{{ VERSION }}{{ isOnProductionHost ? '' : 'dev' }}
|
/>
|
||||||
</button>
|
|
||||||
|
|
||||||
<br />
|
|
||||||
<a href="https://discord.gg/x2mpNN3svk">
|
|
||||||
<img src="/images/icon-discord.png" alt="" /> <b>{{ $t('footer.discord') }}</b>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<div style="display: none">∫ ukryta taktyczna całka do programowania w HTMLu</div>
|
|
||||||
</footer>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -45,7 +30,7 @@
|
|||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
import { version } from '.././package.json';
|
import { version } from '../package.json';
|
||||||
import { Status } from './typings/common';
|
import { Status } from './typings/common';
|
||||||
import { useMainStore } from './store/mainStore';
|
import { useMainStore } from './store/mainStore';
|
||||||
import { useApiStore } from './store/apiStore';
|
import { useApiStore } from './store/apiStore';
|
||||||
@@ -54,11 +39,11 @@ import { useTooltipStore } from './store/tooltipStore';
|
|||||||
import Clock from './components/App/Clock.vue';
|
import Clock from './components/App/Clock.vue';
|
||||||
import StatusIndicator from './components/App/StatusIndicator.vue';
|
import StatusIndicator from './components/App/StatusIndicator.vue';
|
||||||
import AppHeader from './components/App/AppHeader.vue';
|
import AppHeader from './components/App/AppHeader.vue';
|
||||||
import TrainModal from './components/TrainsView/TrainModal.vue';
|
|
||||||
import Tooltip from './components/Tooltip/Tooltip.vue';
|
import Tooltip from './components/Tooltip/Tooltip.vue';
|
||||||
import UpdateCard from './components/App/UpdateCard.vue';
|
import UpdateCard from './components/App/UpdateCard.vue';
|
||||||
|
|
||||||
import StorageManager from './managers/storageManager';
|
import StorageManager from './managers/storageManager';
|
||||||
|
import AppFooter from './components/App/AppFooter.vue';
|
||||||
|
|
||||||
const STORAGE_VERSION_KEY = 'app_version';
|
const STORAGE_VERSION_KEY = 'app_version';
|
||||||
|
|
||||||
@@ -67,7 +52,7 @@ export default defineComponent({
|
|||||||
Clock,
|
Clock,
|
||||||
StatusIndicator,
|
StatusIndicator,
|
||||||
AppHeader,
|
AppHeader,
|
||||||
TrainModal,
|
AppFooter,
|
||||||
UpdateCard,
|
UpdateCard,
|
||||||
Tooltip
|
Tooltip
|
||||||
},
|
},
|
||||||
@@ -81,9 +66,7 @@ export default defineComponent({
|
|||||||
isUpdateCardOpen: false,
|
isUpdateCardOpen: false,
|
||||||
|
|
||||||
currentLang: 'pl',
|
currentLang: 'pl',
|
||||||
isOnProductionHost: location.hostname == 'stacjownik-td2.web.app',
|
isOnProductionHost: location.hostname == 'stacjownik-td2.web.app'
|
||||||
|
|
||||||
nextUpdateTime: 0
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
@@ -92,26 +75,18 @@ export default defineComponent({
|
|||||||
|
|
||||||
async mounted() {
|
async mounted() {
|
||||||
window.addEventListener('mousemove', (e: MouseEvent) => this.tooltipStore.handle(e));
|
window.addEventListener('mousemove', (e: MouseEvent) => this.tooltipStore.handle(e));
|
||||||
|
window.addEventListener('mousedown', () => this.tooltipStore.hide());
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
init() {
|
init() {
|
||||||
|
if (!this.isOnProductionHost) document.title = 'Stacjownik Dev';
|
||||||
|
|
||||||
this.loadLang();
|
this.loadLang();
|
||||||
this.setupOfflineHandling();
|
this.setupOfflineHandling();
|
||||||
this.checkAppVersion();
|
this.checkAppVersion();
|
||||||
|
|
||||||
this.apiStore.setupAPIData();
|
this.apiStore.setupAPIData();
|
||||||
window.requestAnimationFrame(this.update);
|
|
||||||
|
|
||||||
if (!this.isOnProductionHost) document.title = 'Stacjownik Dev';
|
|
||||||
},
|
|
||||||
|
|
||||||
update(t: number) {
|
|
||||||
if (t >= this.nextUpdateTime) {
|
|
||||||
this.apiStore.fetchActiveData();
|
|
||||||
this.nextUpdateTime = t + 20000;
|
|
||||||
}
|
|
||||||
window.requestAnimationFrame(this.update);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async checkAppVersion() {
|
async checkAppVersion() {
|
||||||
@@ -131,7 +106,8 @@ export default defineComponent({
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.isUpdateCardOpen =
|
this.isUpdateCardOpen =
|
||||||
storageVersion != version || import.meta.env.VITE_UPDATE_TEST === 'test';
|
(storageVersion != '' && storageVersion != version && this.isOnProductionHost) ||
|
||||||
|
import.meta.env.VITE_UPDATE_TEST === 'test';
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Wystąpił błąd podczas pobierania danych z API GitHuba: ${error}`);
|
console.error(`Wystąpił błąd podczas pobierania danych z API GitHuba: ${error}`);
|
||||||
}
|
}
|
||||||
@@ -157,6 +133,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
handleOnlineMode() {
|
handleOnlineMode() {
|
||||||
this.store.isOffline = false;
|
this.store.isOffline = false;
|
||||||
|
this.apiStore.dataStatuses.connection = Status.Data.Loading;
|
||||||
|
|
||||||
this.apiStore.connectToAPI();
|
this.apiStore.connectToAPI();
|
||||||
},
|
},
|
||||||
@@ -180,7 +157,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
const naviLanguage = window.navigator.language.toString();
|
const naviLanguage = window.navigator.language.toString();
|
||||||
|
|
||||||
if (naviLanguage.includes('en')) {
|
if (naviLanguage.startsWith('en')) {
|
||||||
this.changeLang('en');
|
this.changeLang('en');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -210,7 +187,7 @@ export default defineComponent({
|
|||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
|
||||||
@include smallScreen() {
|
@include smallScreen() {
|
||||||
font-size: calc(0.65rem + 0.8vw);
|
font-size: calc(0.65rem + 0.85vw);
|
||||||
}
|
}
|
||||||
|
|
||||||
@include screenLandscape() {
|
@include screenLandscape() {
|
||||||
@@ -226,6 +203,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app_main {
|
.app_main {
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<template>
|
||||||
|
<footer class="app_footer">
|
||||||
|
©
|
||||||
|
<a href="https://td2.info.pl/profile/?u=20777" target="_blank">Spythere</a>
|
||||||
|
{{ new Date().getUTCFullYear() }} |
|
||||||
|
<button class="btn--text" @click="openUpdateCard">
|
||||||
|
v{{ version }}{{ isOnProductionHost ? '' : 'dev' }}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<a href="https://discord.gg/x2mpNN3svk">
|
||||||
|
<img src="/images/icon-discord.png" alt="" /> <b>{{ $t('footer.discord') }}</b>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div style="display: none">∫ ukryta taktyczna całka do programowania w HTMLu</div>
|
||||||
|
</footer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
emits: ['openUpdateCard'],
|
||||||
|
props: {
|
||||||
|
isUpdateCardOpen: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
version: String,
|
||||||
|
isOnProductionHost: Boolean
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
openUpdateCard() {
|
||||||
|
this.$emit('openUpdateCard');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
@@ -2,10 +2,9 @@
|
|||||||
<transition name="modal-anim" tag="div">
|
<transition name="modal-anim" tag="div">
|
||||||
<div class="card" v-if="isOpen">
|
<div class="card" v-if="isOpen">
|
||||||
<div class="card-background" @click="toggleCard(false)"></div>
|
<div class="card-background" @click="toggleCard(false)"></div>
|
||||||
<div class="card-body" ref="wrapper" tabindex="0">
|
<div class="card-body" tabindex="0">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-exit" ref="exit" tabindex="0" @focus="toggleCard(false)"></div>
|
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</template>
|
</template>
|
||||||
@@ -52,8 +51,12 @@ export default defineComponent({
|
|||||||
left: 0;
|
left: 0;
|
||||||
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100vh;
|
height: 100%;
|
||||||
z-index: 200;
|
z-index: 200;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-background {
|
.card-background {
|
||||||
@@ -69,19 +72,22 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
.card-body {
|
.card-body {
|
||||||
position: absolute;
|
position: relative;
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
z-index: 210;
|
|
||||||
overflow: auto;
|
|
||||||
max-height: 95vh;
|
|
||||||
|
|
||||||
|
margin: 1em;
|
||||||
|
|
||||||
|
max-height: 95vh;
|
||||||
|
max-height: 95dvh;
|
||||||
|
|
||||||
|
background-color: #1a1a1a;
|
||||||
box-shadow: 0 0 15px 10px #0e0e0e;
|
box-shadow: 0 0 15px 10px #0e0e0e;
|
||||||
|
|
||||||
& > :slotted(div) {
|
overflow: auto;
|
||||||
background-color: #1a1a1a;
|
}
|
||||||
width: 95vw;
|
|
||||||
|
@include smallScreen {
|
||||||
|
.card {
|
||||||
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ export default defineComponent({
|
|||||||
|
|
||||||
width: 6em;
|
width: 6em;
|
||||||
height: 1em;
|
height: 1em;
|
||||||
margin: 0.5em 0;
|
|
||||||
|
|
||||||
.bar-fg,
|
.bar-fg,
|
||||||
.bar-bg {
|
.bar-bg {
|
||||||
|
|||||||
@@ -88,8 +88,9 @@ $unknown: #b93c3c;
|
|||||||
.status-badge {
|
.status-badge {
|
||||||
border-radius: 1em;
|
border-radius: 1em;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
text-wrap: nowrap;
|
||||||
|
|
||||||
padding: 0.2em 0.55em;
|
padding: 0.2rem 0.55rem;
|
||||||
|
|
||||||
background-color: $online;
|
background-color: $online;
|
||||||
|
|
||||||
@@ -106,13 +107,13 @@ $unknown: #b93c3c;
|
|||||||
|
|
||||||
&.no-limit {
|
&.no-limit {
|
||||||
background-color: $no-limit;
|
background-color: $no-limit;
|
||||||
font-size: 0.85em;
|
font-size: 0.9em;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.not-signed,
|
&.not-signed,
|
||||||
&.unavailable {
|
&.unavailable {
|
||||||
background-color: $unav;
|
background-color: $unav;
|
||||||
font-size: 0.85em;
|
font-size: 0.9em;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.afk {
|
&.afk {
|
||||||
@@ -125,7 +126,7 @@ $unknown: #b93c3c;
|
|||||||
background-color: $no-space;
|
background-color: $no-space;
|
||||||
border: 1px solid white;
|
border: 1px solid white;
|
||||||
color: white;
|
color: white;
|
||||||
font-size: 0.85em;
|
font-size: 0.9em;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.unknown,
|
&.unknown,
|
||||||
|
|||||||
@@ -1,84 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="stock-list">
|
<div class="list-wrapper">
|
||||||
<div v-if="tractionOnly">
|
<ul class="stock-list">
|
||||||
<p>
|
<li v-for="({ images, imagesFallbacks, vehicleString }, i) in thumbnailNames">
|
||||||
{{ computedStockList[0].split(':')[0].split('_').splice(0, 2).join(' ') }}
|
<VehicleThumbnail
|
||||||
{{ computedStockList[0].split(':')[1] }}
|
:key="i"
|
||||||
</p>
|
:vehicle-string="vehicleString"
|
||||||
|
:images="images"
|
||||||
<img
|
:image-fallbacks="imagesFallbacks"
|
||||||
class="traction-only"
|
/>
|
||||||
:src="
|
|
||||||
getVehicleThumbnailURL(
|
|
||||||
computedStockList[0].split(':')[0],
|
|
||||||
/^EN/.test(computedStockList[0]) ? 'rb' : ''
|
|
||||||
)
|
|
||||||
"
|
|
||||||
@error="onImageError($event, computedStockList[0])"
|
|
||||||
width="300"
|
|
||||||
height="60"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul v-else>
|
|
||||||
<li v-for="(stockName, i) in computedStockList" :key="i">
|
|
||||||
<p>
|
|
||||||
{{ stockName.split(':')[0].split('_').splice(0, 2).join(' ') }}
|
|
||||||
{{ stockName.split(':')[1] }}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<span>
|
|
||||||
<img
|
|
||||||
:data-mouseover="stockName"
|
|
||||||
data-tooltip-type="VehiclePreviewTooltip"
|
|
||||||
:data-tooltip-content="stockName.split(':')[0]"
|
|
||||||
:src="
|
|
||||||
getVehicleThumbnailURL(stockName.split(':')[0], /^EN/.test(stockName) ? 'rb' : '')
|
|
||||||
"
|
|
||||||
@error="onImageError($event, stockName)"
|
|
||||||
@click.stop="() => {}"
|
|
||||||
width="400"
|
|
||||||
height="60"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- /// Manualne dodawanie miniaturek członów dla kibelków /// -->
|
|
||||||
<img
|
|
||||||
:data-mouseover="stockName"
|
|
||||||
data-tooltip-type="VehiclePreviewTooltip"
|
|
||||||
:data-tooltip-content="stockName.split(':')[0]"
|
|
||||||
v-if="/^(EN|2EN)/.test(stockName)"
|
|
||||||
:src="getVehicleThumbnailURL(stockName, 's')"
|
|
||||||
@error="
|
|
||||||
(event) => ((event.target as HTMLImageElement).src = '/images/icon-loco-ezt-s.png')
|
|
||||||
"
|
|
||||||
@click.stop="() => {}"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<img
|
|
||||||
:data-mouseover="stockName"
|
|
||||||
data-tooltip-type="VehiclePreviewTooltip"
|
|
||||||
:data-tooltip-content="stockName.split(':')[0]"
|
|
||||||
v-if="/^EN71/.test(stockName)"
|
|
||||||
:src="getVehicleThumbnailURL(stockName, 's')"
|
|
||||||
@error="
|
|
||||||
(event) => ((event.target as HTMLImageElement).src = '/images/icon-loco-ezt-s.png')
|
|
||||||
"
|
|
||||||
@click.stop="() => {}"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<img
|
|
||||||
:data-mouseover="stockName"
|
|
||||||
data-tooltip-type="VehiclePreviewTooltip"
|
|
||||||
:data-tooltip-content="stockName.split(':')[0]"
|
|
||||||
v-if="/^(EN|2EN)/.test(stockName)"
|
|
||||||
:src="getVehicleThumbnailURL(stockName, 'ra')"
|
|
||||||
@error="
|
|
||||||
(event) => ((event.target as HTMLImageElement).src = '/images/icon-loco-ezt-ra.png')
|
|
||||||
"
|
|
||||||
@click.stop="() => {}"
|
|
||||||
/>
|
|
||||||
<!-- /// -->
|
|
||||||
</span>
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -87,8 +16,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { PropType, defineComponent } from 'vue';
|
import { PropType, defineComponent } from 'vue';
|
||||||
import { useApiStore } from '../../store/apiStore';
|
import { useApiStore } from '../../store/apiStore';
|
||||||
|
import VehicleThumbnail from './VehicleThumbnail.vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
components: { VehicleThumbnail },
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
trainStockList: {
|
trainStockList: {
|
||||||
type: Array as PropType<string[]>,
|
type: Array as PropType<string[]>,
|
||||||
@@ -109,71 +41,126 @@ export default defineComponent({
|
|||||||
computed: {
|
computed: {
|
||||||
computedStockList() {
|
computedStockList() {
|
||||||
return this.tractionOnly ? this.trainStockList.slice(0, 1) : this.trainStockList;
|
return this.tractionOnly ? this.trainStockList.slice(0, 1) : this.trainStockList;
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
getVehicleThumbnailURL(locoType: string, suffix?: string) {
|
|
||||||
return `https://static.spythere.eu/thumbnails/${locoType}${suffix}.png`;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onImageError(event: Event, stockName: string) {
|
thumbnailNames() {
|
||||||
let fallbackName = '';
|
return (this.tractionOnly ? this.trainStockList.slice(0, 1) : this.trainStockList)
|
||||||
|
.filter((v) => v.length != 0)
|
||||||
|
.map((vehicleString) => {
|
||||||
|
const [vehicleName] = vehicleString.split(':');
|
||||||
|
|
||||||
const isLoco = /.-\d{3}/.test(stockName);
|
const vehicleThumbnailData = {
|
||||||
|
images: [] as string[],
|
||||||
|
imagesFallbacks: [] as string[],
|
||||||
|
vehicleName,
|
||||||
|
vehicleString
|
||||||
|
};
|
||||||
|
|
||||||
if (isLoco) {
|
// Generowanie członów EN57
|
||||||
if (/^\d?EN\d{2}/.test(stockName)) fallbackName = 'loco-ezt';
|
if (vehicleName.startsWith('EN57')) {
|
||||||
else if (/^SN\d{2}/.test(stockName)) fallbackName = 'loco-szt';
|
vehicleThumbnailData['images'] = [
|
||||||
else if (/^\d{0,}?E/.test(stockName)) fallbackName = 'loco-e';
|
vehicleName + 'ra',
|
||||||
else fallbackName = 'loco-s';
|
vehicleName + 's',
|
||||||
} else {
|
vehicleName + 'rb'
|
||||||
const isCarPassenger = /(\d{3}a|(Bau|Gor)\d{2}|304C)_/.test(stockName);
|
];
|
||||||
|
vehicleThumbnailData['imagesFallbacks'] = [
|
||||||
|
'unknown_ezt-ra',
|
||||||
|
'unknown_ezt-s',
|
||||||
|
'unknown_ezt-rb'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
// Generowanie członów EN71
|
||||||
|
else if (vehicleName.startsWith('EN71')) {
|
||||||
|
vehicleThumbnailData['images'] = [
|
||||||
|
vehicleName + 'ra',
|
||||||
|
vehicleName + 'sa',
|
||||||
|
vehicleName + 'sb',
|
||||||
|
vehicleName + 'rb'
|
||||||
|
];
|
||||||
|
vehicleThumbnailData['imagesFallbacks'] = [
|
||||||
|
'unknown_ezt-ra',
|
||||||
|
'unknown_ezt-sa',
|
||||||
|
'unknown_ezt-sb',
|
||||||
|
'unknown_ezt-rb'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
// Generowanie pojazdów i członów 2EN57
|
||||||
|
else if (vehicleString.startsWith('2EN57')) {
|
||||||
|
const [firstVehicleNumber, secondVehicleNumber] = vehicleString
|
||||||
|
.replace('2EN57-', '')
|
||||||
|
.split('+');
|
||||||
|
|
||||||
fallbackName += 'car-';
|
vehicleThumbnailData['images'] = [
|
||||||
fallbackName += isCarPassenger ? 'passenger' : 'cargo';
|
`EN57-${firstVehicleNumber}ra`,
|
||||||
}
|
`EN57-${firstVehicleNumber}s`,
|
||||||
|
`EN57-${firstVehicleNumber}rb`,
|
||||||
|
`EN57-${secondVehicleNumber}ra`,
|
||||||
|
`EN57-${secondVehicleNumber}s`,
|
||||||
|
`EN57-${secondVehicleNumber}rb`
|
||||||
|
];
|
||||||
|
|
||||||
(event.target as HTMLImageElement).src = `/images/icon-${fallbackName}.png`;
|
vehicleThumbnailData['imagesFallbacks'] = [
|
||||||
|
'unknown_ezt-ra',
|
||||||
|
'unknown_ezt-s',
|
||||||
|
'unknown_ezt-rb',
|
||||||
|
'unknown_ezt-ra',
|
||||||
|
'unknown_ezt-s',
|
||||||
|
'unknown_ezt-rb'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
// Generowanie członów Gor77
|
||||||
|
else if (vehicleString.startsWith('Gor77')) {
|
||||||
|
vehicleThumbnailData['images'] = [
|
||||||
|
vehicleName + '-A',
|
||||||
|
vehicleName + '-B',
|
||||||
|
vehicleName + '-C',
|
||||||
|
vehicleName + '-D'
|
||||||
|
];
|
||||||
|
vehicleThumbnailData['imagesFallbacks'] = [
|
||||||
|
'unknown_Gor77-A',
|
||||||
|
'unknown_Gor77-B',
|
||||||
|
'unknown_Gor77-C',
|
||||||
|
'unknown_Gor77-D'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
// Generowanie członów ET41
|
||||||
|
else if (vehicleString.startsWith('ET41')) {
|
||||||
|
vehicleThumbnailData['images'] = [vehicleName + '-A', vehicleName + '-B'];
|
||||||
|
vehicleThumbnailData['imagesFallbacks'] = ['unknown_ET41-A', 'unknown_ET41-B'];
|
||||||
|
}
|
||||||
|
// Generowanie pozostałych pojazdów
|
||||||
|
else {
|
||||||
|
let fallbackVehicleImage = 'unknown_cargo';
|
||||||
|
|
||||||
|
if (/^(EP|EU|ET|201E)/.test(vehicleName)) fallbackVehicleImage = 'unknown_train';
|
||||||
|
else if (/^(SM42)/.test(vehicleName)) fallbackVehicleImage = 'unknown_SM42';
|
||||||
|
else if (/(\d{3}a|(Bau|Gor)\d{2}|304C)_/.test(vehicleName))
|
||||||
|
fallbackVehicleImage = 'unknown_passenger';
|
||||||
|
|
||||||
|
vehicleThumbnailData['images'] = [vehicleName];
|
||||||
|
vehicleThumbnailData['imagesFallbacks'] = [fallbackVehicleImage];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.tractionOnly) vehicleThumbnailData['images'].length = 1;
|
||||||
|
|
||||||
|
return vehicleThumbnailData;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.stock-list {
|
|
||||||
|
.list-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stock-list ul {
|
.stock-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 1em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul > li > span {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-end;
|
|
||||||
cursor: crosshair;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
max-height: 60px;
|
|
||||||
width: auto;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
img.traction-only {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
text-align: center;
|
|
||||||
color: #aaa;
|
|
||||||
font-size: 0.95em;
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
<template>
|
||||||
|
<div class="vehicle-thumbnail" :data-load-status="imgStatus" ref="thumbRef">
|
||||||
|
<div class="stock-text">
|
||||||
|
<div>{{ vehicleName }}</div>
|
||||||
|
<small v-if="vehicleCargo">({{ vehicleCargo }})</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stock-images">
|
||||||
|
<img
|
||||||
|
v-for="(thumbnailImage, imageIndex) in images"
|
||||||
|
:src="`https://stacjownik.spythere.eu/static/thumbnails/${thumbnailImage}.png`"
|
||||||
|
height="60"
|
||||||
|
loading="lazy"
|
||||||
|
data-tooltip-type="VehiclePreviewTooltip"
|
||||||
|
:data-tooltip-content="vehicleString"
|
||||||
|
@error="onImageError($event, imageFallbacks[imageIndex])"
|
||||||
|
@load="onImageLoad"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, PropType, Ref, ref } from 'vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
vehicleString: { type: String, required: true },
|
||||||
|
images: { type: Object as PropType<string[]>, required: true },
|
||||||
|
imageFallbacks: { type: Object as PropType<string[]>, required: true }
|
||||||
|
});
|
||||||
|
|
||||||
|
const thumbRef = ref(null) as Ref<HTMLElement | null>;
|
||||||
|
const imgStatus = ref('loading');
|
||||||
|
|
||||||
|
const vehicleName = computed(() => props.vehicleString.split(':')[0].replace(/_/g, ' '));
|
||||||
|
const vehicleCargo = computed(() => props.vehicleString.split(':')[1]);
|
||||||
|
|
||||||
|
function onImageError(event: Event, fallbackImage: string) {
|
||||||
|
(event.target as HTMLImageElement).src = `/images/${fallbackImage}.png`;
|
||||||
|
imgStatus.value = 'error';
|
||||||
|
}
|
||||||
|
|
||||||
|
function onImageLoad() {
|
||||||
|
if (imgStatus.value != 'error') {
|
||||||
|
imgStatus.value = 'loaded';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thumbRef.value) thumbRef.value.style.opacity = '1';
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.vehicle-thumbnail {
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 100ms ease-in-out;
|
||||||
|
|
||||||
|
&[data-load-status='loading'] {
|
||||||
|
min-height: 60px;
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stock-text {
|
||||||
|
text-align: center;
|
||||||
|
color: #aaa;
|
||||||
|
font-size: 0.9em;
|
||||||
|
margin-bottom: 0.25em;
|
||||||
|
padding: 0.25em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stock-images {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: flex-end;
|
||||||
|
cursor: crosshair;
|
||||||
|
|
||||||
|
padding: 0.5em 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,29 +1,28 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="daily-stats">
|
<section class="daily-stats">
|
||||||
<span :data-active="statsStatus">
|
<span :data-active="statsStatus">
|
||||||
<span class="stats-list">
|
<h3>
|
||||||
<h3>
|
{{ $t('journal.daily-stats.title') }}
|
||||||
{{ $t('journal.daily-stats.title') }}
|
<b class="text--primary">{{ new Date().toLocaleDateString($i18n.locale) }}</b>
|
||||||
<b class="text--primary">{{ new Date().toLocaleDateString($i18n.locale) }}</b>
|
</h3>
|
||||||
</h3>
|
|
||||||
|
|
||||||
<hr class="header-separator" />
|
<hr class="header-separator" />
|
||||||
|
|
||||||
<b v-if="statsStatus == Status.Data.Loading">
|
<b v-if="statsStatus == Status.Data.Loading">
|
||||||
{{ $t('app.loading') }}
|
{{ $t('app.loading') }}
|
||||||
</b>
|
</b>
|
||||||
|
|
||||||
<b class="text--error" v-else-if="statsStatus == Status.Data.Error">
|
<b class="text--error" v-else-if="statsStatus == Status.Data.Error">
|
||||||
{{ $t('journal.stats-error') }}
|
{{ $t('journal.stats-error') }}
|
||||||
</b>
|
</b>
|
||||||
|
|
||||||
<b v-else-if="topDispatchers.length == 0">
|
<b v-else-if="topDispatchers.length == 0">
|
||||||
{{ $t('journal.daily-stats.info') }}
|
{{ $t('journal.daily-stats.info') }}
|
||||||
</b>
|
</b>
|
||||||
|
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<div v-if="stats.totalTimetables">
|
<ul class="stats-list">
|
||||||
•
|
<li v-if="stats.totalTimetables">
|
||||||
<i18n-t keypath="journal.daily-stats.total">
|
<i18n-t keypath="journal.daily-stats.total">
|
||||||
<template #count>
|
<template #count>
|
||||||
<b class="text--primary">
|
<b class="text--primary">
|
||||||
@@ -36,10 +35,9 @@
|
|||||||
<b class="text--primary"> {{ stats.distanceSum?.toFixed(2) }} km</b>
|
<b class="text--primary"> {{ stats.distanceSum?.toFixed(2) }} km</b>
|
||||||
</template>
|
</template>
|
||||||
</i18n-t>
|
</i18n-t>
|
||||||
</div>
|
</li>
|
||||||
|
|
||||||
<div v-if="stats.maxTimetable">
|
<li v-if="stats.maxTimetable">
|
||||||
•
|
|
||||||
<i18n-t keypath="journal.daily-stats.longest">
|
<i18n-t keypath="journal.daily-stats.longest">
|
||||||
<template #id>
|
<template #id>
|
||||||
<router-link :to="`/journal/timetables?search-train=%23${stats.maxTimetable.id}`">
|
<router-link :to="`/journal/timetables?search-train=%23${stats.maxTimetable.id}`">
|
||||||
@@ -60,10 +58,9 @@
|
|||||||
<b class="text--primary">{{ stats.maxTimetable.routeDistance }} km</b>
|
<b class="text--primary">{{ stats.maxTimetable.routeDistance }} km</b>
|
||||||
</template>
|
</template>
|
||||||
</i18n-t>
|
</i18n-t>
|
||||||
</div>
|
</li>
|
||||||
|
|
||||||
<div v-if="topDispatchers.length == 1">
|
<li v-if="topDispatchers.length == 1">
|
||||||
•
|
|
||||||
<i18n-t keypath="journal.daily-stats.most-active-dr">
|
<i18n-t keypath="journal.daily-stats.most-active-dr">
|
||||||
<template #dispatcher>
|
<template #dispatcher>
|
||||||
<router-link
|
<router-link
|
||||||
@@ -79,10 +76,9 @@
|
|||||||
</b>
|
</b>
|
||||||
</template>
|
</template>
|
||||||
</i18n-t>
|
</i18n-t>
|
||||||
</div>
|
</li>
|
||||||
|
|
||||||
<div v-if="topDispatchers.length > 1">
|
<li v-if="topDispatchers.length > 1">
|
||||||
•
|
|
||||||
<i18n-t keypath="journal.daily-stats.most-active-dr-many">
|
<i18n-t keypath="journal.daily-stats.most-active-dr-many">
|
||||||
<template #dispatchers>
|
<template #dispatchers>
|
||||||
<span v-for="(disp, i) in topDispatchers" :key="i">
|
<span v-for="(disp, i) in topDispatchers" :key="i">
|
||||||
@@ -103,10 +99,9 @@
|
|||||||
</b>
|
</b>
|
||||||
</template>
|
</template>
|
||||||
</i18n-t>
|
</i18n-t>
|
||||||
</div>
|
</li>
|
||||||
|
|
||||||
<div v-if="stats.longestDuties.length > 0">
|
<li v-if="stats.longestDuties.length > 0">
|
||||||
•
|
|
||||||
<i18n-t keypath="journal.daily-stats.longest-duties">
|
<i18n-t keypath="journal.daily-stats.longest-duties">
|
||||||
<template #dispatcher>
|
<template #dispatcher>
|
||||||
<router-link
|
<router-link
|
||||||
@@ -122,10 +117,9 @@
|
|||||||
{{ calculateDuration(stats.longestDuties[0].duration) }}
|
{{ calculateDuration(stats.longestDuties[0].duration) }}
|
||||||
</template>
|
</template>
|
||||||
</i18n-t>
|
</i18n-t>
|
||||||
</div>
|
</li>
|
||||||
|
|
||||||
<div v-if="stats.mostActiveDrivers.length > 0">
|
<li v-if="stats.mostActiveDrivers.length > 0">
|
||||||
•
|
|
||||||
<i18n-t keypath="journal.daily-stats.most-active-driver">
|
<i18n-t keypath="journal.daily-stats.most-active-driver">
|
||||||
<template #driver>
|
<template #driver>
|
||||||
<router-link
|
<router-link
|
||||||
@@ -138,30 +132,30 @@
|
|||||||
<b class="text--primary">{{ stats.mostActiveDrivers[0].distance.toFixed(2) }} km</b>
|
<b class="text--primary">{{ stats.mostActiveDrivers[0].distance.toFixed(2) }} km</b>
|
||||||
</template>
|
</template>
|
||||||
</i18n-t>
|
</i18n-t>
|
||||||
</div>
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
<hr class="section-separator" />
|
<hr class="section-separator" />
|
||||||
|
|
||||||
<div class="stats-badges">
|
<div class="stats-badges">
|
||||||
<span
|
<span
|
||||||
class="stat-badge"
|
class="badge stat-badge"
|
||||||
v-for="key in [
|
v-for="key in [
|
||||||
'rippedSwitches',
|
'rippedSwitches',
|
||||||
'derailments',
|
'derailments',
|
||||||
'skippedStopSignals',
|
'skippedStopSignals',
|
||||||
'radioStops',
|
'radioStops',
|
||||||
'kills'
|
'kills'
|
||||||
]"
|
]"
|
||||||
:key="key"
|
:key="key"
|
||||||
>
|
>
|
||||||
<span>{{ $t(`journal.daily-stats.${key}`) }}</span>
|
<span>{{ $t(`journal.daily-stats.${key}`) }}</span>
|
||||||
<span>{{
|
<span>
|
||||||
Object.entries(stats.globalDiff).find(([k, v]) => k == key)?.[1] || '--'
|
{{ Object.entries(stats.globalDiff).find(([k, v]) => k == key)?.[1] || '--' }}
|
||||||
}}</span>
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
@@ -178,7 +172,6 @@ export default defineComponent({
|
|||||||
name: 'journal-daily-stats',
|
name: 'journal-daily-stats',
|
||||||
|
|
||||||
mixins: [dateMixin],
|
mixins: [dateMixin],
|
||||||
// emits: ['toggleStatsOpen'],
|
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -193,7 +186,6 @@ export default defineComponent({
|
|||||||
|
|
||||||
activated() {
|
activated() {
|
||||||
this.startFetchingDailyStats();
|
this.startFetchingDailyStats();
|
||||||
// this.$emit('toggleStatsOpen', true);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
deactivated() {
|
deactivated() {
|
||||||
@@ -249,14 +241,24 @@ export default defineComponent({
|
|||||||
.daily-stats {
|
.daily-stats {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.daily-stats > span[data-active='0'] {
|
.daily-stats > span[data-active='0'] {
|
||||||
opacity: 0.75;
|
opacity: 0.75;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ul.stats-list {
|
||||||
|
list-style: disc;
|
||||||
|
padding: 0 1em;
|
||||||
|
}
|
||||||
|
|
||||||
.stats-list a {
|
.stats-list a {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stats-list > li {
|
||||||
|
margin: 0.25em 0;
|
||||||
|
}
|
||||||
|
|
||||||
.stats-badges {
|
.stats-badges {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|||||||
@@ -0,0 +1,217 @@
|
|||||||
|
<template>
|
||||||
|
<li class="dispatcher-history-entry">
|
||||||
|
<div class="entry-info">
|
||||||
|
<span>
|
||||||
|
<span>
|
||||||
|
<router-link :to="`/journal/dispatchers?search-station=${entry.stationName}`">
|
||||||
|
<b>{{ entry.stationName }}</b>
|
||||||
|
</router-link>
|
||||||
|
|
||||||
|
<b class="text--grayed"> #{{ entry.stationHash }}</b>
|
||||||
|
</span>
|
||||||
|
•
|
||||||
|
<b
|
||||||
|
v-if="entry.dispatcherLevel !== null"
|
||||||
|
class="level-badge dispatcher"
|
||||||
|
:style="calculateExpStyle(entry.dispatcherLevel, entry.dispatcherIsSupporter)"
|
||||||
|
>
|
||||||
|
{{ entry.dispatcherLevel >= 2 ? entry.dispatcherLevel : 'L' }}
|
||||||
|
</b>
|
||||||
|
<b style="margin-left: 5px">
|
||||||
|
<span
|
||||||
|
v-if="apiStore.donatorsData.includes(entry.dispatcherName)"
|
||||||
|
data-tooltip-type="DonatorTooltip"
|
||||||
|
:data-tooltip-content="$t('donations.dispatcher-message')"
|
||||||
|
>
|
||||||
|
<router-link
|
||||||
|
class="text--donator"
|
||||||
|
:to="`/journal/dispatchers?search-dispatcher=${entry.dispatcherName}`"
|
||||||
|
>
|
||||||
|
{{ entry.dispatcherName }}
|
||||||
|
</router-link>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<router-link
|
||||||
|
v-else
|
||||||
|
:to="`/journal/dispatchers?search-dispatcher=${entry.dispatcherName}`"
|
||||||
|
>
|
||||||
|
{{ entry.dispatcherName }}
|
||||||
|
</router-link>
|
||||||
|
</b>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<span v-if="entry.timestampTo">
|
||||||
|
<b>{{ $d(entry.timestampFrom) }}</b>
|
||||||
|
{{ timestampToString(entry.timestampFrom) }}
|
||||||
|
-
|
||||||
|
<b
|
||||||
|
v-if="
|
||||||
|
new Date(entry.timestampFrom).getDate() != new Date(entry.timestampTo).getDate()
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ $d(entry.timestampTo) }}
|
||||||
|
</b>
|
||||||
|
{{ timestampToString(entry.timestampTo) }} ({{
|
||||||
|
calculateDuration(entry.currentDuration)
|
||||||
|
}})
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<router-link
|
||||||
|
:to="`/scenery?station=${entry.stationName}`"
|
||||||
|
class="dispatcher-online"
|
||||||
|
v-else
|
||||||
|
>
|
||||||
|
{{ $t('journal.online-since') }}
|
||||||
|
<b>
|
||||||
|
{{
|
||||||
|
new Date().getDate() != new Date(entry.timestampFrom).getDate()
|
||||||
|
? $d(entry.timestampFrom)
|
||||||
|
: ''
|
||||||
|
}}
|
||||||
|
{{ timestampToString(entry.timestampFrom) }}
|
||||||
|
</b>
|
||||||
|
({{ calculateDuration(entry.currentDuration) }})
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="entry-info-right">
|
||||||
|
<div>
|
||||||
|
<span>
|
||||||
|
{{ $t('scenery.dispatcher-rate') }}
|
||||||
|
<b class="text--primary"> {{ entry.dispatcherRate }}</b>
|
||||||
|
</span>
|
||||||
|
<button class="btn btn--option" @click="toggleExtraInfo">
|
||||||
|
{{ $t('scenery.dispatcher-status-changes') }}
|
||||||
|
<b class="text--primary">{{ entry.statusHistory.length }}</b>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<b class="region-badge" :aria-describedby="entry.region">
|
||||||
|
REGION: {{ regions.find((r) => r.id == entry.region)?.name }}
|
||||||
|
</b>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="entry-extra" v-if="showExtraInfo">
|
||||||
|
<ul class="status-list">
|
||||||
|
<li v-for="statusItem in entry.statusHistory">
|
||||||
|
<b style="margin-right: 0.5em">{{
|
||||||
|
timestampToString(parseInt(statusItem.split('@')[0]))
|
||||||
|
}}</b>
|
||||||
|
|
||||||
|
<StationStatusBadge
|
||||||
|
:dispatcher-status="Number(statusItem.split('@')[1])"
|
||||||
|
:is-online="true"
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, PropType } from 'vue';
|
||||||
|
import { regions } from '../../../data/options.json';
|
||||||
|
import { API } from '../../../typings/api';
|
||||||
|
import dateMixin from '../../../mixins/dateMixin';
|
||||||
|
import styleMixin from '../../../mixins/styleMixin';
|
||||||
|
import { useApiStore } from '../../../store/apiStore';
|
||||||
|
import StationStatusBadge from '../../Global/StationStatusBadge.vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
|
entry: {
|
||||||
|
type: Object as PropType<API.DispatcherHistory.Data>,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
showExtraInfo: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
components: { StationStatusBadge },
|
||||||
|
mixins: [dateMixin, styleMixin],
|
||||||
|
emits: ['toggleShowExtraInfo'],
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
regions,
|
||||||
|
apiStore: useApiStore()
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
toggleExtraInfo() {
|
||||||
|
this.$emit('toggleShowExtraInfo', this.entry.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '../../../styles/responsive.scss';
|
||||||
|
@import '../../../styles/badge.scss';
|
||||||
|
|
||||||
|
.region-badge {
|
||||||
|
padding: 0 0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.level-badge {
|
||||||
|
text-align: center;
|
||||||
|
display: inline-block;
|
||||||
|
line-height: 1.6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatcher-online {
|
||||||
|
color: springgreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dispatcher-history-entry {
|
||||||
|
background-color: #1a1a1a;
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-info {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
line-height: 1.75em;
|
||||||
|
gap: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-info-right {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
gap: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-extra {
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-list {
|
||||||
|
display: flex;
|
||||||
|
overflow: auto;
|
||||||
|
gap: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-list > li {
|
||||||
|
background-color: #313131;
|
||||||
|
padding: 0.2rem 0 0.2rem 0.5em;
|
||||||
|
margin: 0.5em 0;
|
||||||
|
border-radius: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include smallScreen {
|
||||||
|
.entry-info {
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -16,17 +16,17 @@
|
|||||||
<hr class="header-separator" />
|
<hr class="header-separator" />
|
||||||
|
|
||||||
<div class="info-stats">
|
<div class="info-stats">
|
||||||
<span class="stat-badge" v-if="stats.services">
|
<span class="badge stat-badge" v-if="stats.services">
|
||||||
<span>{{ $t('journal.dispatcher-stats.services-count') }}</span>
|
<span>{{ $t('journal.dispatcher-stats.services-count') }}</span>
|
||||||
<span>{{ stats.services.count }}</span>
|
<span>{{ stats.services.count }}</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="stat-badge" v-if="stats.services">
|
<span class="badge stat-badge" v-if="stats.services">
|
||||||
<span>{{ $t('journal.dispatcher-stats.service-max') }}</span>
|
<span>{{ $t('journal.dispatcher-stats.service-max') }}</span>
|
||||||
<span>{{ calculateDuration(stats.services.durationMax) }}</span>
|
<span>{{ calculateDuration(stats.services.durationMax) }}</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="stat-badge" v-if="stats.services">
|
<span class="badge stat-badge" v-if="stats.services">
|
||||||
<span>{{ $t('journal.dispatcher-stats.service-avg') }}</span>
|
<span>{{ $t('journal.dispatcher-stats.service-avg') }}</span>
|
||||||
<span>{{ calculateDuration(stats.services.durationAvg) }}</span>
|
<span>{{ calculateDuration(stats.services.durationAvg) }}</span>
|
||||||
</span>
|
</span>
|
||||||
@@ -35,22 +35,22 @@
|
|||||||
<hr class="section-separator" />
|
<hr class="section-separator" />
|
||||||
|
|
||||||
<div class="info-stats">
|
<div class="info-stats">
|
||||||
<span class="stat-badge" v-if="stats.issuedTimetables">
|
<span class="badge stat-badge" v-if="stats.issuedTimetables">
|
||||||
<span>{{ $t('journal.dispatcher-stats.timetables-count') }}</span>
|
<span>{{ $t('journal.dispatcher-stats.timetables-count') }}</span>
|
||||||
<span>{{ stats.issuedTimetables.count }}</span>
|
<span>{{ stats.issuedTimetables.count }}</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="stat-badge" v-if="stats.issuedTimetables">
|
<span class="badge stat-badge" v-if="stats.issuedTimetables">
|
||||||
<span>{{ $t('journal.dispatcher-stats.timetables-sum') }}</span>
|
<span>{{ $t('journal.dispatcher-stats.timetables-sum') }}</span>
|
||||||
<span>{{ stats.issuedTimetables.distanceSum.toFixed(2) }}km</span>
|
<span>{{ stats.issuedTimetables.distanceSum.toFixed(2) }}km</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="stat-badge" v-if="stats.issuedTimetables">
|
<span class="badge stat-badge" v-if="stats.issuedTimetables">
|
||||||
<span>{{ $t('journal.dispatcher-stats.timetables-max') }}</span>
|
<span>{{ $t('journal.dispatcher-stats.timetables-max') }}</span>
|
||||||
<span>{{ stats.issuedTimetables.distanceMax.toFixed(2) }}km</span>
|
<span>{{ stats.issuedTimetables.distanceMax.toFixed(2) }}km</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="stat-badge" v-if="stats.issuedTimetables">
|
<span class="badge stat-badge" v-if="stats.issuedTimetables">
|
||||||
<span>{{ $t('journal.dispatcher-stats.timetables-avg') }}</span>
|
<span>{{ $t('journal.dispatcher-stats.timetables-avg') }}</span>
|
||||||
<span>{{ stats.issuedTimetables.distanceAvg.toFixed(2) }}km</span>
|
<span>{{ stats.issuedTimetables.distanceAvg.toFixed(2) }}km</span>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -1,140 +1,59 @@
|
|||||||
<template>
|
<template>
|
||||||
<transition name="status-anim" mode="out-in">
|
<div>
|
||||||
<div :key="dataStatus">
|
<div class="journal_warning" v-if="store.isOffline">
|
||||||
<div class="journal_warning" v-if="store.isOffline">
|
{{ $t('app.offline') }}
|
||||||
{{ $t('app.offline') }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Loading v-else-if="dataStatus == Status.Data.Loading" />
|
|
||||||
|
|
||||||
<div v-else-if="dataStatus == Status.Data.Error" class="journal_warning error">
|
|
||||||
{{ $t('app.error') }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="journal_warning" v-else-if="dispatcherHistory.length == 0">
|
|
||||||
{{ $t('app.no-result') }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else>
|
|
||||||
<table class="dispatchers-table">
|
|
||||||
<thead>
|
|
||||||
<th>{{ $t('journal.history-name') }}</th>
|
|
||||||
<th>{{ $t('journal.history-hash') }}</th>
|
|
||||||
<th>{{ $t('journal.history-dispatcher') }}</th>
|
|
||||||
<th>{{ $t('journal.history-level') }}</th>
|
|
||||||
<th>{{ $t('journal.history-rate') }}</th>
|
|
||||||
<th>{{ $t('journal.history-region') }}</th>
|
|
||||||
<th>{{ $t('journal.history-date') }}</th>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tbody>
|
|
||||||
<transition-group name="list-anim">
|
|
||||||
<tr v-for="historyItem in dispatcherHistory" :key="historyItem.id">
|
|
||||||
<td>
|
|
||||||
<router-link
|
|
||||||
:to="`/journal/dispatchers?search-station=${historyItem.stationName}`"
|
|
||||||
>
|
|
||||||
<b>{{ historyItem.stationName }}</b>
|
|
||||||
</router-link>
|
|
||||||
</td>
|
|
||||||
<td>#{{ historyItem.stationHash }}</td>
|
|
||||||
<td>
|
|
||||||
<router-link
|
|
||||||
:to="`/journal/dispatchers?search-dispatcher=${historyItem.dispatcherName}`"
|
|
||||||
>
|
|
||||||
<b
|
|
||||||
v-if="apiStore.donatorsData.includes(historyItem.dispatcherName)"
|
|
||||||
class="text--donator"
|
|
||||||
:title="$t('donations.dispatcher-message')"
|
|
||||||
>
|
|
||||||
{{ historyItem.dispatcherName }}
|
|
||||||
</b>
|
|
||||||
|
|
||||||
<b v-else>
|
|
||||||
{{ historyItem.dispatcherName }}
|
|
||||||
</b>
|
|
||||||
</router-link>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<b
|
|
||||||
v-if="historyItem.dispatcherLevel !== null"
|
|
||||||
class="level-badge dispatcher"
|
|
||||||
:style="
|
|
||||||
calculateExpStyle(
|
|
||||||
historyItem.dispatcherLevel,
|
|
||||||
historyItem.dispatcherIsSupporter
|
|
||||||
)
|
|
||||||
"
|
|
||||||
>
|
|
||||||
{{ historyItem.dispatcherLevel >= 2 ? historyItem.dispatcherLevel : 'L' }}
|
|
||||||
</b>
|
|
||||||
</td>
|
|
||||||
<td class="text--primary">
|
|
||||||
<b>{{ historyItem.dispatcherRate }}</b>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<b class="region-badge" :aria-describedby="historyItem.region">{{
|
|
||||||
regions.find((r) => r.id == historyItem.region)?.value || '???'
|
|
||||||
}}</b>
|
|
||||||
</td>
|
|
||||||
<td style="min-width: 200px" class="time">
|
|
||||||
<span v-if="historyItem.timestampTo" class="text--offline">
|
|
||||||
<b>{{ $d(historyItem.timestampFrom) }}</b>
|
|
||||||
{{ timestampToString(historyItem.timestampFrom) }}
|
|
||||||
- {{ timestampToString(historyItem.timestampTo) }} ({{
|
|
||||||
calculateDuration(historyItem.currentDuration)
|
|
||||||
}})
|
|
||||||
</span>
|
|
||||||
<span class="dispatcher-online" v-else>
|
|
||||||
<b class="text--online">
|
|
||||||
<router-link :to="`/scenery?station=${historyItem.stationName}`">{{
|
|
||||||
$t('journal.online-since')
|
|
||||||
}}</router-link>
|
|
||||||
{{ timestampToString(historyItem.timestampFrom) }}
|
|
||||||
</b>
|
|
||||||
({{ calculateDuration(historyItem.currentDuration) }})
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</transition-group>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<AddDataButton
|
|
||||||
:list="dispatcherHistory"
|
|
||||||
:scrollDataLoaded="scrollDataLoaded"
|
|
||||||
:scrollNoMoreData="scrollNoMoreData"
|
|
||||||
@addHistoryData="addHistoryData"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="journal_warning" v-if="scrollNoMoreData">
|
|
||||||
{{ $t('journal.no-further-data') }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="journal_warning" v-else-if="!scrollDataLoaded">
|
|
||||||
{{ $t('journal.loading-further-data') }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
|
||||||
|
<Loading v-else-if="dataStatus == Status.Data.Loading" />
|
||||||
|
|
||||||
|
<div v-else-if="dataStatus == Status.Data.Error" class="journal_warning error">
|
||||||
|
{{ $t('app.error') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="journal_warning" v-else-if="dispatcherHistory.length == 0">
|
||||||
|
{{ $t('app.no-result') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else>
|
||||||
|
<transition-group name="list-anim" class="journal-list" tag="ul">
|
||||||
|
<JournalDispatcherEntry
|
||||||
|
v-for="entry in dispatcherHistory"
|
||||||
|
:key="entry.id"
|
||||||
|
:entry="entry"
|
||||||
|
:onToggleShowExtraInfo="toggleExtraInfo"
|
||||||
|
:showExtraInfo="extraInfoIndexes.includes(entry.id)"
|
||||||
|
/>
|
||||||
|
</transition-group>
|
||||||
|
|
||||||
|
<AddDataButton
|
||||||
|
:list="dispatcherHistory"
|
||||||
|
:scrollDataLoaded="scrollDataLoaded"
|
||||||
|
:scrollNoMoreData="scrollNoMoreData"
|
||||||
|
@addHistoryData="addHistoryData"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="journal_warning" v-if="scrollNoMoreData">
|
||||||
|
{{ $t('journal.no-further-data') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="journal_warning" v-else-if="!scrollDataLoaded">
|
||||||
|
{{ $t('journal.loading-further-data') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, PropType } from 'vue';
|
import { defineComponent, PropType } from 'vue';
|
||||||
import { regions } from '../../../data/options.json';
|
|
||||||
import { useMainStore } from '../../../store/mainStore';
|
import { useMainStore } from '../../../store/mainStore';
|
||||||
import { API } from '../../../typings/api';
|
import { API } from '../../../typings/api';
|
||||||
import { Status } from '../../../typings/common';
|
import { Status } from '../../../typings/common';
|
||||||
import Loading from '../../Global/Loading.vue';
|
import Loading from '../../Global/Loading.vue';
|
||||||
import AddDataButton from '../../Global/AddDataButton.vue';
|
import AddDataButton from '../../Global/AddDataButton.vue';
|
||||||
import dateMixin from '../../../mixins/dateMixin';
|
import JournalDispatcherEntry from './JournalDispatcherEntry.vue';
|
||||||
import styleMixin from '../../../mixins/styleMixin';
|
|
||||||
import { useApiStore } from '../../../store/apiStore';
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { Loading, AddDataButton },
|
components: { Loading, AddDataButton, JournalDispatcherEntry },
|
||||||
|
|
||||||
mixins: [dateMixin, styleMixin],
|
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
dispatcherHistory: {
|
dispatcherHistory: {
|
||||||
@@ -159,99 +78,32 @@ export default defineComponent({
|
|||||||
return {
|
return {
|
||||||
Status,
|
Status,
|
||||||
store: useMainStore(),
|
store: useMainStore(),
|
||||||
apiStore: useApiStore(),
|
|
||||||
regions
|
extraInfoIndexes: [] as number[]
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
watch: {
|
||||||
computedDispatcherHistory() {
|
'$route.query': {
|
||||||
return this.dispatcherHistory.reduce(
|
deep: true,
|
||||||
(acc, historyItem, i) => {
|
handler() {
|
||||||
if (this.isAnotherDay(i - 1, i))
|
this.extraInfoIndexes.length = 0;
|
||||||
acc.push(new Date(historyItem.timestampFrom).toLocaleDateString('pl-PL'));
|
}
|
||||||
acc.push(historyItem);
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
},
|
|
||||||
[] as (API.DispatcherHistory.Data | string)[]
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
navigateToScenery(name: string, isOnline: boolean) {
|
toggleExtraInfo(id: number) {
|
||||||
if (!isOnline) return;
|
const existingIdx = this.extraInfoIndexes.indexOf(id);
|
||||||
|
|
||||||
this.$router.push(`/scenery?station=${name.trim().replace(/ /g, '_')}`);
|
if (existingIdx != -1) this.extraInfoIndexes.splice(existingIdx, 1);
|
||||||
},
|
else this.extraInfoIndexes.push(id);
|
||||||
|
|
||||||
isAnotherDay(prevIndex: number, currIndex: number) {
|
|
||||||
if (currIndex == 0) return true;
|
|
||||||
|
|
||||||
return (
|
|
||||||
new Date(this.dispatcherHistory[prevIndex].timestampFrom).getDate() !=
|
|
||||||
new Date(this.dispatcherHistory[currIndex].timestampFrom).getDate()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '../../../styles/animations.scss';
|
|
||||||
@import '../../../styles/responsive.scss';
|
|
||||||
@import '../../../styles/badge.scss';
|
|
||||||
@import '../../../styles/variables.scss';
|
@import '../../../styles/variables.scss';
|
||||||
@import '../../../styles/JournalSection.scss';
|
@import '../../../styles/JournalSection.scss';
|
||||||
|
|
||||||
table.dispatchers-table {
|
|
||||||
--_bg-table: #111;
|
|
||||||
--_bg-head: #101010;
|
|
||||||
--_bg-row: #2f2f2f;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
position: relative;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
margin-bottom: 1em;
|
|
||||||
|
|
||||||
thead {
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
background-color: var(--_bg-head);
|
|
||||||
}
|
|
||||||
|
|
||||||
th {
|
|
||||||
padding: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
tr {
|
|
||||||
background-color: var(--_bg-row);
|
|
||||||
border-bottom: 2px solid black;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
padding: 0.75em;
|
|
||||||
|
|
||||||
.level-badge {
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.text {
|
|
||||||
&--online {
|
|
||||||
color: springgreen;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--offline {
|
|
||||||
color: #ddd;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -30,7 +30,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<transition name="dropdown-anim">
|
<transition name="dropdown-anim">
|
||||||
<div class="dropdown_wrapper" v-if="currentStatsTab !== null">
|
<div
|
||||||
|
class="dropdown_wrapper"
|
||||||
|
:class="{ 'dropdown-align-right': true }"
|
||||||
|
v-if="currentStatsTab !== null"
|
||||||
|
>
|
||||||
<keep-alive>
|
<keep-alive>
|
||||||
<component :is="currentStatsTab" :key="currentStatsTab"></component>
|
<component :is="currentStatsTab" :key="currentStatsTab"></component>
|
||||||
</keep-alive>
|
</keep-alive>
|
||||||
@@ -79,7 +83,10 @@ export default defineComponent({
|
|||||||
@import '../../styles/dropdown_filters.scss';
|
@import '../../styles/dropdown_filters.scss';
|
||||||
@import '../../styles/variables.scss';
|
@import '../../styles/variables.scss';
|
||||||
|
|
||||||
.dropdown_wrapper {
|
.dropdown_wrapper.dropdown-align-right {
|
||||||
max-width: 100%;
|
left: auto;
|
||||||
|
right: 0;
|
||||||
|
max-width: 700px;
|
||||||
|
// max-width: 100%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -0,0 +1,270 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="details-actions">
|
||||||
|
<button class="btn--action" @click="toggleExtraInfo">
|
||||||
|
<b>{{ $t('journal.entry-details') }}</b>
|
||||||
|
<img :src="`/images/icon-arrow-${showExtraInfo ? 'asc' : 'desc'}.svg`" alt="Arrow icon" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<router-link
|
||||||
|
v-if="driverRouteLocation !== null"
|
||||||
|
class="a-button btn--action btn-timetable"
|
||||||
|
:to="driverRouteLocation"
|
||||||
|
>
|
||||||
|
<img src="/images/icon-train.svg" alt="train icon" />
|
||||||
|
<b>{{ $t('journal.timetable-online-button') }}</b>
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="details-body" v-if="showExtraInfo">
|
||||||
|
<div class="g-separator"></div>
|
||||||
|
|
||||||
|
<EntryStops :timetable="timetable" />
|
||||||
|
|
||||||
|
<div class="g-separator"></div>
|
||||||
|
|
||||||
|
<div class="stock-specs">
|
||||||
|
<span class="badge" v-if="timetable.authorName">
|
||||||
|
<span>{{ $t('journal.dispatcher-name') }}</span>
|
||||||
|
<span>{{ timetable.authorName }}</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="badge" v-if="timetable.maxSpeed">
|
||||||
|
<span>{{ $t('journal.stock-max-speed') }}</span>
|
||||||
|
<span>{{ timetable.maxSpeed }}km/h</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="badge" v-if="timetable.stockLength">
|
||||||
|
<span>{{ $t('journal.stock-length') }}</span>
|
||||||
|
<span>
|
||||||
|
{{
|
||||||
|
currentHistoryIndex == 0
|
||||||
|
? timetable.stockLength
|
||||||
|
: stockHistory[currentHistoryIndex].stockLength || timetable.stockLength
|
||||||
|
}}m
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="badge" v-if="timetable.stockMass">
|
||||||
|
<span>{{ $t('journal.stock-mass') }}</span>
|
||||||
|
<span>
|
||||||
|
{{
|
||||||
|
Math.floor(
|
||||||
|
(currentHistoryIndex == 0
|
||||||
|
? timetable.stockMass
|
||||||
|
: stockHistory[currentHistoryIndex].stockMass || timetable.stockMass) / 1000
|
||||||
|
)
|
||||||
|
}}t
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stock-dangers" v-if="timetable.twr || timetable.skr">
|
||||||
|
<div class="g-separator"></div>
|
||||||
|
|
||||||
|
<b>{{ $t('journal.stock-dangers') }}:</b>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li v-if="timetable.twr">
|
||||||
|
<b class="text--primary">{{ $t('warnings.TWR') }} (TWR)</b>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li v-if="timetable.skr">
|
||||||
|
<b class="text--primary">{{ $t('warnings.SKR') }}</b>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li v-if="timetable.hasDangerousCargo">
|
||||||
|
<b class="text--primary">{{ $t('warnings.TN') }}</b>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li v-if="timetable.hasExtraDeliveries">
|
||||||
|
<b class="text--primary">{{ $t('warnings.PN') }}</b>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="dangers-notes" v-if="timetable.warningNotes">
|
||||||
|
<h4>{{ $t('warnings.header-title') }}</h4>
|
||||||
|
<p>
|
||||||
|
<i>{{ timetable.warningNotes }}</i>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Historia zmian w składzie -->
|
||||||
|
<div v-if="timetable.stockString || stockHistory.length != 0">
|
||||||
|
<div class="g-separator"></div>
|
||||||
|
<b>{{ $t('journal.stock-preview') }}:</b>
|
||||||
|
|
||||||
|
<div class="stock-history" v-if="stockHistory.length > 1">
|
||||||
|
<button
|
||||||
|
v-for="(sh, i) in stockHistory"
|
||||||
|
:key="i"
|
||||||
|
class="btn--action"
|
||||||
|
:data-checked="i == currentHistoryIndex"
|
||||||
|
@click.stop="currentHistoryIndex = i"
|
||||||
|
>
|
||||||
|
{{ sh.updatedAt }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="timetable.stockString" style="margin-top: 1em">
|
||||||
|
<StockList
|
||||||
|
:trainStockList="
|
||||||
|
(currentHistoryIndex == 0
|
||||||
|
? timetable.stockString
|
||||||
|
: stockHistory[currentHistoryIndex].stockString
|
||||||
|
).split(';')
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { PropType, defineComponent } from 'vue';
|
||||||
|
import StockList from '../../Global/StockList.vue';
|
||||||
|
import { API } from '../../../typings/api';
|
||||||
|
import { RouteLocationRaw } from 'vue-router';
|
||||||
|
import EntryStops from './EntryStops.vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: { StockList, EntryStops },
|
||||||
|
|
||||||
|
emits: ['toggleExtraInfo'],
|
||||||
|
|
||||||
|
props: {
|
||||||
|
showExtraInfo: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
timetable: {
|
||||||
|
type: Object as PropType<API.TimetableHistory.Data>,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
currentHistoryIndex: 0
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
stockHistory() {
|
||||||
|
return this.timetable.stockHistory
|
||||||
|
.slice()
|
||||||
|
.reverse()
|
||||||
|
.map((h) => {
|
||||||
|
const historyData = h.split('@');
|
||||||
|
return {
|
||||||
|
updatedAt: new Date(Number(historyData[0])).toLocaleTimeString(this.$i18n.locale, {
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit'
|
||||||
|
}),
|
||||||
|
stockString: historyData[1],
|
||||||
|
stockMass: Number(historyData[2]) || undefined,
|
||||||
|
stockLength: Number(historyData[3]) || undefined
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
driverRouteLocation(): RouteLocationRaw | null {
|
||||||
|
if (this.timetable.terminated) return null;
|
||||||
|
return {
|
||||||
|
name: 'DriverView',
|
||||||
|
query: {
|
||||||
|
trainId: `${this.timetable.driverId}|${this.timetable.trainNo}|eu`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onImageError(e: Event) {
|
||||||
|
const imageEl = e.target as HTMLImageElement;
|
||||||
|
imageEl.src = '/images/icon-unknown.png';
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleExtraInfo() {
|
||||||
|
this.$emit('toggleExtraInfo', this.timetable.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '../../../styles/variables.scss';
|
||||||
|
@import '../../../styles/responsive.scss';
|
||||||
|
@import '../../../styles/badge.scss';
|
||||||
|
|
||||||
|
.details-body {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5em;
|
||||||
|
margin-top: 1em;
|
||||||
|
|
||||||
|
button img {
|
||||||
|
height: 1.25em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stock-history {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5em;
|
||||||
|
margin-top: 1em;
|
||||||
|
|
||||||
|
button[data-checked='true'] {
|
||||||
|
color: $accentCol;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stock-specs {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5em;
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
span:last-child {
|
||||||
|
color: black;
|
||||||
|
background-color: $accentCol;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
margin: 0.5em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stock-dangers ul {
|
||||||
|
list-style: disc;
|
||||||
|
padding-left: 1em;
|
||||||
|
padding-top: 0.5em;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dangers-notes {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-top: 0.25em;
|
||||||
|
max-height: 200px;
|
||||||
|
max-width: 500px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include smallScreen() {
|
||||||
|
.stock-specs {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details-actions {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -3,13 +3,48 @@
|
|||||||
<span class="general-train">
|
<span class="general-train">
|
||||||
<span class="text--grayed">#{{ timetable.id }}</span>
|
<span class="text--grayed">#{{ timetable.id }}</span>
|
||||||
|
|
||||||
<span class="badges" v-if="timetable.skr || timetable.twr">
|
<span
|
||||||
<span class="train-badge twr" v-if="timetable.twr" :title="$t('general.TWR')">TWR</span>
|
class="train-badge twr"
|
||||||
<span class="train-badge skr" v-if="timetable.skr" :title="$t('general.SKR')">SKR</span>
|
v-if="timetable.twr"
|
||||||
|
data-tooltip-type="BaseTooltip"
|
||||||
|
:data-tooltip-content="$t('warnings.TWR')"
|
||||||
|
>
|
||||||
|
TWR
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span
|
||||||
|
class="train-badge skr"
|
||||||
|
v-if="timetable.skr"
|
||||||
|
data-tooltip-type="BaseTooltip"
|
||||||
|
:data-tooltip-content="$t('warnings.SKR')"
|
||||||
|
>
|
||||||
|
SKR
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span
|
||||||
|
class="train-badge tn"
|
||||||
|
v-if="timetable.hasDangerousCargo"
|
||||||
|
data-tooltip-type="BaseTooltip"
|
||||||
|
:data-tooltip-content="$t('warnings.TN')"
|
||||||
|
>
|
||||||
|
TN
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span
|
||||||
|
class="train-badge pn"
|
||||||
|
v-if="timetable.hasExtraDeliveries"
|
||||||
|
data-tooltip-type="BaseTooltip"
|
||||||
|
:data-tooltip-content="$t('warnings.PN')"
|
||||||
|
>
|
||||||
|
PN
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span>
|
<span>
|
||||||
<strong class="text--primary">
|
<strong
|
||||||
|
data-tooltip-type="BaseTooltip"
|
||||||
|
:data-tooltip-content="getCategoryExplanation(timetable.trainCategoryCode)"
|
||||||
|
class="text--primary tooltip-help"
|
||||||
|
>
|
||||||
{{ timetable.trainCategoryCode }}
|
{{ timetable.trainCategoryCode }}
|
||||||
</strong>
|
</strong>
|
||||||
<strong> {{ timetable.trainNo }}</strong>
|
<strong> {{ timetable.trainNo }}</strong>
|
||||||
@@ -23,17 +58,19 @@
|
|||||||
{{ timetable.driverLevel < 2 ? 'L' : `${timetable.driverLevel}` }}
|
{{ timetable.driverLevel < 2 ? 'L' : `${timetable.driverLevel}` }}
|
||||||
</strong>
|
</strong>
|
||||||
|
|
||||||
<strong
|
<router-link
|
||||||
v-if="apiStore.donatorsData.includes(timetable.driverName)"
|
v-if="apiStore.donatorsData.includes(timetable.driverName)"
|
||||||
class="text--donator"
|
class="text--donator"
|
||||||
:title="$t('donations.driver-message')"
|
data-tooltip-type="DonatorTooltip"
|
||||||
|
:data-tooltip-content="$t('donations.driver-message')"
|
||||||
|
:to="`/journal/timetables?search-driver=${timetable.driverName}`"
|
||||||
>
|
>
|
||||||
{{ timetable.driverName }}
|
<strong>{{ timetable.driverName }}</strong>
|
||||||
</strong>
|
</router-link>
|
||||||
|
|
||||||
<strong v-else>
|
<router-link v-else :to="`/journal/timetables?search-driver=${timetable.driverName}`">
|
||||||
{{ timetable.driverName }}
|
<strong>{{ timetable.driverName }}</strong>
|
||||||
</strong>
|
</router-link>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="general-time">
|
<span class="general-time">
|
||||||
@@ -61,15 +98,6 @@
|
|||||||
: `${$t('journal.timetable-abandoned')} ${localeTime(timetable.endDate, $i18n.locale)}`
|
: `${$t('journal.timetable-abandoned')} ${localeTime(timetable.endDate, $i18n.locale)}`
|
||||||
}}
|
}}
|
||||||
</b>
|
</b>
|
||||||
|
|
||||||
<button
|
|
||||||
v-if="timetable.terminated == false"
|
|
||||||
class="btn--action btn-timetable"
|
|
||||||
@click.stop="showTimetable(timetable, $event.currentTarget)"
|
|
||||||
>
|
|
||||||
<img src="/images/icon-train.svg" alt="train icon" />
|
|
||||||
<b>{{ $t('journal.timetable-online-button') }}</b>
|
|
||||||
</button>
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -79,12 +107,12 @@ import { PropType, defineComponent } from 'vue';
|
|||||||
|
|
||||||
import { API } from '../../../typings/api';
|
import { API } from '../../../typings/api';
|
||||||
import dateMixin from '../../../mixins/dateMixin';
|
import dateMixin from '../../../mixins/dateMixin';
|
||||||
import modalTrainMixin from '../../../mixins/modalTrainMixin';
|
|
||||||
import styleMixin from '../../../mixins/styleMixin';
|
import styleMixin from '../../../mixins/styleMixin';
|
||||||
import { useApiStore } from '../../../store/apiStore';
|
import { useApiStore } from '../../../store/apiStore';
|
||||||
|
import trainCategoryMixin from '../../../mixins/trainCategoryMixin';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
mixins: [dateMixin, modalTrainMixin, styleMixin],
|
mixins: [dateMixin, styleMixin, trainCategoryMixin],
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -97,14 +125,6 @@ export default defineComponent({
|
|||||||
type: Object as PropType<API.TimetableHistory.Data>,
|
type: Object as PropType<API.TimetableHistory.Data>,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
showTimetable(timetable: API.TimetableHistory.Data, target: EventTarget | null) {
|
|
||||||
if (timetable?.terminated) return;
|
|
||||||
|
|
||||||
this.selectModalTrain(timetable.driverName + timetable.trainNo.toString(), target);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@@ -131,7 +151,6 @@ export default defineComponent({
|
|||||||
gap: 0.25em;
|
gap: 0.25em;
|
||||||
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
line-height: 2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.general-time {
|
.general-time {
|
||||||
@@ -174,6 +193,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
@include smallScreen {
|
@include smallScreen {
|
||||||
.item-general {
|
.item-general {
|
||||||
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="item-status" style="margin: 0.5em 0">
|
<div class="entry-status" style="margin: 0.5em 0">
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
:progressPercent="~~((timetable.currentDistance / timetable.routeDistance) * 100)"
|
:progressPercent="~~((timetable.currentDistance / timetable.routeDistance) * 100)"
|
||||||
:progressType="!timetable.fulfilled && timetable.terminated ? 'abandoned' : ''"
|
:progressType="!timetable.fulfilled && timetable.terminated ? 'abandoned' : ''"
|
||||||
@@ -61,7 +61,7 @@ export default defineComponent({
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '../../../styles/responsive.scss';
|
@import '../../../styles/responsive.scss';
|
||||||
|
|
||||||
.item-status {
|
.entry-status {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@@ -0,0 +1,299 @@
|
|||||||
|
<template>
|
||||||
|
<div class="entry-stops">
|
||||||
|
<ul class="stop-list">
|
||||||
|
<li v-for="(stop, i) in timetableStops" :key="stop.stopName">
|
||||||
|
<span class="stop-label" :data-confirmed="stop.isConfirmed">
|
||||||
|
<span v-if="i > 0">></span>
|
||||||
|
|
||||||
|
<span class="stop-name">{{ stop.stopName }}</span>
|
||||||
|
|
||||||
|
<span
|
||||||
|
class="stop-date"
|
||||||
|
v-if="stop.scheduledArrivalTimestamp != 0"
|
||||||
|
:data-delayed="
|
||||||
|
stop.isConfirmed && stop.arrivalTimestamp - stop.scheduledArrivalTimestamp > 0
|
||||||
|
"
|
||||||
|
:data-preponed="
|
||||||
|
stop.isConfirmed &&
|
||||||
|
stop.arrivalTimestamp != 0 &&
|
||||||
|
stop.arrivalTimestamp - stop.scheduledArrivalTimestamp < 0
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-if="stop.isConfirmed && stop.arrivalTimestamp - stop.scheduledArrivalTimestamp != 0"
|
||||||
|
>
|
||||||
|
p. <s>{{ timestampToString(stop.scheduledArrivalTimestamp) }}</s>
|
||||||
|
{{ timestampToString(stop.arrivalTimestamp) }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span v-else>p. {{ timestampToString(stop.scheduledArrivalTimestamp) }}</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span
|
||||||
|
class="stop-time"
|
||||||
|
v-if="stop.stopTime > 0"
|
||||||
|
:data-stop-ph="stop.stopType.includes('ph')"
|
||||||
|
:data-stop-pt="stop.stopType.includes('pt')"
|
||||||
|
:data-stop-pm="stop.stopType.includes('pm')"
|
||||||
|
>
|
||||||
|
/<span>{{ stop.stopTime }} {{ stop.stopType }}</span
|
||||||
|
>/
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span
|
||||||
|
class="stop-date"
|
||||||
|
v-if="
|
||||||
|
stop.scheduledDepartureTimestamp != 0 &&
|
||||||
|
stop.scheduledArrivalTimestamp != stop.scheduledDepartureTimestamp
|
||||||
|
"
|
||||||
|
:data-delayed="
|
||||||
|
stop.isConfirmed && stop.departureTimestamp - stop.scheduledDepartureTimestamp > 0
|
||||||
|
"
|
||||||
|
:data-preponed="
|
||||||
|
stop.isConfirmed &&
|
||||||
|
stop.departureTimestamp != 0 &&
|
||||||
|
stop.departureTimestamp - stop.scheduledDepartureTimestamp < 0
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-if="
|
||||||
|
stop.isConfirmed && stop.departureTimestamp - stop.scheduledDepartureTimestamp != 0
|
||||||
|
"
|
||||||
|
>
|
||||||
|
o. <s>{{ timestampToString(stop.scheduledDepartureTimestamp) }}</s>
|
||||||
|
{{ timestampToString(stop.departureTimestamp) }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span v-else>o. {{ timestampToString(stop.scheduledDepartureTimestamp) }}</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<ul class="timetable-path-list" v-if="timetablePathDetails">
|
||||||
|
<li
|
||||||
|
v-for="(pathData, i) in timetablePathDetails"
|
||||||
|
:data-visited="pathData.isVisited"
|
||||||
|
:data-next-visited="
|
||||||
|
i < timetablePathDetails.length - 1 && timetablePathDetails[i + 1].isVisited
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<span v-if="i > 0" class="path-arrow">></span>
|
||||||
|
<span class="path-arrival" v-if="pathData.arrival">{{ pathData.arrival }}</span>
|
||||||
|
<b class="path-scenery">{{ pathData.sceneryName }}</b>
|
||||||
|
<span class="path-departure" v-if="pathData.departure">{{ pathData.departure }}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { PropType, defineComponent } from 'vue';
|
||||||
|
import dateMixin from '../../../mixins/dateMixin';
|
||||||
|
import { API } from '../../../typings/api';
|
||||||
|
|
||||||
|
interface ITimetableStopDetails {
|
||||||
|
stopName: string;
|
||||||
|
stopComments: string | null;
|
||||||
|
stopTime: number;
|
||||||
|
stopType: string;
|
||||||
|
arrivalTimestamp: number;
|
||||||
|
scheduledArrivalTimestamp: number;
|
||||||
|
departureTimestamp: number;
|
||||||
|
scheduledDepartureTimestamp: number;
|
||||||
|
isConfirmed: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
mixins: [dateMixin],
|
||||||
|
|
||||||
|
props: {
|
||||||
|
timetable: {
|
||||||
|
type: Object as PropType<API.TimetableHistory.Data>,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
timetablePathDetails() {
|
||||||
|
if (!this.timetable.path || this.timetable.path == '') return null;
|
||||||
|
|
||||||
|
return this.timetable.path.split(';').map((pathEl, i) => {
|
||||||
|
const [arrival, name, departure] = pathEl.split(',');
|
||||||
|
const sceneryName = name.split(' ').slice(0, -1).join(' ');
|
||||||
|
const sceneryHash = name.split(' ').pop()?.replace('.sc', '') ?? '';
|
||||||
|
const isVisited = this.timetable.visitedSceneries.includes(sceneryHash);
|
||||||
|
|
||||||
|
return {
|
||||||
|
arrival,
|
||||||
|
sceneryName,
|
||||||
|
sceneryHash,
|
||||||
|
departure,
|
||||||
|
isVisited,
|
||||||
|
isVisitedOffline:
|
||||||
|
!isVisited &&
|
||||||
|
this.timetable.visitedSceneries.includes(`${sceneryName} ${sceneryHash}.sc`)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
timetableStops(): ITimetableStopDetails[] {
|
||||||
|
const timetable = this.timetable;
|
||||||
|
|
||||||
|
const stopNames = timetable.sceneriesString.split('%');
|
||||||
|
|
||||||
|
return stopNames.reduce<ITimetableStopDetails[]>((acc, stopName, i, arr) => {
|
||||||
|
const arrivalDate =
|
||||||
|
i == arr.length - 1
|
||||||
|
? (timetable.checkpointArrivals.at(i) ?? timetable.endDate)
|
||||||
|
: timetable.checkpointArrivals.at(i);
|
||||||
|
|
||||||
|
const scheduledArrivalDate =
|
||||||
|
i == arr.length - 1
|
||||||
|
? (timetable.checkpointArrivalsScheduled.at(i) ?? timetable.scheduledEndDate)
|
||||||
|
: timetable.checkpointArrivalsScheduled.at(i);
|
||||||
|
|
||||||
|
const departureDate =
|
||||||
|
i == 0
|
||||||
|
? (timetable.checkpointDepartures.at(i) ?? timetable.beginDate)
|
||||||
|
: timetable.checkpointDepartures.at(i);
|
||||||
|
|
||||||
|
const scheduledDepartureDate =
|
||||||
|
i == 0
|
||||||
|
? (timetable.checkpointDeparturesScheduled.at(i) ?? timetable.scheduledBeginDate)
|
||||||
|
: timetable.checkpointDeparturesScheduled.at(i);
|
||||||
|
|
||||||
|
const stopTime = Number(timetable.checkpointStopTypes.at(i)?.split(',')[0]) || 0;
|
||||||
|
const stopType = timetable.checkpointStopTypes.at(i)?.split(',').slice(1).join(',') || 'pt';
|
||||||
|
const stopComments = timetable.checkpointComments.at(i) ?? null;
|
||||||
|
|
||||||
|
acc.push({
|
||||||
|
stopName,
|
||||||
|
stopTime,
|
||||||
|
stopType,
|
||||||
|
stopComments,
|
||||||
|
arrivalTimestamp: this.dateStringToTimestamp(arrivalDate),
|
||||||
|
scheduledArrivalTimestamp: this.dateStringToTimestamp(scheduledArrivalDate),
|
||||||
|
departureTimestamp: this.dateStringToTimestamp(departureDate),
|
||||||
|
scheduledDepartureTimestamp: this.dateStringToTimestamp(scheduledDepartureDate),
|
||||||
|
isConfirmed: i < timetable.confirmedStopsCount
|
||||||
|
});
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '../../../styles/badge.scss';
|
||||||
|
|
||||||
|
.entry-stops {
|
||||||
|
word-wrap: break-word;
|
||||||
|
gap: 0.25em;
|
||||||
|
font-size: 0.95em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stop-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5em;
|
||||||
|
padding: 0.5em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stop-label {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5em;
|
||||||
|
align-items: center;
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
&[data-confirmed='true'] > .stop-name {
|
||||||
|
color: lightgreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-confirmed='true'] > .stop-date:not([data-preponed='true']):not([data-delayed='true']) {
|
||||||
|
color: lightgreen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stop-name {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stop-date {
|
||||||
|
color: #ccc;
|
||||||
|
|
||||||
|
s {
|
||||||
|
color: #aaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-delayed='true'] {
|
||||||
|
color: salmon;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-preponed='true'] {
|
||||||
|
color: mediumspringgreen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stop-time {
|
||||||
|
&[data-stop-pt='true'] span {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-stop-ph='true'] span,
|
||||||
|
&[data-stop-pm='true'] span {
|
||||||
|
color: gold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.timetable-path-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5em 0;
|
||||||
|
padding: 0.5em 0;
|
||||||
|
color: #ccc;
|
||||||
|
|
||||||
|
li > .path-scenery:first-child,
|
||||||
|
li > .path-arrival:nth-child(2) {
|
||||||
|
border-radius: 0.5em 0 0 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
li > :last-child {
|
||||||
|
border-radius: 0 0.5em 0.5em 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-scenery {
|
||||||
|
padding: 0.25em 0.5em;
|
||||||
|
background-color: #303030;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-arrival,
|
||||||
|
.path-departure {
|
||||||
|
padding: 0.25em;
|
||||||
|
display: inline-block;
|
||||||
|
background-color: #4e4e4e;
|
||||||
|
min-width: 25px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-arrow {
|
||||||
|
padding: 0 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timetable-path-list > li[data-visited='true'] {
|
||||||
|
.path-arrival,
|
||||||
|
.path-scenery,
|
||||||
|
.path-arrow {
|
||||||
|
color: lightgreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-next-visited='true'] .path-departure {
|
||||||
|
color: lightgreen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
<hr class="header-separator" />
|
<hr class="header-separator" />
|
||||||
|
|
||||||
<div class="info-stats">
|
<div class="info-stats">
|
||||||
<span class="stat-badge">
|
<span class="badge stat-badge">
|
||||||
<span>{{ $t('journal.driver-stats.timetables') }}</span>
|
<span>{{ $t('journal.driver-stats.timetables') }}</span>
|
||||||
<span
|
<span
|
||||||
>{{ store.driverStatsData._count.fulfilled }} /
|
>{{ store.driverStatsData._count.fulfilled }} /
|
||||||
@@ -20,17 +20,17 @@
|
|||||||
>
|
>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="stat-badge">
|
<span class="badge stat-badge">
|
||||||
<span>{{ $t('journal.driver-stats.longest-timetable') }}</span>
|
<span>{{ $t('journal.driver-stats.longest-timetable') }}</span>
|
||||||
<span> {{ store.driverStatsData._max.routeDistance.toFixed(2) }}km </span>
|
<span> {{ store.driverStatsData._max.routeDistance.toFixed(2) }}km </span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="stat-badge">
|
<span class="badge stat-badge">
|
||||||
<span>{{ $t('journal.driver-stats.avg-timetable') }}</span>
|
<span>{{ $t('journal.driver-stats.avg-timetable') }}</span>
|
||||||
<span> {{ store.driverStatsData._avg.routeDistance.toFixed(2) }}km </span>
|
<span> {{ store.driverStatsData._avg.routeDistance.toFixed(2) }}km </span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="stat-badge">
|
<span class="badge stat-badge">
|
||||||
<span>{{ $t('journal.driver-stats.distance') }}</span>
|
<span>{{ $t('journal.driver-stats.distance') }}</span>
|
||||||
<span>
|
<span>
|
||||||
{{ store.driverStatsData._sum.currentDistance.toFixed(2) }} /
|
{{ store.driverStatsData._sum.currentDistance.toFixed(2) }} /
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="stat-badge">
|
<span class="badge stat-badge">
|
||||||
<span>{{ $t('journal.driver-stats.stations') }}</span>
|
<span>{{ $t('journal.driver-stats.stations') }}</span>
|
||||||
<span>
|
<span>
|
||||||
{{ store.driverStatsData._sum.confirmedStopsCount }} /
|
{{ store.driverStatsData._sum.confirmedStopsCount }} /
|
||||||
|
|||||||
@@ -0,0 +1,149 @@
|
|||||||
|
<template>
|
||||||
|
<li class="timetable-history-entry">
|
||||||
|
<!-- General -->
|
||||||
|
<EntryGeneral :timetable="timetableEntry" />
|
||||||
|
|
||||||
|
<div @click="toggleExtraInfo" style="cursor: pointer">
|
||||||
|
<!-- Route -->
|
||||||
|
<div class="entry-route">
|
||||||
|
<b>{{ timetableEntry.route.replace('|', ' - ') }}</b>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<!-- Status -->
|
||||||
|
<EntryStatus :timetable="timetableEntry" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Extra -->
|
||||||
|
<EntryDetails
|
||||||
|
:timetable="timetableEntry"
|
||||||
|
:show-extra-info="showExtraInfo"
|
||||||
|
@toggle-extra-info="toggleExtraInfo"
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, PropType } from 'vue';
|
||||||
|
import { API } from '../../../typings/api';
|
||||||
|
import { useApiStore } from '../../../store/apiStore';
|
||||||
|
import { Journal } from '../typings';
|
||||||
|
|
||||||
|
import trainCategoryMixin from '../../../mixins/trainCategoryMixin';
|
||||||
|
import dateMixin from '../../../mixins/dateMixin';
|
||||||
|
import styleMixin from '../../../mixins/styleMixin';
|
||||||
|
|
||||||
|
import EntryGeneral from './EntryGeneral.vue';
|
||||||
|
import EntryStatus from './EntryStatus.vue';
|
||||||
|
import EntryDetails from './EntryDetails.vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
|
timetableEntry: {
|
||||||
|
type: Object as PropType<API.TimetableHistory.Data>,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
showExtraInfo: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
components: { EntryDetails, EntryGeneral, EntryStatus },
|
||||||
|
mixins: [trainCategoryMixin, dateMixin, styleMixin],
|
||||||
|
emits: ['toggleShowExtraInfo'],
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
apiStore: useApiStore()
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
timetablePathDetails() {
|
||||||
|
if (!this.timetableEntry.path || this.timetableEntry.path == '') return null;
|
||||||
|
|
||||||
|
return this.timetableEntry.path.split(';').map((pathEl, i) => {
|
||||||
|
const [arrival, name, departure] = pathEl.split(',');
|
||||||
|
const sceneryName = name.split(' ').slice(0, -1).join(' ');
|
||||||
|
const sceneryHash = name.split(' ').pop()?.replace('.sc', '') ?? '';
|
||||||
|
|
||||||
|
return {
|
||||||
|
arrival,
|
||||||
|
sceneryName,
|
||||||
|
sceneryHash,
|
||||||
|
departure,
|
||||||
|
isVisited: this.timetableEntry.visitedSceneries?.includes(sceneryHash) ?? false
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
timetableStops(): Journal.TimetableStopDetails[] {
|
||||||
|
const timetableEntry = this.timetableEntry;
|
||||||
|
|
||||||
|
const stopNames = timetableEntry.sceneriesString.split('%');
|
||||||
|
|
||||||
|
return stopNames.reduce<Journal.TimetableStopDetails[]>((acc, stopName, i, arr) => {
|
||||||
|
const arrivalDate =
|
||||||
|
i == arr.length - 1
|
||||||
|
? (timetableEntry.checkpointArrivals.at(i) ?? timetableEntry.endDate)
|
||||||
|
: timetableEntry.checkpointArrivals.at(i);
|
||||||
|
|
||||||
|
const scheduledArrivalDate =
|
||||||
|
i == arr.length - 1
|
||||||
|
? (timetableEntry.checkpointArrivalsScheduled.at(i) ?? timetableEntry.scheduledEndDate)
|
||||||
|
: timetableEntry.checkpointArrivalsScheduled.at(i);
|
||||||
|
|
||||||
|
const departureDate =
|
||||||
|
i == 0
|
||||||
|
? (timetableEntry.checkpointDepartures.at(i) ?? timetableEntry.beginDate)
|
||||||
|
: timetableEntry.checkpointDepartures.at(i);
|
||||||
|
|
||||||
|
const scheduledDepartureDate =
|
||||||
|
i == 0
|
||||||
|
? (timetableEntry.checkpointDeparturesScheduled.at(i) ??
|
||||||
|
timetableEntry.scheduledBeginDate)
|
||||||
|
: timetableEntry.checkpointDeparturesScheduled.at(i);
|
||||||
|
|
||||||
|
const stopTime = Number(timetableEntry.checkpointStopTypes.at(i)?.split(',')[0]) || 0;
|
||||||
|
const stopType = timetableEntry.checkpointStopTypes.at(i)?.split(',')[1] || '';
|
||||||
|
|
||||||
|
acc.push({
|
||||||
|
stopName,
|
||||||
|
arrivalTimestamp: this.dateStringToTimestamp(arrivalDate),
|
||||||
|
scheduledArrivalTimestamp: this.dateStringToTimestamp(scheduledArrivalDate),
|
||||||
|
departureTimestamp: this.dateStringToTimestamp(departureDate),
|
||||||
|
scheduledDepartureTimestamp: this.dateStringToTimestamp(scheduledDepartureDate),
|
||||||
|
stopTime,
|
||||||
|
stopType,
|
||||||
|
isConfirmed: i < timetableEntry.confirmedStopsCount
|
||||||
|
});
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
toggleExtraInfo() {
|
||||||
|
this.$emit('toggleShowExtraInfo');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '../../../styles/responsive.scss';
|
||||||
|
|
||||||
|
.timetable-history-entry {
|
||||||
|
background-color: #1a1a1a;
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include smallScreen {
|
||||||
|
.entry-route {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,62 +1,40 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<transition name="status-anim" mode="out-in">
|
<div class="journal_warning" v-if="store.isOffline">
|
||||||
<div :key="dataStatus">
|
{{ $t('app.offline') }}
|
||||||
<div class="journal_warning" v-if="store.isOffline">
|
</div>
|
||||||
{{ $t('app.offline') }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Loading v-else-if="dataStatus == Status.Data.Loading" />
|
<Loading v-else-if="dataStatus == Status.Data.Loading" />
|
||||||
|
|
||||||
<div v-else-if="dataStatus == Status.Data.Error" class="journal_warning error">
|
<div v-else-if="dataStatus == Status.Data.Error" class="journal_warning error">
|
||||||
{{ $t('app.error') }}
|
{{ $t('app.error') }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="timetableHistory.length == 0" class="journal_warning">
|
<div v-else-if="timetableHistory.length == 0" class="journal_warning">
|
||||||
{{ $t('app.no-result') }}
|
{{ $t('app.no-result') }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<ul class="journal-list">
|
<transition-group name="list-anim" class="journal-list" tag="ul">
|
||||||
<transition-group name="list-anim">
|
<JournalTimetableEntry
|
||||||
<li
|
v-for="(timetableEntry, i) in timetableHistory"
|
||||||
v-for="{ timetable, showExtraInfo } in computedTimetableHistory"
|
:key="timetableEntry.id"
|
||||||
class="journal_item"
|
:timetableEntry="timetableEntry"
|
||||||
:key="timetable.id"
|
:onToggleShowExtraInfo="() => toggleExtraInfo(timetableEntry.id)"
|
||||||
@click="showExtraInfo.value = !showExtraInfo.value"
|
:showExtraInfo="extraInfoIndexes.includes(timetableEntry.id)"
|
||||||
>
|
/>
|
||||||
<div class="journal_item-info">
|
</transition-group>
|
||||||
<!-- General -->
|
|
||||||
<TimetableGeneral :timetable="timetable" />
|
|
||||||
<!-- Route -->
|
|
||||||
<span class="item-route">
|
|
||||||
<b>{{ timetable.route.replace('|', ' - ') }}</b>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<hr />
|
<AddDataButton
|
||||||
<!-- Stops -->
|
:list="timetableHistory"
|
||||||
<TimetableStops :timetable="timetable" :showExtraInfo="showExtraInfo.value" />
|
:scrollDataLoaded="scrollDataLoaded"
|
||||||
<!-- Status -->
|
:scrollNoMoreData="scrollNoMoreData"
|
||||||
<TimetableStatus :timetable="timetable" />
|
@addHistoryData="addHistoryData"
|
||||||
|
/>
|
||||||
<!-- Extra -->
|
</div>
|
||||||
<TimetableDetails :timetable="timetable" :showExtraInfo="showExtraInfo.value" />
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</transition-group>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<AddDataButton
|
|
||||||
:list="timetableHistory"
|
|
||||||
:scrollDataLoaded="scrollDataLoaded"
|
|
||||||
:scrollNoMoreData="scrollNoMoreData"
|
|
||||||
@addHistoryData="addHistoryData"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
|
|
||||||
<div class="journal_warning" v-if="scrollNoMoreData">{{ $t('journal.no-further-data') }}</div>
|
<div class="journal_warning" v-if="scrollNoMoreData">{{ $t('journal.no-further-data') }}</div>
|
||||||
|
|
||||||
<div class="journal_warning" v-else-if="!scrollDataLoaded">
|
<div class="journal_warning" v-else-if="!scrollDataLoaded">
|
||||||
{{ $t('journal.loading-further-data') }}
|
{{ $t('journal.loading-further-data') }}
|
||||||
</div>
|
</div>
|
||||||
@@ -64,28 +42,21 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, PropType, ref } from 'vue';
|
import { defineComponent, PropType } from 'vue';
|
||||||
|
|
||||||
import Loading from '../../Global/Loading.vue';
|
import Loading from '../../Global/Loading.vue';
|
||||||
import AddDataButton from '../../Global/AddDataButton.vue';
|
import AddDataButton from '../../Global/AddDataButton.vue';
|
||||||
|
import JournalTimetableEntry from './JournalTimetableEntry.vue';
|
||||||
|
|
||||||
import { useMainStore } from '../../../store/mainStore';
|
import { useMainStore } from '../../../store/mainStore';
|
||||||
import { Status } from '../../../typings/common';
|
import { Status } from '../../../typings/common';
|
||||||
import { API } from '../../../typings/api';
|
import { API } from '../../../typings/api';
|
||||||
|
|
||||||
import TimetableGeneral from './TimetableGeneral.vue';
|
|
||||||
import TimetableStops from './TimetableStops.vue';
|
|
||||||
import TimetableStatus from './TimetableStatus.vue';
|
|
||||||
import TimetableDetails from './TimetableDetails.vue';
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
Loading,
|
Loading,
|
||||||
AddDataButton,
|
AddDataButton,
|
||||||
TimetableDetails,
|
JournalTimetableEntry
|
||||||
TimetableGeneral,
|
|
||||||
TimetableStatus,
|
|
||||||
TimetableStops
|
|
||||||
},
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
@@ -110,16 +81,26 @@ export default defineComponent({
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
Status,
|
Status,
|
||||||
store: useMainStore()
|
store: useMainStore(),
|
||||||
|
extraInfoIndexes: [] as number[]
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
watch: {
|
||||||
computedTimetableHistory() {
|
'$route.query': {
|
||||||
return this.timetableHistory.map((timetable) => ({
|
deep: true,
|
||||||
timetable,
|
handler() {
|
||||||
showExtraInfo: ref(false)
|
this.extraInfoIndexes.length = 0;
|
||||||
}));
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
toggleExtraInfo(id: number) {
|
||||||
|
const existingIdx = this.extraInfoIndexes.indexOf(id);
|
||||||
|
|
||||||
|
if (existingIdx != -1) this.extraInfoIndexes.splice(existingIdx, 1);
|
||||||
|
else this.extraInfoIndexes.push(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,195 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<div class="details-actions">
|
|
||||||
<button class="btn--action">
|
|
||||||
<b>{{ $t('journal.stock-info') }}</b>
|
|
||||||
<img :src="`/images/icon-arrow-${showExtraInfo ? 'asc' : 'desc'}.svg`" alt="Arrow icon" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="details-body" v-if="timetable.stockString && timetable.stockMass && showExtraInfo">
|
|
||||||
<hr />
|
|
||||||
|
|
||||||
<div class="stock-specs">
|
|
||||||
<span class="badge">
|
|
||||||
<span>{{ $t('journal.dispatcher-name') }}</span>
|
|
||||||
<span>{{ timetable.authorName }}</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="stock-specs">
|
|
||||||
<span class="badge">
|
|
||||||
<span>{{ $t('journal.stock-max-speed') }}</span>
|
|
||||||
<span>{{ timetable.maxSpeed }}km/h</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span class="badge">
|
|
||||||
<span>{{ $t('journal.stock-length') }}</span>
|
|
||||||
<span>
|
|
||||||
{{
|
|
||||||
currentHistoryIndex == 0
|
|
||||||
? timetable.stockLength
|
|
||||||
: stockHistory[currentHistoryIndex].stockLength || timetable.stockLength
|
|
||||||
}}m
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span class="badge">
|
|
||||||
<span>{{ $t('journal.stock-mass') }}</span>
|
|
||||||
<span>
|
|
||||||
{{
|
|
||||||
Math.floor(
|
|
||||||
(currentHistoryIndex == 0
|
|
||||||
? timetable.stockMass!
|
|
||||||
: stockHistory[currentHistoryIndex].stockMass || timetable.stockMass) / 1000
|
|
||||||
)
|
|
||||||
}}t
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Historia zmian w składzie -->
|
|
||||||
<div class="stock-history" v-if="stockHistory.length > 1">
|
|
||||||
<button
|
|
||||||
v-for="(sh, i) in stockHistory"
|
|
||||||
:key="i"
|
|
||||||
class="btn--action"
|
|
||||||
:data-checked="i == currentHistoryIndex"
|
|
||||||
@click.stop="currentHistoryIndex = i"
|
|
||||||
>
|
|
||||||
{{ sh.updatedAt }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<StockList
|
|
||||||
:trainStockList="
|
|
||||||
(currentHistoryIndex == 0
|
|
||||||
? timetable.stockString
|
|
||||||
: stockHistory[currentHistoryIndex].stockString
|
|
||||||
).split(';')
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { PropType, defineComponent } from 'vue';
|
|
||||||
import StockList from '../../Global/StockList.vue';
|
|
||||||
import { API } from '../../../typings/api';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: { StockList },
|
|
||||||
props: {
|
|
||||||
showExtraInfo: {
|
|
||||||
type: Boolean,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
timetable: {
|
|
||||||
type: Object as PropType<API.TimetableHistory.Data>,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
currentHistoryIndex: 0
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
stockHistory() {
|
|
||||||
return this.timetable.stockHistory
|
|
||||||
.slice()
|
|
||||||
.reverse()
|
|
||||||
.map((h) => {
|
|
||||||
const historyData = h.split('@');
|
|
||||||
return {
|
|
||||||
updatedAt: new Date(Number(historyData[0])).toLocaleTimeString(this.$i18n.locale, {
|
|
||||||
hour: '2-digit',
|
|
||||||
minute: '2-digit'
|
|
||||||
}),
|
|
||||||
stockString: historyData[1],
|
|
||||||
stockMass: Number(historyData[2]) || undefined,
|
|
||||||
stockLength: Number(historyData[3]) || undefined
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
onImageError(e: Event) {
|
|
||||||
const imageEl = e.target as HTMLImageElement;
|
|
||||||
imageEl.src = '/images/icon-unknown.png';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
@import '../../../styles/variables.scss';
|
|
||||||
@import '../../../styles/responsive.scss';
|
|
||||||
@import '../../../styles/badge.scss';
|
|
||||||
|
|
||||||
.details-body {
|
|
||||||
margin-top: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.details-actions {
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
button img {
|
|
||||||
height: 1.25em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.stock-history {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 0.5em;
|
|
||||||
margin-top: 1em;
|
|
||||||
|
|
||||||
button[data-checked='true'] {
|
|
||||||
color: $accentCol;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.stock-specs {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 0.5em;
|
|
||||||
margin-top: 0.5em;
|
|
||||||
|
|
||||||
.badge {
|
|
||||||
margin: 0;
|
|
||||||
|
|
||||||
span:last-child {
|
|
||||||
color: black;
|
|
||||||
background-color: $accentCol;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.stock-list {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-end;
|
|
||||||
overflow: auto;
|
|
||||||
|
|
||||||
padding-bottom: 0.5em;
|
|
||||||
|
|
||||||
li > div {
|
|
||||||
margin: 1em 0;
|
|
||||||
|
|
||||||
text-align: center;
|
|
||||||
color: #aaa;
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include smallScreen() {
|
|
||||||
.stock-specs {
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.details-actions {
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
<template>
|
|
||||||
<ul class="journal-list">
|
|
||||||
<transition-group name="list-anim">
|
|
||||||
<li
|
|
||||||
v-for="{ timetable, showExtraInfo } in computedTimetableHistory"
|
|
||||||
class="journal_item"
|
|
||||||
:key="timetable.id"
|
|
||||||
@click="showExtraInfo.value = !showExtraInfo.value"
|
|
||||||
>
|
|
||||||
<div class="journal_item-info">
|
|
||||||
<!-- General -->
|
|
||||||
<TimetableGeneral :timetable="timetable" />
|
|
||||||
<!-- Route -->
|
|
||||||
<span class="item-route">
|
|
||||||
<b>{{ timetable.route.replace('|', ' - ') }}</b>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<hr />
|
|
||||||
<!-- Stops -->
|
|
||||||
<TimetableStops :timetable="timetable" :showExtraInfo="showExtraInfo.value" />
|
|
||||||
<!-- Status -->
|
|
||||||
<TimetableStatus :timetable="timetable" />
|
|
||||||
|
|
||||||
<button class="btn--action btn--show">
|
|
||||||
{{ $t('journal.stock-info') }}
|
|
||||||
<img
|
|
||||||
:src="`/images/icon-arrow-${showExtraInfo.value ? 'asc' : 'desc'}.svg`"
|
|
||||||
alt="Arrow icon"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
<!-- Extra -->
|
|
||||||
<TimetableExtra :timetable="timetable" :showExtraInfo="showExtraInfo.value" />
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</transition-group>
|
|
||||||
</ul>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { PropType, defineComponent, ref } from 'vue';
|
|
||||||
|
|
||||||
import TimetableGeneral from './TimetableGeneral.vue';
|
|
||||||
import TimetableStops from './TimetableStops.vue';
|
|
||||||
import TimetableStatus from './TimetableStatus.vue';
|
|
||||||
import TimetableExtra from './TimetableExtra.vue';
|
|
||||||
import { API } from '../../../typings/api';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: { TimetableGeneral, TimetableStops, TimetableStatus, TimetableExtra },
|
|
||||||
|
|
||||||
props: {
|
|
||||||
timetableHistory: {
|
|
||||||
type: Array as PropType<API.TimetableHistory.Response>,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
computedTimetableHistory() {
|
|
||||||
return this.timetableHistory.map((timetable) => ({
|
|
||||||
timetable,
|
|
||||||
showExtraInfo: ref(false)
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
@import '../../../styles/variables';
|
|
||||||
@import '../../../styles/responsive';
|
|
||||||
@import '../../../styles/JournalSection';
|
|
||||||
|
|
||||||
.btn--show {
|
|
||||||
display: flex;
|
|
||||||
font-weight: bold;
|
|
||||||
padding: 0.2em 0.45em;
|
|
||||||
|
|
||||||
img {
|
|
||||||
height: 1.3em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hr {
|
|
||||||
margin: 0.25em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@include smallScreen {
|
|
||||||
.journal_item-info {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-route {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn--show {
|
|
||||||
margin: 1em auto 0 auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="stop-list" v-if="showExtraInfo == true">
|
|
||||||
<span
|
|
||||||
v-for="(stop, i) in timetableStops.filter((_, i) =>
|
|
||||||
!showExtraInfo ? i == 0 || i == timetableStops.length - 1 : true
|
|
||||||
)"
|
|
||||||
class="stop-list-item"
|
|
||||||
:key="stop.stopName"
|
|
||||||
:data-confirmed="stop.confirmed"
|
|
||||||
>
|
|
||||||
<span v-if="i > 0">
|
|
||||||
>
|
|
||||||
<span v-if="!showExtraInfo && i == 1 && timetableStops.length > 2">
|
|
||||||
... (+{{ timetableStops.length - 2 }}) >
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span class="stop-name">{{ stop.stopName }}</span>
|
|
||||||
<span v-html="stop.html"></span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { PropType, defineComponent } from 'vue';
|
|
||||||
import dateMixin from '../../../mixins/dateMixin';
|
|
||||||
import { API } from '../../../typings/api';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
mixins: [dateMixin],
|
|
||||||
|
|
||||||
props: {
|
|
||||||
showExtraInfo: {
|
|
||||||
type: Boolean,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
|
|
||||||
timetable: {
|
|
||||||
type: Object as PropType<API.TimetableHistory.Data>,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
timetableStops() {
|
|
||||||
const timetable = this.timetable;
|
|
||||||
|
|
||||||
const stopNames = timetable.sceneriesString.split('%');
|
|
||||||
|
|
||||||
const beginDateHTML = ` (o. ${
|
|
||||||
timetable.beginDate != timetable.scheduledBeginDate
|
|
||||||
? `<s class="text--grayed">${this.localeTime(timetable.beginDate, this.$i18n.locale)}</s>`
|
|
||||||
: ''
|
|
||||||
} <span>${this.localeTime(timetable.scheduledBeginDate, this.$i18n.locale)}</span>)`;
|
|
||||||
|
|
||||||
const endDateHTML = ` (p. ${
|
|
||||||
timetable.endDate != timetable.scheduledEndDate && timetable.fulfilled
|
|
||||||
? `<s class="text--grayed">${this.localeTime(timetable.endDate, this.$i18n.locale)}</s>`
|
|
||||||
: ''
|
|
||||||
} <span>${this.localeTime(timetable.scheduledEndDate, this.$i18n.locale)}</span>)`;
|
|
||||||
|
|
||||||
return stopNames.map((stopName, i) => {
|
|
||||||
const confirmed = i < timetable.confirmedStopsCount;
|
|
||||||
if (i == 0) return { stopName, html: beginDateHTML, confirmed };
|
|
||||||
if (i == stopNames.length - 1) return { stopName, html: endDateHTML, confirmed };
|
|
||||||
|
|
||||||
const departureDateScheduled = this.stringToDate(
|
|
||||||
timetable.checkpointDeparturesScheduled?.at(i)
|
|
||||||
);
|
|
||||||
const departureDateReal = this.stringToDate(timetable.checkpointDepartures?.at(i));
|
|
||||||
const arrivalDateScheduled = this.stringToDate(
|
|
||||||
timetable.checkpointArrivalsScheduled?.at(i)
|
|
||||||
);
|
|
||||||
const arrivalDateReal = this.stringToDate(timetable.checkpointArrivals?.at(i));
|
|
||||||
const arrivalHTML =
|
|
||||||
(arrivalDateReal &&
|
|
||||||
arrivalDateScheduled &&
|
|
||||||
arrivalDateReal?.getTime() != arrivalDateScheduled?.getTime()
|
|
||||||
? `<s class="text--grayed">${this.parseDateToTimeString(arrivalDateScheduled)}</s> `
|
|
||||||
: '') + this.parseDateToTimeString(arrivalDateReal || arrivalDateScheduled);
|
|
||||||
const departureHTML =
|
|
||||||
(departureDateReal &&
|
|
||||||
departureDateScheduled &&
|
|
||||||
departureDateReal?.getTime() != departureDateScheduled?.getTime()
|
|
||||||
? `<s class="text--grayed">${this.parseDateToTimeString(departureDateScheduled)}</s> `
|
|
||||||
: '') + this.parseDateToTimeString(departureDateReal || departureDateScheduled);
|
|
||||||
let html = `${arrivalHTML}${departureHTML ? ` / ${departureHTML}` : ''}`;
|
|
||||||
if (html) html = ` (${html})`;
|
|
||||||
return { stopName, html, confirmed };
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.stop-list {
|
|
||||||
word-wrap: break-word;
|
|
||||||
gap: 0.25em;
|
|
||||||
font-size: 0.95em;
|
|
||||||
|
|
||||||
color: #adadad;
|
|
||||||
|
|
||||||
&-item[data-confirmed='true'] {
|
|
||||||
color: lightgreen;
|
|
||||||
|
|
||||||
.stop-name {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -6,7 +6,9 @@ export namespace Journal {
|
|||||||
| 'search-train'
|
| 'search-train'
|
||||||
| 'search-date'
|
| 'search-date'
|
||||||
| 'search-dispatcher'
|
| 'search-dispatcher'
|
||||||
| 'search-issuedFrom';
|
| 'search-issuedFrom'
|
||||||
|
| 'search-terminatingAt'
|
||||||
|
| 'search-via';
|
||||||
|
|
||||||
export type TimetableSearchType = {
|
export type TimetableSearchType = {
|
||||||
[key in TimetableSearchKey]: string;
|
[key in TimetableSearchKey]: string;
|
||||||
@@ -17,7 +19,7 @@ export namespace Journal {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type TimetableSorterKey = 'timetableId' | 'beginDate' | 'distance' | 'total-stops';
|
export type TimetableSorterKey = 'timetableId' | 'beginDate' | 'distance' | 'total-stops';
|
||||||
export type DispatcherSorterKey = 'timestampFrom' | 'duration';
|
export type DispatcherSorterKey = 'timestampFrom' | 'currentDuration';
|
||||||
|
|
||||||
export interface DispatcherSorter {
|
export interface DispatcherSorter {
|
||||||
id: DispatcherSorterKey;
|
id: DispatcherSorterKey;
|
||||||
@@ -37,6 +39,8 @@ export namespace Journal {
|
|||||||
ALL_SPECIALS = 'all-specials',
|
ALL_SPECIALS = 'all-specials',
|
||||||
TWR = 'twr',
|
TWR = 'twr',
|
||||||
SKR = 'skr',
|
SKR = 'skr',
|
||||||
|
PN = 'pn',
|
||||||
|
TN = 'tn',
|
||||||
TWR_SKR = 'twr-skr'
|
TWR_SKR = 'twr-skr'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,4 +68,16 @@ export namespace Journal {
|
|||||||
iconName: string;
|
iconName: string;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TimetableStopDetails {
|
||||||
|
stopName: string;
|
||||||
|
arrivalTimestamp: number;
|
||||||
|
scheduledArrivalTimestamp: number;
|
||||||
|
departureTimestamp: number;
|
||||||
|
scheduledDepartureTimestamp: number;
|
||||||
|
stopTime: number;
|
||||||
|
stopType: string;
|
||||||
|
isConfirmed: boolean;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="scenery-table-section">
|
<div class="scenery-dispatchers-history">
|
||||||
<Loading v-if="dataStatus != DataStatus.Loaded && historyList.length == 0" />
|
<div class="history-wrapper">
|
||||||
|
<Loading v-if="dataStatus != DataStatus.Loaded && historyList.length == 0" />
|
||||||
|
|
||||||
<div class="no-history" v-else-if="historyList.length == 0">
|
<div v-else-if="historyList.length == 0" class="no-history">
|
||||||
{{ $t('scenery.history-list-empty') }}
|
{{ $t('scenery.history-list-empty') }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<table class="scenery-history-table" v-else>
|
<div v-else class="journal-list">
|
||||||
<thead>
|
<div v-for="historyItem in historyList" :key="historyItem.id">
|
||||||
<th>{{ $t('scenery.dispatchers-history-hash') }}</th>
|
<span>
|
||||||
<th>{{ $t('scenery.dispatchers-history-dispatcher') }}</th>
|
<span class="text--grayed" style="margin-right: 10px">
|
||||||
<th>{{ $t('scenery.dispatchers-history-level') }}</th>
|
#{{ historyItem.stationHash }}
|
||||||
<th>{{ $t('scenery.dispatchers-history-rate') }}</th>
|
</span>
|
||||||
<th>{{ $t('scenery.dispatchers-history-date') }}</th>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tbody>
|
|
||||||
<tr v-for="historyItem in historyList" :key="historyItem.id">
|
|
||||||
<td>#{{ historyItem.stationHash }}</td>
|
|
||||||
<td>
|
|
||||||
<router-link
|
|
||||||
:to="`/journal/dispatchers?search-dispatcher=${historyItem.dispatcherName}`"
|
|
||||||
>
|
|
||||||
<b>{{ historyItem.dispatcherName }}</b>
|
|
||||||
</router-link>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<b
|
<b
|
||||||
v-if="historyItem.dispatcherLevel !== null"
|
v-if="historyItem.dispatcherLevel !== null"
|
||||||
class="level-badge dispatcher"
|
class="level-badge dispatcher"
|
||||||
@@ -35,37 +22,52 @@
|
|||||||
>
|
>
|
||||||
{{ historyItem.dispatcherLevel >= 2 ? historyItem.dispatcherLevel : 'L' }}
|
{{ historyItem.dispatcherLevel >= 2 ? historyItem.dispatcherLevel : 'L' }}
|
||||||
</b>
|
</b>
|
||||||
|
<b style="margin-left: 5px">
|
||||||
|
<router-link
|
||||||
|
:to="`/journal/dispatchers?search-dispatcher=${historyItem.dispatcherName}`"
|
||||||
|
>
|
||||||
|
{{ historyItem.dispatcherName }}
|
||||||
|
</router-link>
|
||||||
|
</b>
|
||||||
|
|
||||||
<b v-else>?</b>
|
<div>
|
||||||
</td>
|
<span>
|
||||||
<td class="text--primary">
|
{{ $t('scenery.dispatcher-rate') }}
|
||||||
<b>{{ historyItem.dispatcherRate }}</b>
|
<b class="text--primary"> {{ historyItem.dispatcherRate }}</b>
|
||||||
</td>
|
</span>
|
||||||
<td style="min-width: 300px">
|
|
|
||||||
<div v-if="historyItem.timestampTo">
|
<span>
|
||||||
|
{{ $t('scenery.dispatcher-status-changes') }}
|
||||||
|
<b class="text--primary">{{ historyItem.statusHistory.length }}</b>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span>
|
||||||
|
<span v-if="historyItem.timestampTo">
|
||||||
<b>{{ $d(historyItem.timestampFrom) }}</b>
|
<b>{{ $d(historyItem.timestampFrom) }}</b>
|
||||||
|
|
||||||
{{ timestampToString(historyItem.timestampFrom) }}
|
{{ timestampToString(historyItem.timestampFrom) }}
|
||||||
- {{ timestampToString(historyItem.timestampTo) }} ({{
|
- {{ timestampToString(historyItem.timestampTo) }} ({{
|
||||||
calculateDuration(historyItem.currentDuration)
|
calculateDuration(historyItem.currentDuration)
|
||||||
}})
|
}})
|
||||||
</div>
|
</span>
|
||||||
|
|
||||||
<div class="dispatcher-online" v-else>
|
<span class="dispatcher-online" v-else>
|
||||||
{{ $t('journal.online-since') }}
|
{{ $t('journal.online-since') }}
|
||||||
<b>{{ timestampToString(historyItem.timestampFrom) }}</b>
|
<b>{{ timestampToString(historyItem.timestampFrom) }}</b>
|
||||||
({{ calculateDuration(historyItem.currentDuration) }})
|
({{ calculateDuration(historyItem.currentDuration) }})
|
||||||
</div>
|
</span>
|
||||||
</td>
|
</span>
|
||||||
</tr>
|
</div>
|
||||||
</tbody>
|
</div>
|
||||||
</table>
|
</div>
|
||||||
</section>
|
|
||||||
|
|
||||||
<div class="bottom-info">
|
<div class="bottom-info">
|
||||||
<button class="btn btn--option" v-if="historyList.length > 0" @click="navigateToHistory">
|
<button class="btn btn--option" v-if="historyList.length > 0" @click="navigateToHistory">
|
||||||
{{ $t('scenery.bottom-info') }}
|
{{ $t('scenery.bottom-info') }}
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -149,8 +151,43 @@ export default defineComponent({
|
|||||||
@import '../../styles/responsive.scss';
|
@import '../../styles/responsive.scss';
|
||||||
@import '../../styles/sceneryViewTables.scss';
|
@import '../../styles/sceneryViewTables.scss';
|
||||||
|
|
||||||
|
.scenery-dispatchers-history {
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
display: grid;
|
||||||
|
gap: 0.5em;
|
||||||
|
grid-template-rows: auto 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-wrapper {
|
||||||
|
position: relative;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.journal-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5em;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.journal-list > div {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
gap: 0.5em;
|
||||||
|
padding: 0.5em;
|
||||||
|
background-color: #2b2b2b;
|
||||||
|
line-height: 1.75em;
|
||||||
|
}
|
||||||
|
|
||||||
.level-badge {
|
.level-badge {
|
||||||
margin: 0 auto;
|
text-align: center;
|
||||||
|
display: inline-block;
|
||||||
|
line-height: 1.6em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dispatcher-online {
|
.dispatcher-online {
|
||||||
@@ -158,13 +195,10 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
@include smallScreen {
|
@include smallScreen {
|
||||||
.history-list {
|
.journal-list > div {
|
||||||
font-size: 1.1em;
|
|
||||||
}
|
|
||||||
.list-item {
|
|
||||||
align-items: center;
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
../../store/storeTypes
|
|
||||||
|
|||||||
@@ -124,11 +124,6 @@ h3.section-header {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
|
|
||||||
img {
|
|
||||||
width: 1.1em;
|
|
||||||
margin-left: 0.5em;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-lists {
|
.info-lists {
|
||||||
|
|||||||
@@ -15,14 +15,13 @@
|
|||||||
<li
|
<li
|
||||||
v-for="{ train, status } in stationTrains"
|
v-for="{ train, status } in stationTrains"
|
||||||
class="badge user"
|
class="badge user"
|
||||||
tabindex="0"
|
|
||||||
:key="train.id"
|
:key="train.id"
|
||||||
:data-status="status"
|
:data-status="status"
|
||||||
@click.prevent="selectModalTrain(train.id, $event.currentTarget)"
|
|
||||||
@keydown.enter="selectModalTrain(train.id, $event.currentTarget)"
|
|
||||||
>
|
>
|
||||||
<span class="user_train">{{ train.trainNo }}</span>
|
<router-link :to="train.driverRouteLocation" class="a-block">
|
||||||
<span class="user_name">{{ train.driverName }}</span>
|
<span class="user_train">{{ train.trainNo }}</span>
|
||||||
|
<span class="user_name">{{ train.driverName }}</span>
|
||||||
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
</transition-group>
|
</transition-group>
|
||||||
</section>
|
</section>
|
||||||
@@ -30,14 +29,13 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { PropType, defineComponent } from 'vue';
|
import { PropType, defineComponent } from 'vue';
|
||||||
import modalTrainMixin from '../../../mixins/modalTrainMixin';
|
|
||||||
import routerMixin from '../../../mixins/routerMixin';
|
import routerMixin from '../../../mixins/routerMixin';
|
||||||
import { ActiveScenery, Station, StopStatus } from '../../../typings/common';
|
import { ActiveScenery, Station } from '../../../typings/common';
|
||||||
import { getTrainStopStatus } from '../utils';
|
import { getTrainStopStatus } from '../utils';
|
||||||
import { useMainStore } from '../../../store/mainStore';
|
import { useMainStore } from '../../../store/mainStore';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
mixins: [routerMixin, modalTrainMixin],
|
mixins: [routerMixin],
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
onlineScenery: {
|
onlineScenery: {
|
||||||
@@ -63,7 +61,9 @@ export default defineComponent({
|
|||||||
|
|
||||||
return this.onlineScenery.stationTrains.map((train) => {
|
return this.onlineScenery.stationTrains.map((train) => {
|
||||||
const stop = train.timetableData?.followingStops.find(
|
const stop = train.timetableData?.followingStops.find(
|
||||||
(stop) => stop.stopNameRAW.toLowerCase() == name.toLowerCase()
|
(stop) =>
|
||||||
|
stop.stopNameRAW.toLowerCase() == name.toLowerCase() ||
|
||||||
|
this.station?.generalInfo?.checkpoints.includes(stop.stopNameRAW)
|
||||||
);
|
);
|
||||||
|
|
||||||
const status = stop
|
const status = stop
|
||||||
@@ -97,38 +97,27 @@ ul {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.user {
|
.user {
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&_train {
|
|
||||||
color: black;
|
|
||||||
background-color: $no-timetable;
|
|
||||||
|
|
||||||
transition: background-color 200ms;
|
|
||||||
-ms-transition: background-color 200ms;
|
|
||||||
-webkit-transition: background-color 200ms;
|
|
||||||
}
|
|
||||||
|
|
||||||
&[data-status='no-timetable'] .user_train {
|
&[data-status='no-timetable'] .user_train {
|
||||||
background-color: $no-timetable;
|
background-color: $no-timetable;
|
||||||
}
|
}
|
||||||
|
|
||||||
&[data-status='departed'] > &_train {
|
&[data-status='departed'] .user_train {
|
||||||
background-color: $departed;
|
background-color: $departed;
|
||||||
}
|
}
|
||||||
|
|
||||||
&[data-status='stopped'] > &_train {
|
&[data-status='stopped'] .user_train {
|
||||||
background-color: $stopped;
|
background-color: $stopped;
|
||||||
}
|
}
|
||||||
|
|
||||||
&[data-status='online'] > &_train {
|
&[data-status='online'] .user_train {
|
||||||
background-color: $online;
|
background-color: $online;
|
||||||
}
|
}
|
||||||
|
|
||||||
&[data-status='terminated'] > &_train {
|
&[data-status='terminated'] .user_train {
|
||||||
background-color: $terminated;
|
background-color: $terminated;
|
||||||
}
|
}
|
||||||
|
|
||||||
&[data-status='disconnected'] > &_train {
|
&[data-status='disconnected'] .user_train {
|
||||||
background-color: $disconnected;
|
background-color: $disconnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,6 +126,16 @@ ul {
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.user_train {
|
||||||
|
color: black;
|
||||||
|
background-color: $no-timetable;
|
||||||
|
|
||||||
|
transition: background-color 200ms;
|
||||||
|
-ms-transition: background-color 200ms;
|
||||||
|
-webkit-transition: background-color 200ms;
|
||||||
|
}
|
||||||
|
|
||||||
.users-anim {
|
.users-anim {
|
||||||
&-move,
|
&-move,
|
||||||
&-enter-active,
|
&-enter-active,
|
||||||
|
|||||||
@@ -14,6 +14,10 @@
|
|||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="header_links" v-if="station">
|
<span class="header_links" v-if="station">
|
||||||
|
<a :href="pragotronHref" target="_blank" :title="$t('scenery.pragotron-link')">
|
||||||
|
<img src="/images/icon-pragotron.svg" alt="icon-pragotron" />
|
||||||
|
</a>
|
||||||
|
|
||||||
<a :href="tabliceZbiorczeHref" target="_blank" :title="$t('scenery.tablice-link')">
|
<a :href="tabliceZbiorczeHref" target="_blank" :title="$t('scenery.tablice-link')">
|
||||||
<img src="/images/icon-tablice.ico" alt="icon-tablice" />
|
<img src="/images/icon-tablice.ico" alt="icon-tablice" />
|
||||||
</a>
|
</a>
|
||||||
@@ -21,26 +25,23 @@
|
|||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div class="timetable-checkpoints" v-if="station?.generalInfo?.checkpoints">
|
<div class="timetable-checkpoints" v-if="station?.generalInfo?.checkpoints">
|
||||||
<span v-for="(cp, i) in station.generalInfo.checkpoints" :key="i">
|
<template v-for="(ch, i) in station.generalInfo.checkpoints" :key="i">
|
||||||
{{ (i > 0 && '•') || '' }}
|
<template v-if="i > 0">•</template>
|
||||||
|
<router-link
|
||||||
<button
|
class="checkpoint-item"
|
||||||
:key="cp"
|
:class="{ current: chosenCheckpoint === ch }"
|
||||||
class="checkpoint_item"
|
:to="`/scenery?station=${station.name}&checkpoint=${ch}`"
|
||||||
:class="{ current: chosenCheckpoint === cp }"
|
>{{ ch }}</router-link
|
||||||
@click="setCheckpoint(cp)"
|
|
||||||
>
|
>
|
||||||
{{ cp }}
|
</template>
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="timetable-list">
|
<div class="timetable-list">
|
||||||
<transition-group name="list-anim">
|
<transition-group name="list-anim">
|
||||||
<div
|
<div
|
||||||
style="padding-bottom: 5em"
|
|
||||||
v-if="apiStore.dataStatuses.connection == 0 && sceneryTimetables.length == 0"
|
v-if="apiStore.dataStatuses.connection == 0 && sceneryTimetables.length == 0"
|
||||||
|
style="padding-bottom: 5em"
|
||||||
key="list-loading"
|
key="list-loading"
|
||||||
>
|
>
|
||||||
<Loading />
|
<Loading />
|
||||||
@@ -62,29 +63,36 @@
|
|||||||
{{ $t('scenery.no-timetables') }}
|
{{ $t('scenery.no-timetables') }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<router-link
|
||||||
class="timetable-item"
|
class="timetable-item a-block"
|
||||||
v-else
|
v-else
|
||||||
v-for="row in sceneryTimetables"
|
v-for="(row, i) in sceneryTimetables"
|
||||||
:key="row.train.id + row.checkpointStop.arrivalTimestamp"
|
:key="row.train.id + i"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@click.prevent.stop="selectModalTrain(row.train.id, $event.currentTarget)"
|
:to="row.train.driverRouteLocation"
|
||||||
@keydown.enter.prevent="selectModalTrain(row.train.id, $event.currentTarget)"
|
|
||||||
>
|
>
|
||||||
<span class="timetable-general">
|
<span class="timetable-general">
|
||||||
<span class="general-info">
|
<span class="general-info">
|
||||||
<span class="info-number">
|
<div class="info-train">
|
||||||
<strong>{{ row.train.timetableData!.category }}</strong>
|
<b
|
||||||
{{ row.train.trainNo }}
|
data-tooltip-type="BaseTooltip"
|
||||||
|
:data-tooltip-content="getCategoryExplanation(row.train.timetableData!.category)"
|
||||||
<span v-if="row.checkpointStop.comments" :title="row.checkpointStop.comments">
|
class="text--primary tooltip-help"
|
||||||
|
>
|
||||||
|
{{ row.train.timetableData!.category }}
|
||||||
|
</b>
|
||||||
|
<span> </span>
|
||||||
|
<b>{{ row.train.trainNo }}</b>
|
||||||
|
<span> • </span>
|
||||||
|
<span>{{ row.train.driverName }}</span>
|
||||||
|
<span
|
||||||
|
v-if="row.checkpointStop.comments"
|
||||||
|
data-tooltip-type="BaseTooltip"
|
||||||
|
:data-tooltip-content="row.checkpointStop.comments"
|
||||||
|
>
|
||||||
<img src="/images/icon-warning.svg" />
|
<img src="/images/icon-warning.svg" />
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</div>
|
||||||
|
|
|
||||||
<span>
|
|
||||||
{{ row.train.driverName }}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<div class="info-route">
|
<div class="info-route">
|
||||||
<strong>{{ row.train.timetableData!.route.replace('|', ' - ') }}</strong>
|
<strong>{{ row.train.timetableData!.route.replace('|', ' - ') }}</strong>
|
||||||
@@ -160,7 +168,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</router-link>
|
||||||
</transition-group>
|
</transition-group>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -173,12 +181,12 @@ import { useRoute } from 'vue-router';
|
|||||||
import Loading from '../Global/Loading.vue';
|
import Loading from '../Global/Loading.vue';
|
||||||
import dateMixin from '../../mixins/dateMixin';
|
import dateMixin from '../../mixins/dateMixin';
|
||||||
import routerMixin from '../../mixins/routerMixin';
|
import routerMixin from '../../mixins/routerMixin';
|
||||||
|
import trainCategoryMixin from '../../mixins/trainCategoryMixin';
|
||||||
import { useMainStore } from '../../store/mainStore';
|
import { useMainStore } from '../../store/mainStore';
|
||||||
import modalTrainMixin from '../../mixins/modalTrainMixin';
|
|
||||||
import ScheduledTrainStatus from './ScheduledTrainStatus.vue';
|
|
||||||
import { useApiStore } from '../../store/apiStore';
|
import { useApiStore } from '../../store/apiStore';
|
||||||
import { ActiveScenery, Station } from '../../typings/common';
|
import ScheduledTrainStatus from './ScheduledTrainStatus.vue';
|
||||||
import { SceneryTimetableRow } from './typings';
|
import { SceneryTimetableRow } from './typings';
|
||||||
|
import { ActiveScenery, Station } from '../../typings/common';
|
||||||
import { getTrainStopStatus, stopStatusPriority } from './utils';
|
import { getTrainStopStatus, stopStatusPriority } from './utils';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
@@ -186,7 +194,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
components: { Loading, ScheduledTrainStatus },
|
components: { Loading, ScheduledTrainStatus },
|
||||||
|
|
||||||
mixins: [dateMixin, routerMixin, modalTrainMixin],
|
mixins: [dateMixin, routerMixin, trainCategoryMixin],
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
station: {
|
station: {
|
||||||
@@ -205,6 +213,12 @@ export default defineComponent({
|
|||||||
this.loadSelectedOption();
|
this.loadSelectedOption();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
currentURL() {
|
||||||
|
this.loadSelectedOption();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const currentURL = computed(() => `${location.origin}${route.fullPath}`);
|
const currentURL = computed(() => `${location.origin}${route.fullPath}`);
|
||||||
@@ -213,7 +227,10 @@ export default defineComponent({
|
|||||||
const mainStore = useMainStore();
|
const mainStore = useMainStore();
|
||||||
|
|
||||||
const chosenCheckpoint = ref(
|
const chosenCheckpoint = ref(
|
||||||
props.station?.generalInfo?.checkpoints[0] ?? props.station?.name ?? ''
|
props.station?.generalInfo?.checkpoints[0] ??
|
||||||
|
props.station?.name ??
|
||||||
|
route.query['station']?.toString() ??
|
||||||
|
''
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -232,15 +249,22 @@ export default defineComponent({
|
|||||||
return url;
|
return url;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
pragotronHref() {
|
||||||
|
let url = `https://pragotron-td2.web.app/board?name=${this.station!.name}®ion=${this.mainStore.region.id}`;
|
||||||
|
if (this.chosenCheckpoint) url += `&checkpoint=${this.chosenCheckpoint}`;
|
||||||
|
|
||||||
|
return url;
|
||||||
|
},
|
||||||
|
|
||||||
sceneryTimetables(): SceneryTimetableRow[] {
|
sceneryTimetables(): SceneryTimetableRow[] {
|
||||||
if (!this.station) return [];
|
|
||||||
if (!this.onlineScenery) return [];
|
if (!this.onlineScenery) return [];
|
||||||
|
|
||||||
console.log(this.onlineScenery.scheduledTrains, this.chosenCheckpoint);
|
const sceneryName = this.$route.query['station']?.toString().replace(/_/g, ' ') ?? '';
|
||||||
|
|
||||||
return this.onlineScenery.scheduledTrains
|
return this.onlineScenery.scheduledTrains
|
||||||
.filter(
|
.filter(
|
||||||
(ct) =>
|
(ct) =>
|
||||||
|
// ct.timetablePathElement.stationName == sceneryName &&
|
||||||
ct.train.region == this.mainStore.region.id &&
|
ct.train.region == this.mainStore.region.id &&
|
||||||
this.chosenCheckpoint &&
|
this.chosenCheckpoint &&
|
||||||
ct.checkpointStop.stopNameRAW.toLowerCase() == this.chosenCheckpoint.toLowerCase()
|
ct.checkpointStop.stopNameRAW.toLowerCase() == this.chosenCheckpoint.toLowerCase()
|
||||||
@@ -249,75 +273,18 @@ export default defineComponent({
|
|||||||
const trainStopStatus = getTrainStopStatus(
|
const trainStopStatus = getTrainStopStatus(
|
||||||
ct.checkpointStop,
|
ct.checkpointStop,
|
||||||
ct.train.currentStationName,
|
ct.train.currentStationName,
|
||||||
this.station!.name
|
sceneryName
|
||||||
);
|
);
|
||||||
|
|
||||||
const trainStopIndex =
|
|
||||||
ct.train.timetableData?.followingStops.findIndex(
|
|
||||||
(stop) => stop.stopName == ct.checkpointStop.stopName
|
|
||||||
) ?? -1;
|
|
||||||
|
|
||||||
let prevStationName = '',
|
|
||||||
nextStationName = '';
|
|
||||||
|
|
||||||
let departureLine: string | null = null;
|
|
||||||
let arrivingLine: string | null = null;
|
|
||||||
|
|
||||||
let prevDepartureLine: string | null = null,
|
|
||||||
nextArrivalLine: string | null = null;
|
|
||||||
|
|
||||||
if (trainStopIndex > -1 && ct.train.timetableData?.followingStops !== undefined) {
|
|
||||||
for (let i = trainStopIndex; i >= 0; i--) {
|
|
||||||
const stop = ct.train.timetableData.followingStops[i];
|
|
||||||
|
|
||||||
if (
|
|
||||||
/strong|podg\.|pe\./g.test(stop.stopName) &&
|
|
||||||
!prevStationName &&
|
|
||||||
i <= trainStopIndex - 1
|
|
||||||
)
|
|
||||||
prevStationName = stop.stopNameRAW.replace(/,.*/g, '');
|
|
||||||
|
|
||||||
if (
|
|
||||||
stop.arrivalLine != null &&
|
|
||||||
!arrivingLine &&
|
|
||||||
!/-|_|it|sbl/gi.test(stop.arrivalLine)
|
|
||||||
) {
|
|
||||||
arrivingLine = stop.arrivalLine;
|
|
||||||
prevDepartureLine =
|
|
||||||
ct.train.timetableData.followingStops[i - 1]?.departureLine || null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = trainStopIndex; i < ct.train.timetableData.followingStops.length; i++) {
|
|
||||||
const stop = ct.train.timetableData.followingStops[i];
|
|
||||||
|
|
||||||
if (
|
|
||||||
/strong|podg\.|pe\./g.test(stop.stopName) &&
|
|
||||||
!nextStationName &&
|
|
||||||
i > trainStopIndex
|
|
||||||
)
|
|
||||||
nextStationName = stop.stopNameRAW.replace(/,.*/g, '');
|
|
||||||
|
|
||||||
if (
|
|
||||||
stop.departureLine &&
|
|
||||||
!departureLine &&
|
|
||||||
!/-|_|it|sbl/gi.test(stop.departureLine)
|
|
||||||
) {
|
|
||||||
departureLine = stop.departureLine;
|
|
||||||
nextArrivalLine = ct.train.timetableData.followingStops[i + 1]?.arrivalLine || null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
checkpointStop: ct.checkpointStop,
|
checkpointStop: ct.checkpointStop,
|
||||||
train: ct.train,
|
train: ct.train,
|
||||||
prevDepartureLine,
|
prevDepartureLine: ct.previousSceneryElement?.departureRouteExt ?? null,
|
||||||
nextArrivalLine,
|
nextArrivalLine: ct.nextSceneryElement?.arrivalRouteExt ?? null,
|
||||||
departureLine,
|
departureLine: ct.timetablePathElement.departureRouteExt ?? null,
|
||||||
arrivingLine,
|
arrivingLine: ct.timetablePathElement.arrivalRouteExt ?? null,
|
||||||
prevStationName,
|
prevStationName: ct.previousSceneryElement?.stationName ?? null,
|
||||||
nextStationName,
|
nextStationName: ct.nextSceneryElement?.stationName ?? null,
|
||||||
status: trainStopStatus
|
status: trainStopStatus
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
@@ -340,7 +307,19 @@ export default defineComponent({
|
|||||||
loadSelectedOption() {
|
loadSelectedOption() {
|
||||||
if (!this.station) return;
|
if (!this.station) return;
|
||||||
|
|
||||||
this.chosenCheckpoint = this.station.generalInfo?.checkpoints[0] ?? this.station.name;
|
if (!this.station.generalInfo) {
|
||||||
|
this.chosenCheckpoint = this.station.name;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryCheckpoint = this.$route.query['checkpoint']?.toString();
|
||||||
|
|
||||||
|
this.chosenCheckpoint =
|
||||||
|
this.station.generalInfo.checkpoints.find(
|
||||||
|
(ch) => ch.toLocaleLowerCase() === queryCheckpoint?.toLocaleLowerCase()
|
||||||
|
) ??
|
||||||
|
this.station.generalInfo.checkpoints[0] ??
|
||||||
|
this.station.name;
|
||||||
},
|
},
|
||||||
|
|
||||||
setCheckpoint(cp: string) {
|
setCheckpoint(cp: string) {
|
||||||
@@ -410,7 +389,6 @@ export default defineComponent({
|
|||||||
|
|
||||||
background: #353535;
|
background: #353535;
|
||||||
|
|
||||||
cursor: pointer;
|
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
|
|
||||||
&.empty {
|
&.empty {
|
||||||
@@ -440,30 +418,35 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.timetable-list {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timetable-checkpoints {
|
.timetable-checkpoints {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
gap: 0.5em;
|
||||||
|
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
font-size: 1.1em;
|
font-size: 1.1em;
|
||||||
|
|
||||||
margin-top: 0.5em;
|
margin-top: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
button.checkpoint_item {
|
.checkpoint-item {
|
||||||
color: #aaa;
|
color: #aaa;
|
||||||
display: inline;
|
display: inline;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkpoint_item.current {
|
&.current {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $accentCol;
|
color: $accentCol;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.timetable-list {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
.general-info {
|
.general-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@@ -477,7 +460,9 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: 1.1em;
|
height: 0.9em;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin: 0 0.25em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,69 +1,97 @@
|
|||||||
<template>
|
<template>
|
||||||
<!-- WIP -->
|
<div class="scenery-timetables-history">
|
||||||
<!-- <div class="top-filters">
|
<div class="history-modes">
|
||||||
<button class="btn btn--option">ROZPOCZYNA BIEG</button>
|
<button
|
||||||
|
class="btn btn--option"
|
||||||
<button class="btn btn--option">PRZEZ</button>
|
v-for="mode in historyModeList"
|
||||||
|
:key="mode"
|
||||||
<button class="btn btn--option">KOŃCZY BIEG</button>
|
:class="{ checked: checkedHistoryMode == mode }"
|
||||||
</div> -->
|
@click="checkHistoryMode(mode)"
|
||||||
|
>
|
||||||
<section class="scenery-table-section">
|
{{ $t(`scenery.timetable-${mode}`) }}
|
||||||
<Loading v-if="dataStatus != DataStatus.Loaded" />
|
</button>
|
||||||
|
|
||||||
<div class="no-history" v-else-if="historyList.length == 0">
|
|
||||||
{{ $t('scenery.history-list-empty') }}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<table class="scenery-history-table" v-else>
|
<div class="history-wrapper">
|
||||||
<thead>
|
<Loading v-if="dataStatus != DataStatus.Loaded" />
|
||||||
<th>{{ $t('scenery.timetables-history-id') }}</th>
|
|
||||||
<th>{{ $t('scenery.timetables-history-number') }}</th>
|
|
||||||
<th>{{ $t('scenery.timetables-history-route') }}</th>
|
|
||||||
<th>{{ $t('scenery.timetables-history-driver') }}</th>
|
|
||||||
<th>{{ $t('scenery.timetables-history-author') }}</th>
|
|
||||||
<th>{{ $t('scenery.timetables-history-date') }}</th>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tbody>
|
<div v-else-if="historyList.length == 0" class="no-history">
|
||||||
<tr v-for="historyItem in historyList" :key="historyItem.id">
|
{{ $t('scenery.history-list-empty') }}
|
||||||
<td>
|
</div>
|
||||||
<router-link :to="`/journal/timetables?search-train=%23${historyItem.id}`">
|
|
||||||
#{{ historyItem.id }}
|
|
||||||
</router-link>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<b class="text--primary">{{ historyItem.trainCategoryCode }}</b> <br />
|
|
||||||
{{ historyItem.trainNo }}
|
|
||||||
</td>
|
|
||||||
<td>{{ historyItem.route.replace('|', ' -> ') }}</td>
|
|
||||||
<td>
|
|
||||||
<router-link :to="`/journal/timetables?search-driver=${historyItem.driverName}`">
|
|
||||||
{{ historyItem.driverName }}
|
|
||||||
</router-link>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td>
|
<div v-else class="journal-list">
|
||||||
<router-link
|
<div v-for="timetableHistory in historyList" :key="timetableHistory.id">
|
||||||
v-if="historyItem.authorName"
|
<span>
|
||||||
:to="`/journal/timetables?search-dispatcher=${historyItem.authorName}`"
|
<div>
|
||||||
>{{ historyItem.authorName }}
|
<span
|
||||||
</router-link>
|
class="timetable-status-indicator"
|
||||||
<i v-else>{{ $t('scenery.timetable-author-unknown') }}</i>
|
:data-terminated="timetableHistory.terminated"
|
||||||
</td>
|
:data-fulfilled="timetableHistory.fulfilled"
|
||||||
<td>
|
>
|
||||||
<b>{{ localeDay(historyItem.beginDate, $i18n.locale) }}</b>
|
⦿
|
||||||
{{ localeTime(historyItem.beginDate, $i18n.locale) }}
|
</span>
|
||||||
</td>
|
#{{ timetableHistory.id }} |
|
||||||
</tr>
|
<b class="text--primary">{{ timetableHistory.trainCategoryCode }}</b>
|
||||||
</tbody>
|
{{ timetableHistory.trainNo }}
|
||||||
</table>
|
{{ timetableHistory.route.replace('|', ' ⇒ ') }}
|
||||||
</section>
|
</div>
|
||||||
|
|
||||||
<div class="bottom-info">
|
<div class="text--grayed">
|
||||||
<button class="btn btn--option" v-if="historyList.length > 0" @click="navigateToHistory()">
|
<span>
|
||||||
{{ $t('scenery.bottom-info') }}
|
{{ $t('scenery.timetable-issued-date') }}
|
||||||
</button>
|
<b>
|
||||||
|
{{
|
||||||
|
localeDateTime(
|
||||||
|
timetableHistory.createdAt > timetableHistory.beginDate
|
||||||
|
? timetableHistory.beginDate
|
||||||
|
: timetableHistory.createdAt,
|
||||||
|
$i18n.locale
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</b></span
|
||||||
|
>
|
||||||
|
<span v-if="timetableHistory.authorName">
|
||||||
|
{{ $t('scenery.timetable-issued-by') }}
|
||||||
|
<b>
|
||||||
|
<router-link
|
||||||
|
:to="`/journal/timetables?search-dispatcher=${timetableHistory.authorName}`"
|
||||||
|
>
|
||||||
|
{{ timetableHistory.authorName }}
|
||||||
|
</router-link>
|
||||||
|
</b>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span>
|
||||||
|
{{ $t('scenery.timetable-issued-for') }}
|
||||||
|
<b>
|
||||||
|
<router-link
|
||||||
|
:to="`/journal/timetables?search-driver=${timetableHistory.driverName}`"
|
||||||
|
>
|
||||||
|
{{ timetableHistory.driverName }}
|
||||||
|
</router-link>
|
||||||
|
</b>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<button
|
||||||
|
@click="
|
||||||
|
navigateTo(`/journal/timetables`, {
|
||||||
|
'search-train': `#${timetableHistory.id}`
|
||||||
|
})
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<img src="/images/icon-back.svg" alt="icon navigate to timetable" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bottom-info">
|
||||||
|
<button class="btn btn--option" v-if="historyList.length > 0" @click="navigateToHistory()">
|
||||||
|
{{ $t('scenery.bottom-info') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -75,10 +103,15 @@ import Loading from '../Global/Loading.vue';
|
|||||||
import { API } from '../../typings/api';
|
import { API } from '../../typings/api';
|
||||||
import { ActiveScenery, Station, Status } from '../../typings/common';
|
import { ActiveScenery, Station, Status } from '../../typings/common';
|
||||||
import { useApiStore } from '../../store/apiStore';
|
import { useApiStore } from '../../store/apiStore';
|
||||||
|
import routerMixin from '../../mixins/routerMixin';
|
||||||
|
import { useMainStore } from '../../store/mainStore';
|
||||||
|
|
||||||
|
const historyModeList = ['via', 'issuedFrom', 'terminatingAt'] as const;
|
||||||
|
type HistoryMode = (typeof historyModeList)[number];
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'SceneryTimetablesHistory',
|
name: 'SceneryTimetablesHistory',
|
||||||
mixins: [dateMixin],
|
mixins: [dateMixin, routerMixin],
|
||||||
props: {
|
props: {
|
||||||
station: {
|
station: {
|
||||||
type: Object as PropType<Station>
|
type: Object as PropType<Station>
|
||||||
@@ -91,9 +124,14 @@ export default defineComponent({
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
historyList: [] as API.TimetableHistory.Response,
|
historyList: [] as API.TimetableHistory.Response,
|
||||||
|
historyModeList,
|
||||||
|
|
||||||
apiStore: useApiStore(),
|
apiStore: useApiStore(),
|
||||||
|
mainStore: useMainStore(),
|
||||||
dataStatus: Status.Data.Loading,
|
dataStatus: Status.Data.Loading,
|
||||||
DataStatus: Status.Data
|
DataStatus: Status.Data,
|
||||||
|
|
||||||
|
checkedHistoryMode: 'via' as HistoryMode
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -103,17 +141,22 @@ export default defineComponent({
|
|||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
async fetchAPIData() {
|
async fetchAPIData() {
|
||||||
if (!this.station && !this.onlineScenery) {
|
const stationName = this.$route.query['station'];
|
||||||
|
|
||||||
|
if (!stationName) {
|
||||||
|
this.historyList = [];
|
||||||
this.dataStatus = Status.Data.Loaded;
|
this.dataStatus = Status.Data.Loaded;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const requestFilters: Record<string, any> = {};
|
||||||
|
requestFilters[this.checkedHistoryMode] = stationName.toString();
|
||||||
|
requestFilters.countLimit = 30;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response: API.TimetableHistory.Response = await (
|
const response: API.TimetableHistory.Response = await (
|
||||||
await this.apiStore.client!.get('api/getTimetables', {
|
await this.apiStore.client!.get('api/getTimetables', {
|
||||||
params: {
|
params: requestFilters
|
||||||
issuedFrom: this.station?.name || this.onlineScenery?.name
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
).data;
|
).data;
|
||||||
|
|
||||||
@@ -125,11 +168,17 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
checkHistoryMode(mode: HistoryMode) {
|
||||||
|
this.checkedHistoryMode = mode;
|
||||||
|
this.dataStatus = Status.Data.Loading;
|
||||||
|
this.fetchAPIData();
|
||||||
|
},
|
||||||
|
|
||||||
navigateToHistory() {
|
navigateToHistory() {
|
||||||
this.$router.push({
|
this.$router.push({
|
||||||
path: '/journal/timetables',
|
path: '/journal/timetables',
|
||||||
query: {
|
query: {
|
||||||
'search-issuedFrom': this.station?.name || this.onlineScenery?.name
|
[`search-${this.checkedHistoryMode}`]: this.station?.name || this.onlineScenery?.name
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -142,13 +191,66 @@ export default defineComponent({
|
|||||||
@import '../../styles/responsive.scss';
|
@import '../../styles/responsive.scss';
|
||||||
@import '../../styles/sceneryViewTables.scss';
|
@import '../../styles/sceneryViewTables.scss';
|
||||||
|
|
||||||
.top-filters {
|
.scenery-timetables-history {
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
display: grid;
|
||||||
|
gap: 1em;
|
||||||
|
grid-template-rows: auto 1fr 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-wrapper {
|
||||||
|
position: relative;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-modes {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
|
padding: 0.25em;
|
||||||
|
|
||||||
button {
|
button {
|
||||||
padding: 0.5em;
|
padding: 0.35em;
|
||||||
|
min-width: 120px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.journal-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5em;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.journal-list > div {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.5em;
|
||||||
|
background-color: #2b2b2b;
|
||||||
|
line-height: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.journal-list > div > button > img {
|
||||||
|
width: 2em;
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.timetable-status-indicator {
|
||||||
|
&[data-fulfilled='true'] {
|
||||||
|
color: lightgreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-terminated='false'] {
|
||||||
|
color: lightblue;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-terminated='true'][data-fulfilled='false'] {
|
||||||
|
color: lightcoral;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -6,38 +6,45 @@
|
|||||||
<p>[F] {{ $t('options.filters') }}</p>
|
<p>[F] {{ $t('options.filters') }}</p>
|
||||||
<span class="active-indicator" v-if="changedFilters.length != 0"></span>
|
<span class="active-indicator" v-if="changedFilters.length != 0"></span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<label for="scenery-search">
|
|
||||||
<input
|
|
||||||
id="scenery-search"
|
|
||||||
list="sceneries"
|
|
||||||
:placeholder="$t('sceneries.scenery-search')"
|
|
||||||
@focus="preventKeyDown = true"
|
|
||||||
@blur="preventKeyDown = false"
|
|
||||||
v-model="chosenSearchScenery"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<datalist id="sceneries">
|
|
||||||
<option
|
|
||||||
v-for="scenery in sortedStationList"
|
|
||||||
:key="scenery.name"
|
|
||||||
:value="scenery.name"
|
|
||||||
></option>
|
|
||||||
</datalist>
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<transition name="card-anim">
|
<transition name="card-anim">
|
||||||
<div class="card" v-if="isVisible" tabindex="0" ref="cardRef" @keydown.r="resetFilters">
|
<div class="card" v-if="isVisible" ref="cardRef" @keydown.r="resetFilters">
|
||||||
<div class="card_content" @scroll="onScroll" ref="cardContentRef">
|
<div class="card_content" tabindex="0" @scroll="onScroll" ref="cardContentRef">
|
||||||
<div class="card_title flex">{{ $t('filters.title') }}</div>
|
<div class="card_title flex">{{ $t('filters.title') }}</div>
|
||||||
<p class="card_info" v-html="$t('filters.desc')"></p>
|
<p class="card_info" v-html="$t('filters.desc')"></p>
|
||||||
|
|
||||||
<div class="changed-filters" v-if="changedFilters.length > 0">
|
<div class="changed-filters" :data-active="changedFilters.length > 0">
|
||||||
{{ $t('filters.changed-filters-count') }} <b>{{ changedFilters.length }}</b>
|
<template v-if="changedFilters.length > 0">
|
||||||
|
{{ $t('filters.changed-filters-count') }} <b>{{ changedFilters.length }}</b>
|
||||||
|
</template>
|
||||||
|
<template v-else>{{ $t('filters.no-changed-filters') }}</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="changed-filters" v-else>{{ $t('filters.no-changed-filters') }}</div>
|
<section class="card_sceneries-search">
|
||||||
|
<h3 class="section-header">{{ $t('filters.sceneries-search') }}</h3>
|
||||||
|
|
||||||
|
<datalist id="sceneries">
|
||||||
|
<option
|
||||||
|
v-for="scenery in sortedStationList"
|
||||||
|
:key="scenery.name"
|
||||||
|
:value="scenery.name"
|
||||||
|
></option>
|
||||||
|
</datalist>
|
||||||
|
|
||||||
|
<form action="javascript:void(0);" @submit="handleSceneriesInput">
|
||||||
|
<input
|
||||||
|
v-model="chosenSearchScenery"
|
||||||
|
id="scenery-search"
|
||||||
|
list="sceneries"
|
||||||
|
:placeholder="$t('filters.sceneries-placeholder')"
|
||||||
|
@focus="preventKeyDown = true"
|
||||||
|
@blur="preventKeyDown = false"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<button class="btn--action">{{ $t('filters.search-button-title') }}</button>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section class="card_options">
|
<section class="card_options">
|
||||||
<div
|
<div
|
||||||
@@ -56,16 +63,15 @@
|
|||||||
<div class="section-filters">
|
<div class="section-filters">
|
||||||
<label
|
<label
|
||||||
v-for="filterKey in sectionFilters"
|
v-for="filterKey in sectionFilters"
|
||||||
@click="() => (filters[filterKey] = !filters[filterKey])"
|
|
||||||
@dblclick="setSingleSectionFilter(sectionKey, filterKey)"
|
@dblclick="setSingleSectionFilter(sectionKey, filterKey)"
|
||||||
:for="filterKey"
|
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
|
type="checkbox"
|
||||||
:checked="filters[filterKey]"
|
:checked="filters[filterKey]"
|
||||||
v-model="filters[filterKey]"
|
v-model="filters[filterKey]"
|
||||||
type="checkbox"
|
|
||||||
:class="sectionKey"
|
:class="sectionKey"
|
||||||
:name="filterKey"
|
:name="filterKey"
|
||||||
|
:id="filterKey"
|
||||||
/>
|
/>
|
||||||
<span>
|
<span>
|
||||||
{{ $t(`filters.${filterKey}`) }}
|
{{ $t(`filters.${filterKey}`) }}
|
||||||
@@ -110,7 +116,7 @@
|
|||||||
@blur="preventKeyDown = false"
|
@blur="preventKeyDown = false"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<button class="btn--action">{{ $t('filters.authors-button-title') }}</button>
|
<button class="btn--action">{{ $t('filters.search-button-title') }}</button>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -268,20 +274,11 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
chosenSearchScenery(value: string) {
|
|
||||||
const chosenStation = this.store.stationList.find(({ name }) => name == value);
|
|
||||||
|
|
||||||
if (chosenStation) {
|
|
||||||
this.$router.push(`/scenery?station=${chosenStation.name.replace(/ /g, '_')}`);
|
|
||||||
this.chosenSearchScenery = '';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
isVisible(value: boolean) {
|
isVisible(value: boolean) {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
if (value) {
|
if (value) {
|
||||||
(this.$refs['cardRef'] as HTMLDivElement).focus();
|
|
||||||
(this.$refs['cardContentRef'] as HTMLDivElement).scrollTop = this.scrollTop;
|
(this.$refs['cardContentRef'] as HTMLDivElement).scrollTop = this.scrollTop;
|
||||||
|
(this.$refs['cardContentRef'] as HTMLDivElement).focus();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -299,7 +296,18 @@ export default defineComponent({
|
|||||||
|
|
||||||
handleAuthorsInput() {
|
handleAuthorsInput() {
|
||||||
this.filters['authors'] = this.authors;
|
this.filters['authors'] = this.authors;
|
||||||
// if (this.saveOptions) StorageManager.setStringValue('authors', target.value);
|
},
|
||||||
|
|
||||||
|
handleSceneriesInput() {
|
||||||
|
const chosenStation = this.store.stationList.find(
|
||||||
|
({ name }) => name == this.chosenSearchScenery
|
||||||
|
);
|
||||||
|
|
||||||
|
if (chosenStation) {
|
||||||
|
this.$router.push(`/scenery?station=${chosenStation.name.replace(/ /g, '_')}`);
|
||||||
|
this.chosenSearchScenery = '';
|
||||||
|
this.isVisible = false;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
subHour() {
|
subHour() {
|
||||||
@@ -328,6 +336,8 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
resetFilters() {
|
resetFilters() {
|
||||||
|
if (this.preventKeyDown) return;
|
||||||
|
|
||||||
// Reset local model values
|
// Reset local model values
|
||||||
this.minimumHours = 0;
|
this.minimumHours = 0;
|
||||||
this.authors = '';
|
this.authors = '';
|
||||||
@@ -352,7 +362,8 @@ export default defineComponent({
|
|||||||
|
|
||||||
setSingleSectionFilter(sectionKey: StationFilterSection, chosenKey: string) {
|
setSingleSectionFilter(sectionKey: StationFilterSection, chosenKey: string) {
|
||||||
filtersSections[sectionKey].forEach((filterKey) => {
|
filtersSections[sectionKey].forEach((filterKey) => {
|
||||||
if (filterKey != chosenKey) this.filters[filterKey] = initFilters[filterKey];
|
if (typeof this.filters[filterKey] === 'boolean')
|
||||||
|
this.filters[filterKey] = filterKey != chosenKey;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -371,6 +382,7 @@ export default defineComponent({
|
|||||||
@import '../../styles/responsive';
|
@import '../../styles/responsive';
|
||||||
@import '../../styles/card';
|
@import '../../styles/card';
|
||||||
@import '../../styles/animations';
|
@import '../../styles/animations';
|
||||||
|
@import '../../styles/variables';
|
||||||
|
|
||||||
h3.section-header {
|
h3.section-header {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -380,6 +392,7 @@ h3.section-header {
|
|||||||
.card {
|
.card {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: 1fr auto;
|
grid-template-rows: 1fr auto;
|
||||||
|
padding: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card_info {
|
.card_info {
|
||||||
@@ -390,6 +403,10 @@ h3.section-header {
|
|||||||
.changed-filters {
|
.changed-filters {
|
||||||
background-color: #111;
|
background-color: #111;
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
|
|
||||||
|
&[data-active='true'] {
|
||||||
|
color: lightgreen;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.card_controls {
|
.card_controls {
|
||||||
@@ -445,8 +462,12 @@ h3.section-header {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.card_authors-search {
|
.card_authors-search,
|
||||||
|
.card_sceneries-search {
|
||||||
margin: 1em 0;
|
margin: 1em 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
form {
|
form {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -636,10 +657,6 @@ h3.section-header {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@include smallScreen {
|
@include smallScreen {
|
||||||
.card_controls > button.card-button > p {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider {
|
.slider {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|||||||
@@ -1,56 +1,70 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="station-stats">
|
<div
|
||||||
<div class="separator" />
|
class="dropdown"
|
||||||
|
@keydown.esc="showDropdown = false"
|
||||||
|
v-click-outside="() => (showDropdown = false)"
|
||||||
|
>
|
||||||
|
<div class="bg" v-if="showDropdown" @click="showDropdown = false"></div>
|
||||||
|
|
||||||
<div class="stats-row">
|
<button class="filter-button btn--filled btn--image" @click="toggleDropdown" ref="button">
|
||||||
<div>
|
<img src="/images/icon-stats.svg" alt="Open filters icon" />
|
||||||
<span
|
<!-- {{ $t('train-stats.stats-button') }} -->
|
||||||
>{{ $t('station-stats.u-factor') }}
|
<span>STATYSTYKI</span>
|
||||||
<a
|
</button>
|
||||||
href="https://td2.info.pl/dyskusje/wspolczynnik-ugla-czy-to-ma-sens/msg81011/#msg81011"
|
|
||||||
target="_blank"
|
|
||||||
:data-tooltip="$t('station-stats.u-factor-tooltip')"
|
|
||||||
>(?)</a
|
|
||||||
>:
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<b class="u-factor" :style="calculateFactorStyle()">
|
<transition name="dropdown-anim">
|
||||||
{{ uFactor.toFixed(2) }}
|
<div class="dropdown_wrapper" v-if="showDropdown">
|
||||||
</b>
|
<div>
|
||||||
|
<h1 class="text--primary">
|
||||||
|
<img src="/images/icon-stats.svg" alt="Open filters icon" />
|
||||||
|
{{ $t('train-stats.title') }}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<hr style="margin: 0.5em 0" />
|
||||||
|
|
||||||
|
<ul class="stats-list">
|
||||||
|
<li>
|
||||||
|
<span>
|
||||||
|
{{ $t('station-stats.u-factor') }}
|
||||||
|
<a
|
||||||
|
href="https://td2.info.pl/dyskusje/wspolczynnik-ugla-czy-to-ma-sens/msg81011/#msg81011"
|
||||||
|
target="_blank"
|
||||||
|
:data-tooltip="$t('station-stats.u-factor-tooltip')"
|
||||||
|
>(?)</a
|
||||||
|
>:
|
||||||
|
</span>
|
||||||
|
<b class="u-factor" :style="calculateFactorStyle()">
|
||||||
|
{{ uFactor.toFixed(2) }}
|
||||||
|
</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
{{ $t('station-stats.avg-timetable-count') }}
|
||||||
|
<b>{{ avgTimetableCount.toFixed(2) }}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
{{ $t('station-stats.single-track-count') }}
|
||||||
|
<b>{{ trackCount.oneWay }}</b> (<b>{{ trackCount.oneWayElectric }} ⚡</b>)
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
{{ $t('station-stats.double-track-count') }}
|
||||||
|
<b>{{ trackCount.twoWay }}</b>
|
||||||
|
(<b>{{ trackCount.twoWayElectric }} ⚡</b>)
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
{{ $t('station-stats.cross-sceneries') }}
|
||||||
|
<b>{{ trackCount.crossTrack }}</b> (<b>{{ trackCount.crossTrackElectric }} ⚡</b>)
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
{{ $t('station-stats.open-spawns') }} <b>{{ spawnCount.passenger }}</b> - PAS /
|
||||||
|
<b>{{ spawnCount.freight }}</b> - TOW / <b>{{ spawnCount.loco }}</b> - LUZ /
|
||||||
|
<b>{{ spawnCount.all }}</b> - ALL
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div tabindex="0" @focus="() => (showDropdown = false)"></div>
|
||||||
</div>
|
</div>
|
||||||
|
</transition>
|
||||||
<div>
|
|
||||||
•
|
|
||||||
{{ $t('station-stats.avg-timetable-count') }}
|
|
||||||
<b>{{ avgTimetableCount.toFixed(2) }}</b>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
•
|
|
||||||
{{ $t('station-stats.single-track-count') }}
|
|
||||||
<b>{{ trackCount.oneWay }}</b> (<b>{{ trackCount.oneWayElectric }} ⚡</b>)
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
•
|
|
||||||
{{ $t('station-stats.double-track-count') }}
|
|
||||||
<b>{{ trackCount.twoWay }}</b>
|
|
||||||
(<b>{{ trackCount.twoWayElectric }} ⚡</b>)
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
• {{ $t('station-stats.cross-sceneries') }} <b>{{ trackCount.crossTrack }}</b> (<b
|
|
||||||
>{{ trackCount.crossTrackElectric }} ⚡</b
|
|
||||||
>)
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
•
|
|
||||||
{{ $t('station-stats.open-spawns') }} <b>{{ spawnCount.passenger }}</b> - PAS /
|
|
||||||
<b>{{ spawnCount.freight }}</b> - TOW / <b>{{ spawnCount.loco }}</b> - LUZ /
|
|
||||||
<b>{{ spawnCount.all }}</b> - ALL
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -61,11 +75,16 @@ import { useMainStore } from '../../store/mainStore';
|
|||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
mainStore: useMainStore()
|
mainStore: useMainStore(),
|
||||||
|
showDropdown: false
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
toggleDropdown() {
|
||||||
|
this.showDropdown = !this.showDropdown;
|
||||||
|
},
|
||||||
|
|
||||||
calculateFactorStyle() {
|
calculateFactorStyle() {
|
||||||
if (this.uFactor == 0) return '';
|
if (this.uFactor == 0) return '';
|
||||||
|
|
||||||
@@ -171,25 +190,15 @@ export default defineComponent({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.separator {
|
@import '../../styles/dropdown.scss';
|
||||||
width: 100%;
|
@import '../../styles/badge.scss';
|
||||||
height: 2px;
|
|
||||||
|
h1 img {
|
||||||
|
vertical-align: text-bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
margin: 0.5em 0;
|
margin: 0.5em 0;
|
||||||
background-color: #aaa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.station-stats {
|
|
||||||
text-align: center;
|
|
||||||
color: #ddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stats-row {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
text-wrap: pretty;
|
|
||||||
gap: 0.25em;
|
|
||||||
margin-top: 0.25em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.u-factor {
|
.u-factor {
|
||||||
@@ -209,4 +218,20 @@ export default defineComponent({
|
|||||||
color: rgb(22, 245, 22);
|
color: rgb(22, 245, 22);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ul.stats-list {
|
||||||
|
list-style: disc;
|
||||||
|
padding-left: 1em;
|
||||||
|
margin-top: 1em;
|
||||||
|
|
||||||
|
& > li {
|
||||||
|
margin: 0.25em 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include smallScreen {
|
||||||
|
.filter-button span {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -4,302 +4,298 @@
|
|||||||
v-if="apiStore.dataStatuses.connection == Status.Loading && filteredStationList.length == 0"
|
v-if="apiStore.dataStatuses.connection == Status.Loading && filteredStationList.length == 0"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="table_wrapper" v-else-if="filteredStationList.length > 0">
|
<table v-else-if="filteredStationList.length > 0">
|
||||||
<table>
|
<thead>
|
||||||
<thead>
|
<tr>
|
||||||
<tr>
|
<th
|
||||||
<th
|
v-for="headerName in headIds"
|
||||||
v-for="headerName in headIds"
|
:key="headerName"
|
||||||
:key="headerName"
|
@click="changeSorter(headerName)"
|
||||||
@click="changeSorter(headerName)"
|
class="header-text"
|
||||||
class="header-text"
|
:class="headerName"
|
||||||
:class="headerName"
|
|
||||||
>
|
|
||||||
<span class="header_wrapper">
|
|
||||||
<div v-html="$t(`sceneries.headers.${headerName}`)"></div>
|
|
||||||
|
|
||||||
<img
|
|
||||||
class="sort-icon"
|
|
||||||
v-if="activeSorter.headerName == headerName"
|
|
||||||
:src="`/images/icon-arrow-${activeSorter.dir == 1 ? 'asc' : 'desc'}.svg`"
|
|
||||||
alt="sort icon"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</th>
|
|
||||||
|
|
||||||
<th
|
|
||||||
v-for="headerName in headIconsIds"
|
|
||||||
:key="headerName"
|
|
||||||
@click="changeSorter(headerName)"
|
|
||||||
class="header-image"
|
|
||||||
:class="headerName"
|
|
||||||
>
|
|
||||||
<span class="header_wrapper">
|
|
||||||
<img
|
|
||||||
:src="`/images/icon-${headerName}.svg`"
|
|
||||||
:alt="headerName"
|
|
||||||
:title="$t(`sceneries.headers.${headerName}`)"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<img
|
|
||||||
class="sort-icon"
|
|
||||||
v-if="activeSorter.headerName == headerName"
|
|
||||||
:src="`/images/icon-arrow-${activeSorter.dir == 1 ? 'asc' : 'desc'}.svg`"
|
|
||||||
alt="sort icon"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tbody>
|
|
||||||
<tr
|
|
||||||
v-for="station in filteredStationList"
|
|
||||||
:class="{ 'last-selected': lastSelectedStationName == station.name }"
|
|
||||||
:key="station.name"
|
|
||||||
@click.left="setScenery(station.name)"
|
|
||||||
@click.right="openForumSite($event, station.generalInfo?.url)"
|
|
||||||
@keydown.enter="setScenery(station.name)"
|
|
||||||
@keydown.space="openForumSite($event, station.generalInfo?.url)"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
>
|
||||||
<td class="station-name" :class="station.generalInfo?.availability">
|
<span class="header_wrapper">
|
||||||
<b v-if="station.generalInfo?.project" style="color: salmon">{{
|
<div v-html="$t(`sceneries.headers.${headerName}`)"></div>
|
||||||
station.generalInfo.project
|
|
||||||
}}</b>
|
|
||||||
{{ station.name }}
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td class="station-level">
|
<img
|
||||||
<span v-if="station.generalInfo">
|
class="sort-icon"
|
||||||
<span
|
v-if="activeSorter.headerName == headerName"
|
||||||
v-if="
|
:src="`/images/icon-arrow-${activeSorter.dir == 1 ? 'asc' : 'desc'}.svg`"
|
||||||
station.generalInfo.reqLevel > -1 &&
|
alt="sort icon"
|
||||||
station.generalInfo.availability != 'nonPublic' &&
|
|
||||||
station.generalInfo.availability != 'unavailable'
|
|
||||||
"
|
|
||||||
:style="calculateExpStyle(station.generalInfo.reqLevel)"
|
|
||||||
>
|
|
||||||
{{ station.generalInfo.reqLevel >= 2 ? station.generalInfo.reqLevel : 'L' }}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span v-else-if="station.generalInfo.availability == 'abandoned'">
|
|
||||||
<img
|
|
||||||
src="/images/icon-abandoned.svg"
|
|
||||||
alt="non-public"
|
|
||||||
:title="$t('sceneries.info.abandoned')"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span v-else-if="station.generalInfo.availability == 'nonPublic'">
|
|
||||||
<img
|
|
||||||
src="/images/icon-lock.svg"
|
|
||||||
alt="non-public"
|
|
||||||
:title="$t('sceneries.info.non-public')"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span v-else>
|
|
||||||
<img
|
|
||||||
src="/images/icon-unavailable.svg"
|
|
||||||
alt="unavailable"
|
|
||||||
:title="$t('sceneries.info.unavailable')"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span v-else> ? </span>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td class="station-status">
|
|
||||||
<StationStatusBadge
|
|
||||||
:isOnline="station.onlineInfo ? true : false"
|
|
||||||
:dispatcherStatus="station.onlineInfo?.dispatcherStatus"
|
|
||||||
/>
|
/>
|
||||||
</td>
|
</span>
|
||||||
|
</th>
|
||||||
|
|
||||||
<td class="station-dispatcher-name">
|
<th
|
||||||
<span v-if="station.onlineInfo?.dispatcherName">
|
v-for="headerName in headIconsIds"
|
||||||
<b
|
:key="headerName"
|
||||||
v-if="apiStore.donatorsData.includes(station.onlineInfo.dispatcherName)"
|
@click="changeSorter(headerName)"
|
||||||
@click.stop="openDonationCard"
|
class="header-image"
|
||||||
data-tooltip-type="DonatorTooltip"
|
:class="headerName"
|
||||||
:data-tooltip-content="$t('donations.dispatcher-message')"
|
>
|
||||||
>
|
<span class="header_wrapper">
|
||||||
<img src="/images/icon-diamond.svg" alt="" />
|
<img
|
||||||
{{ station.onlineInfo.dispatcherName }}
|
:src="`/images/icon-${headerName}.svg`"
|
||||||
</b>
|
:alt="headerName"
|
||||||
|
:title="$t(`sceneries.headers.${headerName}`)"
|
||||||
|
/>
|
||||||
|
|
||||||
<div v-else>
|
<img
|
||||||
{{ station.onlineInfo.dispatcherName }}
|
class="sort-icon"
|
||||||
</div>
|
v-if="activeSorter.headerName == headerName"
|
||||||
</span>
|
:src="`/images/icon-arrow-${activeSorter.dir == 1 ? 'asc' : 'desc'}.svg`"
|
||||||
</td>
|
alt="sort icon"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
<td class="station-dispatcher-exp">
|
<tbody>
|
||||||
|
<router-link
|
||||||
|
v-for="station in filteredStationList"
|
||||||
|
class="a-row"
|
||||||
|
role="row"
|
||||||
|
:key="station.name"
|
||||||
|
@click.right.prevent="openForumSite($event, station.generalInfo?.url)"
|
||||||
|
@keydown.space.prevent="openForumSite($event, station.generalInfo?.url)"
|
||||||
|
:to="getSceneryRoute(station)"
|
||||||
|
>
|
||||||
|
<td class="station-name" :class="station.generalInfo?.availability">
|
||||||
|
<b v-if="station.generalInfo?.project" style="color: salmon">{{
|
||||||
|
station.generalInfo.project
|
||||||
|
}}</b>
|
||||||
|
{{ station.name }}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td class="station-level">
|
||||||
|
<span v-if="station.generalInfo">
|
||||||
<span
|
<span
|
||||||
v-if="station.onlineInfo && station.onlineInfo?.dispatcherExp != -1"
|
v-if="
|
||||||
:style="
|
station.generalInfo.reqLevel > -1 &&
|
||||||
calculateExpStyle(
|
station.generalInfo.availability != 'nonPublic' &&
|
||||||
station.onlineInfo.dispatcherExp,
|
station.generalInfo.availability != 'unavailable'
|
||||||
station.onlineInfo.dispatcherIsSupporter
|
|
||||||
)
|
|
||||||
"
|
"
|
||||||
|
:style="calculateExpStyle(station.generalInfo.reqLevel)"
|
||||||
>
|
>
|
||||||
{{ station.onlineInfo.dispatcherExp < 2 ? 'L' : station.onlineInfo.dispatcherExp }}
|
{{ station.generalInfo.reqLevel >= 2 ? station.generalInfo.reqLevel : 'L' }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
|
||||||
|
|
||||||
<td class="station-tracks">
|
<span v-else-if="station.generalInfo.availability == 'abandoned'">
|
||||||
<div v-if="station.generalInfo">
|
<img
|
||||||
<span
|
src="/images/icon-abandoned.svg"
|
||||||
v-if="station.generalInfo.routes.singleElectrifiedNames.length != 0"
|
alt="non-public"
|
||||||
class="track catenary"
|
:title="$t('sceneries.info.abandoned')"
|
||||||
:title="`${$t('sceneries.info.single-track-routes-catenary')}${
|
/>
|
||||||
station.generalInfo.routes.singleElectrifiedNames.length
|
</span>
|
||||||
}`"
|
|
||||||
>
|
|
||||||
{{ station.generalInfo.routes.singleElectrifiedNames.length }}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span
|
<span v-else-if="station.generalInfo.availability == 'nonPublic'">
|
||||||
v-if="station.generalInfo.routes.singleOtherNames.length != 0"
|
<img
|
||||||
class="track no-catenary"
|
src="/images/icon-lock.svg"
|
||||||
:title="`${$t('sceneries.info.single-track-routes-other')}${
|
alt="non-public"
|
||||||
station.generalInfo.routes.singleOtherNames.length
|
:title="$t('sceneries.info.non-public')"
|
||||||
}`"
|
/>
|
||||||
>
|
</span>
|
||||||
{{ station.generalInfo.routes.singleOtherNames.length }}
|
|
||||||
</span>
|
<span v-else>
|
||||||
|
<img
|
||||||
|
src="/images/icon-unavailable.svg"
|
||||||
|
alt="unavailable"
|
||||||
|
:title="$t('sceneries.info.unavailable')"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span v-else> ? </span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td class="station-status">
|
||||||
|
<StationStatusBadge
|
||||||
|
:isOnline="station.onlineInfo ? true : false"
|
||||||
|
:dispatcherStatus="station.onlineInfo?.dispatcherStatus"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td class="station-dispatcher-name">
|
||||||
|
<span v-if="station.onlineInfo?.dispatcherName">
|
||||||
|
<b
|
||||||
|
v-if="apiStore.donatorsData.includes(station.onlineInfo.dispatcherName)"
|
||||||
|
@click.prevent="openDonationCard"
|
||||||
|
data-tooltip-type="DonatorTooltip"
|
||||||
|
:data-tooltip-content="$t('donations.dispatcher-message')"
|
||||||
|
>
|
||||||
|
<img src="/images/icon-diamond.svg" alt="" />
|
||||||
|
{{ station.onlineInfo.dispatcherName }}
|
||||||
|
</b>
|
||||||
|
|
||||||
|
<div v-else>
|
||||||
|
{{ station.onlineInfo.dispatcherName }}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
<td class="station-tracks">
|
<td class="station-dispatcher-exp">
|
||||||
<div v-if="station.generalInfo">
|
<span
|
||||||
<span
|
v-if="station.onlineInfo && station.onlineInfo?.dispatcherExp != -1"
|
||||||
v-if="station.generalInfo.routes.doubleElectrifiedNames.length != 0"
|
:style="
|
||||||
class="track catenary"
|
calculateExpStyle(
|
||||||
:title="`${$t('sceneries.info.double-track-routes-catenary')}${
|
station.onlineInfo.dispatcherExp,
|
||||||
station.generalInfo.routes.doubleElectrifiedNames.length
|
station.onlineInfo.dispatcherIsSupporter
|
||||||
}`"
|
)
|
||||||
>
|
"
|
||||||
{{ station.generalInfo.routes.doubleElectrifiedNames.length }}
|
>
|
||||||
</span>
|
{{ station.onlineInfo.dispatcherExp < 2 ? 'L' : station.onlineInfo.dispatcherExp }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
<span
|
<td class="station-tracks">
|
||||||
v-if="station.generalInfo.routes.doubleOtherNames.length != 0"
|
<div v-if="station.generalInfo">
|
||||||
class="track no-catenary"
|
|
||||||
:title="`${$t('sceneries.info.double-track-routes-other')}${
|
|
||||||
station.generalInfo.routes.doubleOtherNames.length
|
|
||||||
}`"
|
|
||||||
>
|
|
||||||
{{ station.generalInfo.routes.doubleOtherNames.length }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td class="station-info">
|
|
||||||
<span
|
<span
|
||||||
v-if="station.generalInfo?.signalType"
|
v-if="station.generalInfo.routes.singleElectrifiedNames.length != 0"
|
||||||
class="scenery-icon icon-info"
|
class="track catenary"
|
||||||
:class="station.generalInfo?.controlType.replace('+', '-')"
|
:title="`${$t('sceneries.info.single-track-routes-catenary')}${
|
||||||
:title="
|
station.generalInfo.routes.singleElectrifiedNames.length
|
||||||
$t('sceneries.info.control-type') +
|
}`"
|
||||||
$t(`controls.${station.generalInfo?.controlType}`)
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
{{ $t(`controls.abbrevs.${station.generalInfo.controlType}`) }}
|
{{ station.generalInfo.routes.singleElectrifiedNames.length }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<img
|
<span
|
||||||
v-if="station.generalInfo?.signalType"
|
v-if="station.generalInfo.routes.singleOtherNames.length != 0"
|
||||||
class="icon-info"
|
class="track no-catenary"
|
||||||
:src="`/images/icon-${station.generalInfo.signalType}.svg`"
|
:title="`${$t('sceneries.info.single-track-routes-other')}${
|
||||||
:alt="station.generalInfo.signalType"
|
station.generalInfo.routes.singleOtherNames.length
|
||||||
:title="
|
}`"
|
||||||
$t('sceneries.info.signals-type') +
|
>
|
||||||
$t(`signals.${station.generalInfo.signalType}`)
|
{{ station.generalInfo.routes.singleOtherNames.length }}
|
||||||
"
|
</span>
|
||||||
/>
|
</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
<img
|
<td class="station-tracks">
|
||||||
v-if="station.generalInfo?.SUP"
|
<div v-if="station.generalInfo">
|
||||||
class="icon-info"
|
<span
|
||||||
src="/images/icon-SUP.svg"
|
v-if="station.generalInfo.routes.doubleElectrifiedNames.length != 0"
|
||||||
alt="SUP (RASP-UZK)"
|
class="track catenary"
|
||||||
:title="$t('sceneries.info.SUP')"
|
:title="`${$t('sceneries.info.double-track-routes-catenary')}${
|
||||||
/>
|
station.generalInfo.routes.doubleElectrifiedNames.length
|
||||||
|
}`"
|
||||||
|
>
|
||||||
|
{{ station.generalInfo.routes.doubleElectrifiedNames.length }}
|
||||||
|
</span>
|
||||||
|
|
||||||
<img
|
<span
|
||||||
v-if="station.generalInfo?.ASDEK"
|
v-if="station.generalInfo.routes.doubleOtherNames.length != 0"
|
||||||
class="icon-info"
|
class="track no-catenary"
|
||||||
src="/images/icon-ASDEK.svg"
|
:title="`${$t('sceneries.info.double-track-routes-other')}${
|
||||||
alt="dSAT ASDEK"
|
station.generalInfo.routes.doubleOtherNames.length
|
||||||
:title="$t('sceneries.info.ASDEK')"
|
}`"
|
||||||
/>
|
>
|
||||||
|
{{ station.generalInfo.routes.doubleOtherNames.length }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
<img
|
<td class="station-info">
|
||||||
v-if="!station.generalInfo"
|
<span
|
||||||
class="icon-info"
|
v-if="station.generalInfo?.signalType"
|
||||||
src="/images/icon-unknown.svg"
|
class="scenery-icon icon-info"
|
||||||
alt="icon-unknown"
|
:class="station.generalInfo?.controlType.replace('+', '-')"
|
||||||
:title="$t('sceneries.info.unknown')"
|
:title="
|
||||||
/>
|
$t('sceneries.info.control-type') +
|
||||||
</td>
|
$t(`controls.${station.generalInfo?.controlType}`)
|
||||||
|
"
|
||||||
<td
|
|
||||||
class="station-users"
|
|
||||||
:class="{ inactive: !station.onlineInfo }"
|
|
||||||
data-tooltip-type="UsersTooltip"
|
|
||||||
:data-tooltip-content="JSON.stringify(station.onlineInfo?.stationTrains ?? [])"
|
|
||||||
>
|
>
|
||||||
<span class="text--primary">{{
|
{{ $t(`controls.abbrevs.${station.generalInfo.controlType}`) }}
|
||||||
station.onlineInfo?.stationTrains?.length ?? '-'
|
</span>
|
||||||
}}</span>
|
|
||||||
/
|
|
||||||
<span class="text--primary">{{ station.onlineInfo?.maxUsers ?? '-' }}</span>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td class="station-likes" :class="{ inactive: !station.onlineInfo }">
|
<img
|
||||||
<span>{{ station.onlineInfo?.dispatcherRate ?? '-' }}</span>
|
v-if="station.generalInfo?.signalType"
|
||||||
</td>
|
class="icon-info"
|
||||||
|
:src="`/images/icon-${station.generalInfo.signalType}.svg`"
|
||||||
|
:alt="station.generalInfo.signalType"
|
||||||
|
:title="
|
||||||
|
$t('sceneries.info.signals-type') + $t(`signals.${station.generalInfo.signalType}`)
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
|
||||||
<td
|
<img
|
||||||
class="station-spawns"
|
v-if="station.generalInfo?.SUP"
|
||||||
:class="{ inactive: !station.onlineInfo }"
|
class="icon-info"
|
||||||
data-tooltip-type="SpawnsTooltip"
|
src="/images/icon-SUP.svg"
|
||||||
:data-tooltip-content="JSON.stringify(station.onlineInfo?.spawns ?? [])"
|
alt="SUP (RASP-UZK)"
|
||||||
>
|
:title="$t('sceneries.info.SUP')"
|
||||||
<span>{{ station.onlineInfo?.spawns.length ?? '-' }}</span>
|
/>
|
||||||
</td>
|
|
||||||
|
|
||||||
<td
|
<img
|
||||||
class="station-schedules all"
|
v-if="station.generalInfo?.ASDEK"
|
||||||
style="width: 30px"
|
class="icon-info"
|
||||||
:class="{ inactive: !station.onlineInfo }"
|
src="/images/icon-ASDEK.svg"
|
||||||
>
|
alt="dSAT ASDEK"
|
||||||
{{ station.onlineInfo?.scheduledTrainCount.all ?? '-' }}
|
:title="$t('sceneries.info.ASDEK')"
|
||||||
</td>
|
/>
|
||||||
|
|
||||||
<td
|
<img
|
||||||
class="station-schedules unconfirmed"
|
v-if="!station.generalInfo"
|
||||||
style="width: 30px"
|
class="icon-info"
|
||||||
:class="{ inactive: !station.onlineInfo }"
|
src="/images/icon-unknown.svg"
|
||||||
>
|
alt="icon-unknown"
|
||||||
{{ station.onlineInfo?.scheduledTrainCount.unconfirmed ?? '-' }}
|
:title="$t('sceneries.info.unknown')"
|
||||||
</td>
|
/>
|
||||||
|
</td>
|
||||||
|
|
||||||
<td
|
<td
|
||||||
class="station-schedules confirmed"
|
class="station-users"
|
||||||
style="width: 30px"
|
:class="{ inactive: !station.onlineInfo }"
|
||||||
:class="{ inactive: !station.onlineInfo }"
|
data-tooltip-type="UsersTooltip"
|
||||||
>
|
:data-tooltip-content="JSON.stringify(station.onlineInfo?.stationTrains ?? [])"
|
||||||
{{ station.onlineInfo?.scheduledTrainCount.confirmed ?? '-' }}
|
>
|
||||||
</td>
|
<span class="text--primary">{{
|
||||||
</tr>
|
station.onlineInfo?.stationTrains?.length ?? '-'
|
||||||
</tbody>
|
}}</span>
|
||||||
</table>
|
/
|
||||||
</div>
|
<span class="text--primary">{{ station.onlineInfo?.maxUsers ?? '-' }}</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td class="station-likes" :class="{ inactive: !station.onlineInfo }">
|
||||||
|
<span>{{ station.onlineInfo?.dispatcherRate ?? '-' }}</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td
|
||||||
|
class="station-spawns"
|
||||||
|
:class="{ inactive: !station.onlineInfo }"
|
||||||
|
data-tooltip-type="SpawnsTooltip"
|
||||||
|
:data-tooltip-content="JSON.stringify(station.onlineInfo?.spawns ?? [])"
|
||||||
|
>
|
||||||
|
<span>{{ station.onlineInfo?.spawns.length ?? '-' }}</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td
|
||||||
|
class="station-schedules all"
|
||||||
|
style="width: 30px"
|
||||||
|
:class="{ inactive: !station.onlineInfo }"
|
||||||
|
>
|
||||||
|
{{ station.onlineInfo?.scheduledTrainCount.all ?? '-' }}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td
|
||||||
|
class="station-schedules unconfirmed"
|
||||||
|
style="width: 30px"
|
||||||
|
:class="{ inactive: !station.onlineInfo }"
|
||||||
|
>
|
||||||
|
{{ station.onlineInfo?.scheduledTrainCount.unconfirmed ?? '-' }}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td
|
||||||
|
class="station-schedules confirmed"
|
||||||
|
style="width: 30px"
|
||||||
|
:class="{ inactive: !station.onlineInfo }"
|
||||||
|
>
|
||||||
|
{{ station.onlineInfo?.scheduledTrainCount.confirmed ?? '-' }}
|
||||||
|
</td>
|
||||||
|
</router-link>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
<div class="no-stations" v-else>
|
<div class="no-stations" v-else>
|
||||||
<div>
|
<div>
|
||||||
@@ -322,7 +318,7 @@ import dateMixin from '../../mixins/dateMixin';
|
|||||||
import styleMixin from '../../mixins/styleMixin';
|
import styleMixin from '../../mixins/styleMixin';
|
||||||
import { useApiStore } from '../../store/apiStore';
|
import { useApiStore } from '../../store/apiStore';
|
||||||
import { useMainStore } from '../../store/mainStore';
|
import { useMainStore } from '../../store/mainStore';
|
||||||
import { Status } from '../../typings/common';
|
import { Station, Status } from '../../typings/common';
|
||||||
import { useTooltipStore } from '../../store/tooltipStore';
|
import { useTooltipStore } from '../../store/tooltipStore';
|
||||||
import { getChangedFilters } from '../../managers/stationFilterManager';
|
import { getChangedFilters } from '../../managers/stationFilterManager';
|
||||||
import { ActiveSorter, HeadIdsType, headIconsIds, headIds } from './typings';
|
import { ActiveSorter, HeadIdsType, headIconsIds, headIds } from './typings';
|
||||||
@@ -337,7 +333,6 @@ export default defineComponent({
|
|||||||
data: () => ({
|
data: () => ({
|
||||||
headIconsIds,
|
headIconsIds,
|
||||||
headIds,
|
headIds,
|
||||||
lastSelectedStationName: '',
|
|
||||||
getChangedFilters
|
getChangedFilters
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -367,21 +362,16 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
setScenery(name: string) {
|
getSceneryRoute(station: Station) {
|
||||||
const station = this.filteredStationList.find((station) => station.name === name);
|
// TODO: Hide tooltips when navigating away
|
||||||
|
|
||||||
if (!station) return;
|
return {
|
||||||
|
|
||||||
this.lastSelectedStationName = station.name;
|
|
||||||
this.tooltipStore.hide();
|
|
||||||
|
|
||||||
this.$router.push({
|
|
||||||
name: 'SceneryView',
|
name: 'SceneryView',
|
||||||
query: {
|
query: {
|
||||||
station: station.name.replaceAll(' ', '_'),
|
station: station.name,
|
||||||
region: this.$route.query.region || undefined
|
region: this.$route.query.region || undefined
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
openDonationCard(e: Event) {
|
openDonationCard(e: Event) {
|
||||||
@@ -417,8 +407,9 @@ export default defineComponent({
|
|||||||
$rowCol: #424242;
|
$rowCol: #424242;
|
||||||
|
|
||||||
.station_table {
|
.station_table {
|
||||||
height: 80vh;
|
height: calc(100vh - 11em);
|
||||||
min-height: 700px;
|
max-height: 2000px;
|
||||||
|
min-height: 500px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
@@ -505,8 +496,10 @@ table {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tr {
|
tr,
|
||||||
|
.a-row {
|
||||||
background-color: $rowCol;
|
background-color: $rowCol;
|
||||||
|
vertical-align: middle;
|
||||||
|
|
||||||
&:nth-child(even) {
|
&:nth-child(even) {
|
||||||
background-color: lighten($rowCol, 5);
|
background-color: lighten($rowCol, 5);
|
||||||
|
|||||||
@@ -23,12 +23,13 @@ export default defineComponent({
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
|
white-space: pre-line;
|
||||||
|
|
||||||
padding: 0.25em 0.5em;
|
padding: 0.25em 0.5em;
|
||||||
border-radius: 0.25em;
|
border-radius: 0.25em;
|
||||||
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: #333;
|
background-color: #1f1f1f;
|
||||||
box-shadow: 0 0 5px 2px #aaa;
|
box-shadow: 0 0 5px 2px #aaa;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,12 +32,11 @@ export default defineComponent({
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.tooltip-content {
|
.tooltip-content {
|
||||||
width: 300px;
|
width: 200px;
|
||||||
|
|
||||||
padding: 0.25em 0.5em;
|
padding: 0.25em 0.5em;
|
||||||
border-radius: 0.25em;
|
border-radius: 0.25em;
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
background-color: #1b1b1b;
|
background-color: #1b1b1b;
|
||||||
box-shadow: 0 0 5px 2px #aaa;
|
box-shadow: 0 0 5px 2px #aaa;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="tooltip" v-show="tooltipStore.type" ref="preview">
|
<div class="tooltip" ref="preview">
|
||||||
<component v-if="tooltipStore.type" :is="tooltipStore.type" />
|
<component v-if="tooltipStore.type" :is="tooltipStore.type" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -13,6 +13,8 @@ import BaseTooltip from './BaseTooltip.vue';
|
|||||||
import SpawnsTooltip from './SpawnsTooltip.vue';
|
import SpawnsTooltip from './SpawnsTooltip.vue';
|
||||||
import UsersTooltip from './UsersTooltip.vue';
|
import UsersTooltip from './UsersTooltip.vue';
|
||||||
|
|
||||||
|
const BOX_PADDING_PX = 20;
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { DonatorTooltip, VehiclePreviewTooltip, BaseTooltip, SpawnsTooltip, UsersTooltip },
|
components: { DonatorTooltip, VehiclePreviewTooltip, BaseTooltip, SpawnsTooltip, UsersTooltip },
|
||||||
|
|
||||||
@@ -33,17 +35,14 @@ export default defineComponent({
|
|||||||
const boxWidth = previewEl.getBoundingClientRect().width;
|
const boxWidth = previewEl.getBoundingClientRect().width;
|
||||||
|
|
||||||
let translateX = '0',
|
let translateX = '0',
|
||||||
translateY = '30px';
|
translateY = `calc(-100% - ${BOX_PADDING_PX}px)`;
|
||||||
|
|
||||||
if (clientWidth < 500) {
|
if (val[0] <= boxWidth / 2 + BOX_PADDING_PX) {
|
||||||
previewEl.style.left = '50%';
|
|
||||||
translateX = '-50%';
|
|
||||||
} else if (val[0] <= boxWidth / 2) {
|
|
||||||
previewEl.style.left = '0';
|
previewEl.style.left = '0';
|
||||||
translateX = '0px';
|
translateX = BOX_PADDING_PX + 'px';
|
||||||
} else if (val[0] >= clientWidth - boxWidth / 2) {
|
} else if (val[0] >= clientWidth - boxWidth / 2 - BOX_PADDING_PX) {
|
||||||
previewEl.style.left = '100%';
|
previewEl.style.left = '100%';
|
||||||
translateX = '-100%';
|
translateX = `calc(-100% - ${BOX_PADDING_PX}px)`;
|
||||||
} else {
|
} else {
|
||||||
previewEl.style.left = `${val[0]}px`;
|
previewEl.style.left = `${val[0]}px`;
|
||||||
translateX = '-50%';
|
translateX = '-50%';
|
||||||
@@ -52,10 +51,10 @@ export default defineComponent({
|
|||||||
previewEl.style.top = `${val[1]}px`;
|
previewEl.style.top = `${val[1]}px`;
|
||||||
|
|
||||||
const isOutside =
|
const isOutside =
|
||||||
val[1] + previewEl.getBoundingClientRect().height + 30 >=
|
val[1] - previewEl.getBoundingClientRect().height <=
|
||||||
window.innerHeight + window.scrollY;
|
window.scrollY + BOX_PADDING_PX * 2;
|
||||||
|
|
||||||
if (isOutside) translateY = 'calc(-100% - 30px)';
|
if (isOutside) translateY = BOX_PADDING_PX + 'px';
|
||||||
previewEl.style.transform = `translate(${translateX}, ${translateY})`;
|
previewEl.style.transform = `translate(${translateX}, ${translateY})`;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,12 +32,11 @@ export default defineComponent({
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.tooltip-content {
|
.tooltip-content {
|
||||||
width: 300px;
|
width: 200px;
|
||||||
|
|
||||||
padding: 0.25em 0.5em;
|
padding: 0.25em 0.5em;
|
||||||
border-radius: 0.25em;
|
border-radius: 0.25em;
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
background-color: #1b1b1b;
|
background-color: #1b1b1b;
|
||||||
box-shadow: 0 0 5px 2px #aaa;
|
box-shadow: 0 0 5px 2px #aaa;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,31 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="tooltip-content">
|
<div class="tooltip-content">
|
||||||
<div v-if="imageState == 'loading'" class="loading-info">
|
<div class="image-box">
|
||||||
{{ $t('vehicle-preview.loading') }}
|
<div v-if="imageState == 'loading'" class="loading-info">
|
||||||
|
{{ $t('vehicle-preview.loading') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="imageState == 'error'" class="loading-info">{{ $t('vehicle-preview.error') }}</div>
|
||||||
|
|
||||||
|
<img
|
||||||
|
v-if="tooltipStore.type"
|
||||||
|
@load="onImageLoad"
|
||||||
|
@error="onImageError"
|
||||||
|
width="300"
|
||||||
|
height="176"
|
||||||
|
:src="`https://stacjownik.spythere.eu/static/images/${vehicleName}--300px.jpg`"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="imageState == 'error'">{{ $t('vehicle-preview.error') }}</div>
|
|
||||||
|
|
||||||
<img
|
|
||||||
v-if="tooltipStore.type"
|
|
||||||
@load="onImageLoad"
|
|
||||||
@error="onImageError"
|
|
||||||
width="300"
|
|
||||||
height="176"
|
|
||||||
class="rounded-md w-full h-auto"
|
|
||||||
:src="`https://static.spythere.eu/images/${tooltipStore.content}--300px.jpg`"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div v-if="imageState == 'error'" class="error-placeholder"></div>
|
|
||||||
|
|
||||||
<div class="vehicle-name">
|
<div class="vehicle-name">
|
||||||
{{ tooltipStore.content.replace(/_/g, ' ') }}
|
{{ vehicleName.replace(/_/g, ' ') }}
|
||||||
|
<span v-if="vehicleCargo">({{ vehicleCargo.id }})</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="vehicle-props" v-if="vehicleData">
|
||||||
|
{{ vehicleData.group.speed }}km/h • {{ vehicleData.group.length }}m •
|
||||||
|
{{ (vehicleData.group.weight / 1000).toFixed(1) }}t
|
||||||
|
<span v-if="vehicleCargo">(+{{ (vehicleCargo.weight / 1000).toFixed(1) }}t)</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -27,11 +33,13 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import { useTooltipStore } from '../../store/tooltipStore';
|
import { useTooltipStore } from '../../store/tooltipStore';
|
||||||
|
import { useApiStore } from '../../store/apiStore';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
tooltipStore: useTooltipStore(),
|
tooltipStore: useTooltipStore(),
|
||||||
|
apiStore: useApiStore(),
|
||||||
imageState: 'loading'
|
imageState: 'loading'
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -41,7 +49,7 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
'tooltipStore.type'(prev, val) {
|
vehicleName(prev, val) {
|
||||||
if (prev != val) this.imageState = 'loading';
|
if (prev != val) this.imageState = 'loading';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -56,6 +64,24 @@ export default defineComponent({
|
|||||||
|
|
||||||
(e.target as HTMLElement).style.display = 'none';
|
(e.target as HTMLElement).style.display = 'none';
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
vehicleName() {
|
||||||
|
return this.tooltipStore.content.split(':')[0];
|
||||||
|
},
|
||||||
|
|
||||||
|
vehicleData() {
|
||||||
|
return this.apiStore.vehiclesData?.find((v) => v.name == this.vehicleName);
|
||||||
|
},
|
||||||
|
|
||||||
|
vehicleCargo() {
|
||||||
|
const x = this.vehicleData?.group.cargoTypes?.find(
|
||||||
|
(c) => c.id == this.tooltipStore.content.split(':')[1]
|
||||||
|
);
|
||||||
|
|
||||||
|
return x;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@@ -64,17 +90,24 @@ export default defineComponent({
|
|||||||
.tooltip-content {
|
.tooltip-content {
|
||||||
width: 300px;
|
width: 300px;
|
||||||
min-height: 200px;
|
min-height: 200px;
|
||||||
background-color: #333;
|
background-color: #1f1f1f;
|
||||||
box-shadow: 0 0 10px 2px #aaa;
|
box-shadow: 0 0 10px 2px #aaa;
|
||||||
|
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
border-radius: 0.5em;
|
border-radius: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.image-box {
|
||||||
|
position: relative;
|
||||||
|
min-height: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
.loading-info {
|
.loading-info {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translateX(-50%);
|
top: 50%;
|
||||||
|
font-size: 1.15em;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
@@ -85,10 +118,13 @@ img {
|
|||||||
.vehicle-name {
|
.vehicle-name {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-top: 0.5em;
|
margin-top: 0.5em;
|
||||||
color: #ccc;
|
|
||||||
text-wrap: wrap;
|
text-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vehicle-props {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
.error-placeholder {
|
.error-placeholder {
|
||||||
height: 176px;
|
height: 176px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,22 @@
|
|||||||
<template>
|
<template>
|
||||||
<span class="stop-label" :data-sbl="stop.isSBL">
|
<span
|
||||||
<span class="name" v-html="stop.nameHtml"></span>
|
class="stop-label"
|
||||||
|
:data-minor="stop.isSBL || (stop.nameRaw.endsWith(', po') && !stop.duration)"
|
||||||
|
>
|
||||||
|
<router-link v-if="/(, podg$|<strong>)/.test(stop.nameHtml)" :to="sceneryHref">
|
||||||
|
<span class="stop-name" v-html="stop.nameHtml"></span>
|
||||||
|
</router-link>
|
||||||
|
|
||||||
|
<span v-else class="stop-name" v-html="stop.nameHtml"></span>
|
||||||
|
|
||||||
<span
|
<span
|
||||||
v-if="stop.position != 'begin'"
|
v-if="stop.position != 'begin'"
|
||||||
class="date arrival"
|
class="date arrival"
|
||||||
:data-status="
|
:data-status-delayed="stop.arrivalDelay > 0"
|
||||||
stop.arrivalDelay > 0 && stop.status != 'unconfirmed'
|
:data-status-preponed="stop.arrivalDelay < 0"
|
||||||
? 'delayed'
|
:data-status="stop.status"
|
||||||
: stop.arrivalDelay < 0 && stop.status != 'unconfirmed'
|
|
||||||
? 'preponed'
|
|
||||||
: stop.arrivalDelay == 0 && stop.status == 'confirmed'
|
|
||||||
? 'on-time'
|
|
||||||
: ''
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
|
p.
|
||||||
<span v-if="stop.arrivalDelay != 0 && stop.status != 'unconfirmed'">
|
<span v-if="stop.arrivalDelay != 0 && stop.status != 'unconfirmed'">
|
||||||
<s>{{ timestampToString(stop.arrivalScheduled) }}</s>
|
<s>{{ timestampToString(stop.arrivalScheduled) }}</s>
|
||||||
{{ timestampToString(stop.arrivalReal) }}
|
{{ timestampToString(stop.arrivalReal) }}
|
||||||
@@ -27,19 +29,16 @@
|
|||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span
|
<span
|
||||||
v-if="
|
v-if="stop.duration"
|
||||||
stop.duration ||
|
|
||||||
(stop.status == 'stopped' &&
|
|
||||||
stop.position != 'begin' &&
|
|
||||||
stop.departureDelay != stop.arrivalDelay)
|
|
||||||
"
|
|
||||||
class="date stop"
|
class="date stop"
|
||||||
:data-stop-types="stop.type.replace(', ', '-')"
|
:data-stop-types="stop.type.replace(', ', '-')"
|
||||||
:data-stop-status="
|
:data-stop-status="stop.departureDelay > 0 && !stop.duration ? 'delayed' : ''"
|
||||||
stop.departureDelay - stop.arrivalDelay > 0 && !stop.duration ? 'delayed' : ''
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
{{ stop.duration || stop.departureDelay - stop.arrivalDelay }}
|
{{
|
||||||
|
stop.duration == 0 && stop.departureDelay > 0
|
||||||
|
? stop.departureDelay - stop.arrivalDelay
|
||||||
|
: stop.duration
|
||||||
|
}}
|
||||||
{{ stop.type == '' ? 'pt' : stop.type }}
|
{{ stop.type == '' ? 'pt' : stop.type }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
@@ -49,16 +48,11 @@
|
|||||||
(stop.duration != 0 || stop.status == 'stopped' || stop.departureDelay != stop.arrivalDelay)
|
(stop.duration != 0 || stop.status == 'stopped' || stop.departureDelay != stop.arrivalDelay)
|
||||||
"
|
"
|
||||||
class="date departure"
|
class="date departure"
|
||||||
:data-status="
|
:data-status-delayed="stop.departureDelay > 0"
|
||||||
stop.departureDelay > 0 && stop.status == 'confirmed'
|
:data-status-preponed="stop.departureDelay < 0"
|
||||||
? 'delayed'
|
:data-status-confirmed="stop.status == 'confirmed'"
|
||||||
: stop.departureDelay < 0 && stop.status == 'confirmed'
|
|
||||||
? 'preponed'
|
|
||||||
: stop.departureDelay == 0 && stop.status == 'confirmed'
|
|
||||||
? 'on-time'
|
|
||||||
: ''
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
|
o.
|
||||||
<span v-if="stop.departureDelay != 0 && stop.status == 'confirmed'">
|
<span v-if="stop.departureDelay != 0 && stop.status == 'confirmed'">
|
||||||
<s>{{ timestampToString(stop.departureScheduled) }}</s>
|
<s>{{ timestampToString(stop.departureScheduled) }}</s>
|
||||||
{{ timestampToString(stop.departureReal) }}
|
{{ timestampToString(stop.departureReal) }}
|
||||||
@@ -76,7 +70,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { PropType, defineComponent } from 'vue';
|
import { PropType, defineComponent } from 'vue';
|
||||||
import dateMixin from '../../mixins/dateMixin';
|
import dateMixin from '../../mixins/dateMixin';
|
||||||
import { TrainScheduleStop } from './TrainSchedule.vue';
|
import { TrainScheduleStop } from './typings';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
mixins: [dateMixin],
|
mixins: [dateMixin],
|
||||||
@@ -86,6 +80,12 @@ export default defineComponent({
|
|||||||
type: Object as PropType<TrainScheduleStop>,
|
type: Object as PropType<TrainScheduleStop>,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
sceneryHref() {
|
||||||
|
return `/scenery?station=${this.stop.sceneryName}&checkpoint=${this.stop.nameRaw}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@@ -96,27 +96,20 @@ $delayedClr: salmon;
|
|||||||
$dateClr: #525151;
|
$dateClr: #525151;
|
||||||
$stopExchangeClr: #db8e29;
|
$stopExchangeClr: #db8e29;
|
||||||
$stopDefaultClr: #252525;
|
$stopDefaultClr: #252525;
|
||||||
$stopNameClr: #22a8d1;
|
$stopNameClr: #303030;
|
||||||
|
|
||||||
|
s {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
.stop-label {
|
.stop-label {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
&[data-sbl='true'] {
|
.stop-name {
|
||||||
.date {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.name {
|
|
||||||
background: none;
|
|
||||||
color: #aaa;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.name {
|
|
||||||
background: $stopNameClr;
|
background: $stopNameClr;
|
||||||
|
border-radius: 0.5em 0 0 0.5em;
|
||||||
padding: 0.3em 0.5em;
|
padding: 0.3em 0.5em;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -130,6 +123,22 @@ $stopNameClr: #22a8d1;
|
|||||||
.date {
|
.date {
|
||||||
background: $dateClr;
|
background: $dateClr;
|
||||||
padding: 0.3em 0.5em;
|
padding: 0.3em 0.5em;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-radius: 0 0.5em 0.5em 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-minor='true'] {
|
||||||
|
.date {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stop-name {
|
||||||
|
background: none;
|
||||||
|
color: #aaa;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.stop {
|
.stop {
|
||||||
@@ -145,27 +154,48 @@ $stopNameClr: #22a8d1;
|
|||||||
color: $delayedClr;
|
color: $delayedClr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.arrival,
|
.stop-label > a {
|
||||||
.departure {
|
z-index: 0;
|
||||||
&[data-status='delayed'] {
|
}
|
||||||
s {
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
span {
|
.stop .arrival {
|
||||||
color: $delayedClr;
|
&[data-status='confirmed'][data-status-delayed='true'] {
|
||||||
}
|
span {
|
||||||
|
color: $delayedClr;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&[data-status='preponed'] {
|
&[data-status='confirmed'][data-status-preponed='true'] {
|
||||||
s {
|
span {
|
||||||
color: #999;
|
color: $preponedClr;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
span {
|
&[data-status='stopped'][data-status-preponed='true'] {
|
||||||
color: $preponedClr;
|
span {
|
||||||
}
|
color: $preponedClr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-status='stopped'][data-status-delayed='true'] {
|
||||||
|
span {
|
||||||
|
color: $delayedClr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stop .departure[data-status-confirmed='true'] {
|
||||||
|
&[data-status-delayed='true'] {
|
||||||
|
span {
|
||||||
|
color: $delayedClr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-status-preponed='true'] {
|
||||||
|
span {
|
||||||
|
color: $preponedClr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,47 +2,60 @@
|
|||||||
<div class="train-info" :data-extended="extended">
|
<div class="train-info" :data-extended="extended">
|
||||||
<section class="train-general">
|
<section class="train-general">
|
||||||
<div class="general-top-bar">
|
<div class="general-top-bar">
|
||||||
<div>
|
<div class="top-bar-header">
|
||||||
<b class="warning-timeout" v-if="train.isTimeout" :title="$t('trains.timeout')">?</b>
|
<b class="warning-timeout" v-if="train.isTimeout" :title="$t('trains.timeout')">?</b>
|
||||||
<span class="timetable-id" v-if="train.timetableData">
|
<span class="timetable-id" v-if="train.timetableData">
|
||||||
#{{ train.timetableData.timetableId }}
|
#{{ train.timetableData.timetableId }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span
|
<span
|
||||||
class="timetable-warnings"
|
class="train-badge twr"
|
||||||
v-if="train.timetableData?.TWR || train.timetableData?.SKR"
|
v-if="train.timetableData?.TWR"
|
||||||
|
data-tooltip-type="BaseTooltip"
|
||||||
|
:data-tooltip-content="$t('warnings.TWR')"
|
||||||
>
|
>
|
||||||
<span
|
TWR
|
||||||
class="train-badge twr"
|
|
||||||
v-if="train.timetableData?.TWR"
|
|
||||||
:title="$t('general.TWR')"
|
|
||||||
>
|
|
||||||
TWR
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="train-badge skr"
|
|
||||||
v-if="train.timetableData?.SKR"
|
|
||||||
:title="$t('general.SKR')"
|
|
||||||
>
|
|
||||||
SKR
|
|
||||||
</span>
|
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<strong>
|
<span
|
||||||
<span v-if="train.timetableData" class="text--primary"
|
class="train-badge tn"
|
||||||
>{{ train.timetableData.category }} </span
|
v-if="train.timetableData?.hasDangerousCargo"
|
||||||
>
|
data-tooltip-type="BaseTooltip"
|
||||||
<span class="train-number">{{ train.trainNo }}</span>
|
:data-tooltip-content="$t('warnings.TN')"
|
||||||
</strong>
|
|
||||||
<span>•</span>
|
|
||||||
<b
|
|
||||||
class="level-badge driver"
|
|
||||||
:style="calculateExpStyle(train.driverLevel, train.isSupporter)"
|
|
||||||
>
|
>
|
||||||
{{ train.driverLevel < 2 ? 'L' : `${train.driverLevel}` }}
|
TN
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span
|
||||||
|
class="train-badge pn"
|
||||||
|
v-if="train.timetableData?.hasExtraDeliveries"
|
||||||
|
data-tooltip-type="BaseTooltip"
|
||||||
|
:data-tooltip-content="$t('warnings.PN')"
|
||||||
|
>
|
||||||
|
PN
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<b
|
||||||
|
v-if="train.timetableData"
|
||||||
|
data-tooltip-type="BaseTooltip"
|
||||||
|
:data-tooltip-content="getCategoryExplanation(train.timetableData.category)"
|
||||||
|
class="text--primary tooltip-help"
|
||||||
|
>
|
||||||
|
{{ train.timetableData.category }}
|
||||||
</b>
|
</b>
|
||||||
|
|
||||||
|
<b class="train-number">{{ train.trainNo }}</b>
|
||||||
|
|
||||||
|
<span>•</span>
|
||||||
|
|
||||||
<div class="train-driver">
|
<div class="train-driver">
|
||||||
|
<b
|
||||||
|
class="level-badge driver"
|
||||||
|
:style="calculateExpStyle(train.driverLevel, train.isSupporter)"
|
||||||
|
>
|
||||||
|
{{ train.driverLevel < 2 ? 'L' : `${train.driverLevel}` }}
|
||||||
|
</b>
|
||||||
|
|
||||||
<b
|
<b
|
||||||
v-if="apiStore.donatorsData.includes(train.driverName)"
|
v-if="apiStore.donatorsData.includes(train.driverName)"
|
||||||
data-tooltip-type="DonatorTooltip"
|
data-tooltip-type="DonatorTooltip"
|
||||||
@@ -55,19 +68,6 @@
|
|||||||
<span v-else>{{ train.driverName }}</span>
|
<span v-else>{{ train.driverName }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="extended">
|
|
||||||
<button class="btn-timetable btn--image btn--action" @click="navigateToJournal">
|
|
||||||
<img src="/images/icon-train.svg" alt="train icon" />
|
|
||||||
<span>
|
|
||||||
{{ $t('trains.journal-button') }}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button class="btn-exit btn--image btn--action" @click="closeModal">
|
|
||||||
<img src="/images/icon-exit.svg" alt="modal exit icon" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="general-timetable" v-if="train.timetableData">
|
<div class="general-timetable" v-if="train.timetableData">
|
||||||
@@ -88,7 +88,7 @@
|
|||||||
<div class="general-stops" v-if="train.timetableData">
|
<div class="general-stops" v-if="train.timetableData">
|
||||||
<span v-if="train.timetableData.followingStops.length > 2">
|
<span v-if="train.timetableData.followingStops.length > 2">
|
||||||
{{ $t('trains.via-title') }}
|
{{ $t('trains.via-title') }}
|
||||||
<span v-html="displayStopList(train.timetableData.followingStops)"></span>
|
<span v-html="getTrainStopsHtml(train.timetableData.followingStops)"></span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -97,21 +97,22 @@
|
|||||||
<ProgressBar :progressPercent="confirmedPercentage(train.timetableData.followingStops)" />
|
<ProgressBar :progressPercent="confirmedPercentage(train.timetableData.followingStops)" />
|
||||||
|
|
||||||
<span class="progress-distance">
|
<span class="progress-distance">
|
||||||
{{ currentDistance(train.timetableData.followingStops) }} km /
|
<span>{{ currentDistance(train.timetableData.followingStops) }} km</span>
|
||||||
<span class="text--primary"> {{ train.timetableData.routeDistance }} km </span>
|
<span>/</span>
|
||||||
|
|
<span class="text--primary">{{ train.timetableData.routeDistance }} km </span>
|
||||||
|
<span>|</span>
|
||||||
<span v-html="currentDelay(train.timetableData.followingStops)"></span>
|
<span v-html="currentDelay(train.timetableData.followingStops)"></span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="status-badges">
|
<div class="status-badges">
|
||||||
<div v-if="!train.currentStationHash" class="train-badge offline">
|
<div v-if="!train.currentStationHash" class="train-badge offline">
|
||||||
<img src="/images/icon-offline.svg" alt="" />
|
<img src="/images/icon-offline.svg" alt="offline train icon" />
|
||||||
{{ $t('trains.scenery-offline') }}
|
{{ $t('trains.scenery-offline') }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="!train.online" class="train-badge offline">
|
<div v-if="!train.online" class="train-badge offline">
|
||||||
<img src="/images/icon-offline.svg" alt="" />
|
<img src="/images/icon-offline.svg" alt="offline train icon" />
|
||||||
Offline {{ lastSeenMessage(train.lastSeen) }}
|
Offline {{ lastSeenMessage(train.lastSeen) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -131,12 +132,53 @@
|
|||||||
<div>
|
<div>
|
||||||
<img src="/images/icon-speed.svg" alt="speed icon" />
|
<img src="/images/icon-speed.svg" alt="speed icon" />
|
||||||
{{ train.speed }} km/h
|
{{ train.speed }} km/h
|
||||||
|
|
||||||
|
<span v-if="stockSpeedLimit != Infinity">
|
||||||
|
•
|
||||||
|
<em
|
||||||
|
class="text--grayed"
|
||||||
|
style="text-decoration: underline dotted"
|
||||||
|
tabindex="0"
|
||||||
|
:data-tooltip="$t('trains.vmax-tooltip')"
|
||||||
|
>
|
||||||
|
{{ stockSpeedLimit }} km/h
|
||||||
|
</em>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text--grayed" style="margin-top: 0.25em">
|
<div class="text--grayed" style="margin-top: 0.25em">
|
||||||
{{ displayTrainPosition(train) }}
|
{{ displayTrainPosition(train) }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="train-dangers"
|
||||||
|
v-if="extended && train.timetableData && train.timetableData.warningNotes"
|
||||||
|
>
|
||||||
|
<div class="dangers-badges">
|
||||||
|
<div v-if="train.timetableData?.TWR">
|
||||||
|
<div class="train-badge twr">TWR</div>
|
||||||
|
- {{ $t('warnings.TWR') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="train.timetableData?.hasDangerousCargo">
|
||||||
|
<div class="train-badge tn">TN</div>
|
||||||
|
- {{ $t('warnings.TN') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="train.timetableData?.hasExtraDeliveries">
|
||||||
|
<div class="train-badge pn">PN</div>
|
||||||
|
- {{ $t('warnings.PN') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="dangers-notes">
|
||||||
|
<h4>{{ $t('warnings.header-title') }}</h4>
|
||||||
|
<p>
|
||||||
|
<i>{{ train.timetableData?.warningNotes }}</i>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="train-stats" v-if="!extended">
|
<section class="train-stats" v-if="!extended">
|
||||||
@@ -167,11 +209,11 @@ import ProgressBar from '../Global/ProgressBar.vue';
|
|||||||
import { useMainStore } from '../../store/mainStore';
|
import { useMainStore } from '../../store/mainStore';
|
||||||
import { useApiStore } from '../../store/apiStore';
|
import { useApiStore } from '../../store/apiStore';
|
||||||
import StockList from '../Global/StockList.vue';
|
import StockList from '../Global/StockList.vue';
|
||||||
import modalTrainMixin from '../../mixins/modalTrainMixin';
|
|
||||||
import { Train } from '../../typings/common';
|
import { Train } from '../../typings/common';
|
||||||
|
import trainCategoryMixin from '../../mixins/trainCategoryMixin';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
mixins: [trainInfoMixin, styleMixin, modalTrainMixin],
|
mixins: [trainInfoMixin, styleMixin, trainCategoryMixin],
|
||||||
components: { ProgressBar, StockList },
|
components: { ProgressBar, StockList },
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
@@ -191,16 +233,23 @@ export default defineComponent({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
computed: {
|
||||||
navigateToJournal() {
|
stockSpeedLimit() {
|
||||||
this.$router.push({
|
return this.train.stockList.reduce((acc, stockName) => {
|
||||||
|
const vehicleSpeed =
|
||||||
|
this.apiStore.vehiclesData?.find((v) => v.name == stockName.split(':')[0])?.group.speed ??
|
||||||
|
300;
|
||||||
|
|
||||||
|
return Math.min(vehicleSpeed, acc);
|
||||||
|
}, 300);
|
||||||
|
},
|
||||||
|
journalRouteLocation() {
|
||||||
|
return {
|
||||||
path: '/journal/timetables',
|
path: '/journal/timetables',
|
||||||
query: {
|
query: {
|
||||||
'search-driver': this.train.driverName
|
'search-driver': this.train.driverName
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
this.closeModal();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -227,6 +276,28 @@ export default defineComponent({
|
|||||||
line-height: 1.5em;
|
line-height: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.train-dangers {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dangers-badges {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dangers-notes {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-top: 0.25em;
|
||||||
|
max-height: 200px;
|
||||||
|
max-width: 500px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.train-info {
|
.train-info {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 2fr 1fr;
|
grid-template-columns: 2fr 1fr;
|
||||||
@@ -242,6 +313,12 @@ export default defineComponent({
|
|||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.train-driver {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
.train-driver img {
|
.train-driver img {
|
||||||
max-height: 20px;
|
max-height: 20px;
|
||||||
vertical-align: text-bottom;
|
vertical-align: text-bottom;
|
||||||
@@ -273,24 +350,15 @@ export default defineComponent({
|
|||||||
.general-top-bar {
|
.general-top-bar {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
|
|
||||||
& > div {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
|
|
||||||
gap: 0.25em;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-timetable {
|
.top-bar-header {
|
||||||
padding: 0.25em;
|
display: flex;
|
||||||
}
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
.btn-exit {
|
gap: 0.25em;
|
||||||
padding: 0.25em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.general-status {
|
.general-status {
|
||||||
@@ -337,10 +405,14 @@ export default defineComponent({
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5em;
|
||||||
|
padding: 0.5em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress-distance {
|
.progress-distance {
|
||||||
margin-right: 0.25em;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.25em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.timetable-warnings {
|
.timetable-warnings {
|
||||||
@@ -353,9 +425,5 @@ export default defineComponent({
|
|||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
gap: 1em 0;
|
gap: 1em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-timetable > span {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,100 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="train-modal" v-if="chosenTrain" @keydown.esc="closeModal">
|
|
||||||
<div class="modal-background" @click="closeModal"></div>
|
|
||||||
<div class="modal-content" ref="content" tabindex="0">
|
|
||||||
<TrainInfo :train="chosenTrain" :extended="true" ref="trainInfo" />
|
|
||||||
<TrainSchedule :train="chosenTrain" tabindex="0" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent } from 'vue';
|
|
||||||
import modalTrainMixin from '../../mixins/modalTrainMixin';
|
|
||||||
import TrainInfo from './TrainInfo.vue';
|
|
||||||
import TrainSchedule from './TrainSchedule.vue';
|
|
||||||
import { Train } from '../../typings/common';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: { TrainInfo, TrainSchedule },
|
|
||||||
mixins: [modalTrainMixin],
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
chosenTrain() {
|
|
||||||
return this.store.trainList.find((train) => train.id == this.store.chosenModalTrainId);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
watch: {
|
|
||||||
chosenTrain(train: Train | undefined) {
|
|
||||||
this.$nextTick(() => {
|
|
||||||
if (train) {
|
|
||||||
const contentEl = this.$refs['content'] as HTMLElement;
|
|
||||||
contentEl.focus();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
@import '../../styles/responsive.scss';
|
|
||||||
|
|
||||||
.train-modal {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
color: white;
|
|
||||||
z-index: 200;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-background {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
|
||||||
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
background-color: rgba(0, 0, 0, 0.55);
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-content {
|
|
||||||
position: relative;
|
|
||||||
overflow-y: scroll;
|
|
||||||
|
|
||||||
margin-top: 1em;
|
|
||||||
|
|
||||||
width: 95vw;
|
|
||||||
max-height: 95vh;
|
|
||||||
|
|
||||||
background-color: #1a1a1a;
|
|
||||||
box-shadow: 0 0 15px 10px #0e0e0e;
|
|
||||||
}
|
|
||||||
|
|
||||||
@include midScreen {
|
|
||||||
.exit {
|
|
||||||
margin: 0.5em;
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 1.75rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include smallScreen {
|
|
||||||
.modal_content {
|
|
||||||
max-height: 85vh;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -15,12 +15,14 @@
|
|||||||
<div class="search_content">
|
<div class="search_content">
|
||||||
<div class="search-box">
|
<div class="search-box">
|
||||||
<input
|
<input
|
||||||
|
v-model="searchedTrain"
|
||||||
class="search-input"
|
class="search-input"
|
||||||
ref="initFocusedElement"
|
ref="initFocusedElement"
|
||||||
|
id="train-search"
|
||||||
|
name="train-search"
|
||||||
@focus="preventKeyDown = true"
|
@focus="preventKeyDown = true"
|
||||||
@blur="preventKeyDown = false"
|
@blur="preventKeyDown = false"
|
||||||
:placeholder="$t(`options.search-train`)"
|
:placeholder="$t(`options.search-train`)"
|
||||||
v-model="searchedTrain"
|
|
||||||
/>
|
/>
|
||||||
<button class="search-exit">
|
<button class="search-exit">
|
||||||
<img
|
<img
|
||||||
@@ -33,11 +35,13 @@
|
|||||||
|
|
||||||
<div class="search-box">
|
<div class="search-box">
|
||||||
<input
|
<input
|
||||||
|
v-model="searchedDriver"
|
||||||
class="search-input"
|
class="search-input"
|
||||||
|
id="driver-search"
|
||||||
|
name="driver-search"
|
||||||
@focus="preventKeyDown = true"
|
@focus="preventKeyDown = true"
|
||||||
@blur="preventKeyDown = false"
|
@blur="preventKeyDown = false"
|
||||||
:placeholder="$t(`options.search-driver`)"
|
:placeholder="$t(`options.search-driver`)"
|
||||||
v-model="searchedDriver"
|
|
||||||
/>
|
/>
|
||||||
<button class="search-exit">
|
<button class="search-exit">
|
||||||
<img
|
<img
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
:data-stop-type="stop.type"
|
:data-stop-type="stop.type"
|
||||||
:data-minor-stop-active="stop.isActive"
|
:data-minor-stop-active="stop.isActive"
|
||||||
:data-last-confirmed="stop.isLastConfirmed"
|
:data-last-confirmed="stop.isLastConfirmed"
|
||||||
x
|
|
||||||
>
|
>
|
||||||
<span class="stop_info">
|
<span class="stop_info">
|
||||||
<span class="distance">
|
<span class="distance">
|
||||||
@@ -48,26 +47,78 @@
|
|||||||
<span
|
<span
|
||||||
v-if="
|
v-if="
|
||||||
stop.departureLine &&
|
stop.departureLine &&
|
||||||
stop.departureLine == scheduleStops[i + 1]?.arrivalLine &&
|
scheduleStops[i + 1] != undefined &&
|
||||||
!/sbl/gi.test(stop.departureLine)
|
!/-|_|(^it\d+)|(^sbl)/gi.test(stop.departureLine)
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
{{ stop.departureLine }}
|
<div class="scenery-route">
|
||||||
</span>
|
<span>{{ stop.departureLine }}</span>
|
||||||
|
<span v-if="stop.departureLineInfo">
|
||||||
|
| {{ stop.departureLineInfo.routeSpeed }}
|
||||||
|
|
||||||
<span v-else-if="stop.departureLine && !/sbl/gi.test(stop.departureLine)">
|
<img
|
||||||
<div>{{ stop.departureLine }}</div>
|
:src="
|
||||||
<div
|
stop.departureLineInfo.isElectric
|
||||||
class="scenery-change-name"
|
? '/images/icon-catenary.svg'
|
||||||
v-if="
|
: '/images/icon-we4a.png'
|
||||||
i < scheduleStops.length - 1 &&
|
"
|
||||||
stop.sceneryName != scheduleStops[i + 1].sceneryName
|
width="10"
|
||||||
"
|
data-tooltip-type="BaseTooltip"
|
||||||
>
|
:data-tooltip-content="
|
||||||
{{ scheduleStops[i + 1].sceneryName }}
|
$t(
|
||||||
|
`trains.${!stop.departureLineInfo.isElectric ? 'no-' : ''}catenary-tooltip`
|
||||||
|
)
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<img
|
||||||
|
v-if="stop.departureLineInfo.isRouteSBL"
|
||||||
|
src="/images/icon-sbl-transparent.svg"
|
||||||
|
width="10"
|
||||||
|
data-tooltip-type="BaseTooltip"
|
||||||
|
:data-tooltip-content="$t('trains.sbl-tooltip')"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
{{ scheduleStops[i + 1].arrivalLine }}
|
<div
|
||||||
|
v-if="stop.sceneryName != scheduleStops[i + 1]?.sceneryName"
|
||||||
|
class="scenery-change-name"
|
||||||
|
>
|
||||||
|
<span>{{ scheduleStops[i + 1].sceneryName }}</span>
|
||||||
|
<span v-if="stop.departureLineInfo?.routeTracks == 1"> ↕</span>
|
||||||
|
<span v-else> ⇵</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="scenery-route">
|
||||||
|
<span> {{ scheduleStops[i + 1].arrivalLine }}</span>
|
||||||
|
|
||||||
|
<span v-if="scheduleStops[i + 1].arrivalLineInfo">
|
||||||
|
| {{ scheduleStops[i + 1].arrivalLineInfo!.routeSpeed }}
|
||||||
|
|
||||||
|
<img
|
||||||
|
:src="
|
||||||
|
scheduleStops[i + 1].arrivalLineInfo!.isElectric
|
||||||
|
? '/images/icon-catenary.svg'
|
||||||
|
: '/images/icon-we4a.png'
|
||||||
|
"
|
||||||
|
data-tooltip-type="BaseTooltip"
|
||||||
|
:data-tooltip-content="
|
||||||
|
$t(
|
||||||
|
`trains.${!scheduleStops[i + 1].arrivalLineInfo!.isElectric ? 'no-' : ''}catenary-tooltip`
|
||||||
|
)
|
||||||
|
"
|
||||||
|
width="10"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<img
|
||||||
|
v-if="scheduleStops[i + 1].arrivalLineInfo!.isRouteSBL"
|
||||||
|
src="/images/icon-sbl-transparent.svg"
|
||||||
|
width="10"
|
||||||
|
data-tooltip-type="BaseTooltip"
|
||||||
|
:data-tooltip-content="$t('trains.sbl-tooltip')"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -85,40 +136,8 @@ import StopLabel from './StopLabel.vue';
|
|||||||
import StockList from '../Global/StockList.vue';
|
import StockList from '../Global/StockList.vue';
|
||||||
import { useMainStore } from '../../store/mainStore';
|
import { useMainStore } from '../../store/mainStore';
|
||||||
import { useApiStore } from '../../store/apiStore';
|
import { useApiStore } from '../../store/apiStore';
|
||||||
import { Train } from '../../typings/common';
|
import { StationRoutesInfo, Train } from '../../typings/common';
|
||||||
|
import { TrainScheduleStop } from './typings';
|
||||||
export interface TrainScheduleStop {
|
|
||||||
nameHtml: string;
|
|
||||||
nameRaw: string;
|
|
||||||
|
|
||||||
status: 'confirmed' | 'unconfirmed' | 'stopped';
|
|
||||||
type: string;
|
|
||||||
position: 'begin' | 'end' | 'en-route';
|
|
||||||
|
|
||||||
arrivalScheduled: number;
|
|
||||||
arrivalReal: number;
|
|
||||||
|
|
||||||
departureScheduled: number;
|
|
||||||
departureReal: number;
|
|
||||||
|
|
||||||
departureDelay: number;
|
|
||||||
arrivalDelay: number;
|
|
||||||
|
|
||||||
duration: number | null;
|
|
||||||
|
|
||||||
isActive: boolean;
|
|
||||||
isLastConfirmed: boolean;
|
|
||||||
isSBL: boolean;
|
|
||||||
|
|
||||||
sceneryName: string | null;
|
|
||||||
sceneryHash: string;
|
|
||||||
distance: number;
|
|
||||||
|
|
||||||
arrivalLine: string | null;
|
|
||||||
departureLine: string | null;
|
|
||||||
|
|
||||||
comments: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { StopLabel, StockList },
|
components: { StopLabel, StockList },
|
||||||
@@ -142,17 +161,29 @@ export default defineComponent({
|
|||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
scheduleStops(): TrainScheduleStop[] {
|
scheduleStops(): TrainScheduleStop[] {
|
||||||
let currentSceneryIndex = 0;
|
if (!this.train.timetableData) return [];
|
||||||
|
|
||||||
|
const { timetablePath } = this.train.timetableData;
|
||||||
|
let currentPathIndex = 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
this.train.timetableData?.followingStops.map((stop, i, arr) => {
|
this.train.timetableData?.followingStops.map((stop, i, arr) => {
|
||||||
if (
|
const isExternal =
|
||||||
i > 0 &&
|
i < arr.length - 1 &&
|
||||||
stop.arrivalLine &&
|
stop.departureLine === timetablePath[currentPathIndex].departureRouteExt;
|
||||||
stop.arrivalLine != arr[i - 1].departureLine &&
|
|
||||||
!/sbl/gi.test(stop.arrivalLine)
|
const sceneryName = timetablePath[currentPathIndex].stationName;
|
||||||
)
|
const sceneryInfo = this.apiStore.sceneryData.find((st) => st.name == sceneryName);
|
||||||
currentSceneryIndex++;
|
|
||||||
|
const arrivalLineInfo = sceneryInfo?.routesInfo.find(
|
||||||
|
(r) => r.routeName == stop.arrivalLine
|
||||||
|
);
|
||||||
|
|
||||||
|
const departureLineInfo = sceneryInfo?.routesInfo.find(
|
||||||
|
(r) => r.routeName == stop.departureLine
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isExternal) currentPathIndex++;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
nameHtml: stop.stopName,
|
nameHtml: stop.stopName,
|
||||||
@@ -174,14 +205,18 @@ export default defineComponent({
|
|||||||
arrivalLine: stop.arrivalLine,
|
arrivalLine: stop.arrivalLine,
|
||||||
departureLine: stop.departureLine,
|
departureLine: stop.departureLine,
|
||||||
|
|
||||||
|
arrivalLineInfo: arrivalLineInfo,
|
||||||
|
departureLineInfo: departureLineInfo,
|
||||||
|
|
||||||
|
isExternal,
|
||||||
|
|
||||||
type: stop.stopType,
|
type: stop.stopType,
|
||||||
distance: stop.stopDistance,
|
distance: stop.stopDistance,
|
||||||
isActive: this.activeMinorStops.includes(i),
|
isActive: this.activeMinorStops.includes(i),
|
||||||
isLastConfirmed: this.lastConfirmed === i && !stop.terminatesHere,
|
isLastConfirmed: this.lastConfirmed === i && !stop.terminatesHere,
|
||||||
isSBL: /sbl/gi.test(stop.stopName),
|
isSBL: /sbl/gi.test(stop.stopName),
|
||||||
position: stop.beginsHere ? 'begin' : stop.terminatesHere ? 'end' : 'en-route',
|
position: stop.beginsHere ? 'begin' : stop.terminatesHere ? 'end' : 'en-route',
|
||||||
sceneryHash: '',
|
sceneryName,
|
||||||
sceneryName: this.train.timetableData!.sceneryNames[currentSceneryIndex],
|
|
||||||
status: stop.confirmed ? 'confirmed' : stop.stopped ? 'stopped' : 'unconfirmed'
|
status: stop.confirmed ? 'confirmed' : stop.stopped ? 'stopped' : 'unconfirmed'
|
||||||
};
|
};
|
||||||
}) ?? []
|
}) ?? []
|
||||||
@@ -210,7 +245,7 @@ export default defineComponent({
|
|||||||
i < this.train.timetableData!.followingStops.length;
|
i < this.train.timetableData!.followingStops.length;
|
||||||
i++
|
i++
|
||||||
) {
|
) {
|
||||||
if (/po\.|sbl/gi.test(this.train.timetableData!.followingStops[i].stopNameRAW))
|
if (/(, po$|sbl|, pe$)/gi.test(this.train.timetableData!.followingStops[i].stopNameRAW))
|
||||||
activeMinorStopList.push(i);
|
activeMinorStopList.push(i);
|
||||||
else break;
|
else break;
|
||||||
}
|
}
|
||||||
@@ -247,7 +282,7 @@ $blinkAnim: 0.5s ease-in-out alternate infinite blink;
|
|||||||
}
|
}
|
||||||
|
|
||||||
.train-schedule {
|
.train-schedule {
|
||||||
padding: 0 1em;
|
padding: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.schedule-wrapper {
|
.schedule-wrapper {
|
||||||
@@ -483,22 +518,36 @@ $blinkAnim: 0.5s ease-in-out alternate infinite blink;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom-line-info {
|
.scenery-route {
|
||||||
.scenery-change-name {
|
display: flex;
|
||||||
position: relative;
|
gap: 0.25em;
|
||||||
margin: 0.25em 0;
|
|
||||||
|
|
||||||
&::before {
|
span:nth-child(2) {
|
||||||
content: '';
|
display: flex;
|
||||||
position: absolute;
|
gap: 0.25em;
|
||||||
height: 2px;
|
align-items: center;
|
||||||
width: 30px;
|
}
|
||||||
background-color: #aaa;
|
|
||||||
|
|
||||||
top: 50%;
|
img {
|
||||||
right: calc(100% + 5px);
|
width: 1em;
|
||||||
transform: translate(0, -50%);
|
cursor: help;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.scenery-change-name {
|
||||||
|
position: relative;
|
||||||
|
margin: 0.25em 0;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
height: 2px;
|
||||||
|
width: 30px;
|
||||||
|
background-color: #aaa;
|
||||||
|
|
||||||
|
top: 50%;
|
||||||
|
right: calc(100% + 5px);
|
||||||
|
transform: translate(0, -50%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="dropdown" @keydown.esc="showOptions = false" @focusout="showOptions = false">
|
<div
|
||||||
|
class="dropdown"
|
||||||
|
@keydown.esc="showOptions = false"
|
||||||
|
v-click-outside="() => (showOptions = false)"
|
||||||
|
>
|
||||||
<div class="bg" v-if="showOptions" @click="showOptions = false"></div>
|
<div class="bg" v-if="showOptions" @click="showOptions = false"></div>
|
||||||
|
|
||||||
<button class="filter-button btn--filled btn--image" @click="toggleShowOptions" ref="button">
|
<button class="filter-button btn--filled btn--image" @click="toggleShowOptions" ref="button">
|
||||||
@@ -19,21 +23,21 @@
|
|||||||
<div v-if="apiStore.dataStatuses.connection == Status.Loaded && regionTrains.length > 0">
|
<div v-if="apiStore.dataStatuses.connection == Status.Loaded && regionTrains.length > 0">
|
||||||
<div class="top-list general">
|
<div class="top-list general">
|
||||||
<transition-group tag="ul" name="stats-anim">
|
<transition-group tag="ul" name="stats-anim">
|
||||||
<li class="badge" key="timetable-count">
|
<li class="badge stat-badge" key="timetable-count">
|
||||||
<span>{{ $t('train-stats.timetable-count') }}</span>
|
<span>{{ $t('train-stats.timetable-count') }}</span>
|
||||||
<span>
|
<span>
|
||||||
<b>{{ regionTrainsWithTT.length }}</b>
|
<b>{{ regionTrainsWithTT.length }}</b>
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="badge" key="avg-speed">
|
<li class="badge stat-badge" key="avg-speed">
|
||||||
<span>{{ $t('train-stats.avg-speed') }}</span>
|
<span>{{ $t('train-stats.avg-speed') }}</span>
|
||||||
<span>
|
<span>
|
||||||
<b>{{ stats.avgSpeed.toFixed(1) }} km/h</b>
|
<b>{{ stats.avgSpeed.toFixed(1) }} km/h</b>
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="badge" key="avg-distance">
|
<li class="badge stat-badge" key="avg-distance">
|
||||||
<span>{{ $t('train-stats.avg-timetable') }}</span>
|
<span>{{ $t('train-stats.avg-timetable') }}</span>
|
||||||
<span>
|
<span>
|
||||||
<b>{{ stats.avgDistance.toFixed(1) }} km</b>
|
<b>{{ stats.avgDistance.toFixed(1) }} km</b>
|
||||||
@@ -46,7 +50,7 @@
|
|||||||
<h3>{{ $t('train-stats.top-categories') }}</h3>
|
<h3>{{ $t('train-stats.top-categories') }}</h3>
|
||||||
|
|
||||||
<transition-group tag="ul" name="stats-anim">
|
<transition-group tag="ul" name="stats-anim">
|
||||||
<li class="badge" v-for="top in stats.topCategories" :key="top.name">
|
<li class="badge stat-badge" v-for="top in stats.topCategories" :key="top.name">
|
||||||
<span>{{ top.name }}</span>
|
<span>{{ top.name }}</span>
|
||||||
<span>{{ top.count }}</span>
|
<span>{{ top.count }}</span>
|
||||||
</li>
|
</li>
|
||||||
@@ -61,7 +65,7 @@
|
|||||||
<h3>{{ $t('train-stats.top-vehicles') }}</h3>
|
<h3>{{ $t('train-stats.top-vehicles') }}</h3>
|
||||||
|
|
||||||
<transition-group tag="ul" name="stats-anim">
|
<transition-group tag="ul" name="stats-anim">
|
||||||
<li class="badge" v-for="top in stats.topVehicles" :key="top.name">
|
<li class="badge stat-badge" v-for="top in stats.topVehicles" :key="top.name">
|
||||||
<span>{{ top.name }}</span>
|
<span>{{ top.name }}</span>
|
||||||
<span>{{ top.count }}</span>
|
<span>{{ top.count }}</span>
|
||||||
</li>
|
</li>
|
||||||
@@ -76,7 +80,7 @@
|
|||||||
<h3>{{ $t('train-stats.top-units') }}</h3>
|
<h3>{{ $t('train-stats.top-units') }}</h3>
|
||||||
|
|
||||||
<transition-group tag="ul" name="stats-anim">
|
<transition-group tag="ul" name="stats-anim">
|
||||||
<li class="badge" v-for="top in stats.topUnits.slice(0, 7)" :key="top.name">
|
<li class="badge stat-badge" v-for="top in stats.topUnits.slice(0, 7)" :key="top.name">
|
||||||
<span>{{ top.name }}</span>
|
<span>{{ top.name }}</span>
|
||||||
<span>{{ top.count }}</span>
|
<span>{{ top.count }}</span>
|
||||||
</li>
|
</li>
|
||||||
@@ -95,6 +99,8 @@
|
|||||||
<div class="no-data" v-else>
|
<div class="no-data" v-else>
|
||||||
{{ $t('train-stats.no-stats') }}
|
{{ $t('train-stats.no-stats') }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div tabindex="0" @focus="() => (showOptions = false)"></div>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</div>
|
</div>
|
||||||
@@ -236,43 +242,12 @@ h3 {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
|
|
||||||
// @include smallScreen {
|
|
||||||
// justify-content: center;
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
.badge {
|
|
||||||
margin: 0;
|
|
||||||
|
|
||||||
& > span:first-child {
|
|
||||||
background-color: $accentCol;
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown_wrapper {
|
.dropdown_wrapper {
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stats-anim {
|
|
||||||
&-move,
|
|
||||||
&-enter-active,
|
|
||||||
&-leave-active {
|
|
||||||
transition: all 250ms ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-enter-from,
|
|
||||||
&-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateX(5px);
|
|
||||||
}
|
|
||||||
|
|
||||||
&-leave-active {
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include smallScreen {
|
@include smallScreen {
|
||||||
h1,
|
h1,
|
||||||
.no-data {
|
.no-data {
|
||||||
|
|||||||
@@ -17,11 +17,10 @@
|
|||||||
class="train-row"
|
class="train-row"
|
||||||
v-for="train in trains"
|
v-for="train in trains"
|
||||||
:key="train.id"
|
:key="train.id"
|
||||||
tabindex="0"
|
|
||||||
@click.stop="selectModalTrain(train.id, $event.currentTarget)"
|
|
||||||
@keydown.enter="selectModalTrain(train.id, $event.currentTarget)"
|
|
||||||
>
|
>
|
||||||
<TrainInfo :train="train" :extended="false" />
|
<router-link class="a-block" :to="train.driverRouteLocation">
|
||||||
|
<TrainInfo :train="train" :extended="false" />
|
||||||
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
</transition-group>
|
</transition-group>
|
||||||
</div>
|
</div>
|
||||||
@@ -30,7 +29,6 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, inject, PropType, Ref } from 'vue';
|
import { defineComponent, inject, PropType, Ref } from 'vue';
|
||||||
import modalTrainMixin from '../../mixins/modalTrainMixin';
|
|
||||||
import { useMainStore } from '../../store/mainStore';
|
import { useMainStore } from '../../store/mainStore';
|
||||||
import Loading from '../Global/Loading.vue';
|
import Loading from '../Global/Loading.vue';
|
||||||
import TrainInfo from './TrainInfo.vue';
|
import TrainInfo from './TrainInfo.vue';
|
||||||
@@ -47,8 +45,6 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [modalTrainMixin],
|
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
const store = useMainStore();
|
const store = useMainStore();
|
||||||
const apiStore = useApiStore();
|
const apiStore = useApiStore();
|
||||||
@@ -77,17 +73,6 @@ export default defineComponent({
|
|||||||
|
|
||||||
return Status.Data.Loaded;
|
return Status.Data.Loaded;
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
activated() {
|
|
||||||
const query = this.$route.query;
|
|
||||||
if (query.trainNo && query.driverName) {
|
|
||||||
this.searchedDriver = query.driverName.toString();
|
|
||||||
this.searchedTrain = query.trainNo.toString();
|
|
||||||
setTimeout(() => {
|
|
||||||
this.selectModalTrain(query.driverName! + query.trainNo!.toString());
|
|
||||||
}, 20);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@@ -97,10 +82,10 @@ export default defineComponent({
|
|||||||
@import '../../styles/animations.scss';
|
@import '../../styles/animations.scss';
|
||||||
|
|
||||||
.train-table {
|
.train-table {
|
||||||
position: relative;
|
height: calc(100vh - 11em);
|
||||||
|
min-height: 500px;
|
||||||
|
|
||||||
height: 90vh;
|
position: relative;
|
||||||
min-height: 550px;
|
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
@@ -118,7 +103,5 @@ li.train-row {
|
|||||||
background-color: var(--clr-secondary);
|
background-color: var(--clr-secondary);
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { StationRoutesInfo } from "../../typings/common";
|
||||||
|
|
||||||
export enum TrainFilterSection {
|
export enum TrainFilterSection {
|
||||||
TRAIN_TYPE = 'TRAIN_TYPE',
|
TRAIN_TYPE = 'TRAIN_TYPE',
|
||||||
TIMETABLE_TYPE = 'TIMETABLE_TYPE',
|
TIMETABLE_TYPE = 'TIMETABLE_TYPE',
|
||||||
@@ -10,12 +12,14 @@ export const enum TrainFilterId {
|
|||||||
withComments = 'withComments',
|
withComments = 'withComments',
|
||||||
|
|
||||||
twr = 'twr',
|
twr = 'twr',
|
||||||
skr = 'skr',
|
tn = 'tn',
|
||||||
|
pn = 'pn',
|
||||||
common = 'common',
|
common = 'common',
|
||||||
|
|
||||||
passenger = 'passenger',
|
passenger = 'passenger',
|
||||||
freight = 'freight',
|
freight = 'freight',
|
||||||
other = 'other',
|
other = 'other',
|
||||||
|
|
||||||
noTimetable = 'noTimetable',
|
noTimetable = 'noTimetable',
|
||||||
withTimetable = 'withTimetable'
|
withTimetable = 'withTimetable'
|
||||||
}
|
}
|
||||||
@@ -38,7 +42,12 @@ export const trainFilters: TrainFilter[] = [
|
|||||||
isActive: true
|
isActive: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: TrainFilterId.skr,
|
id: TrainFilterId.tn,
|
||||||
|
section: TrainFilterSection.TRAIN_TYPE,
|
||||||
|
isActive: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: TrainFilterId.pn,
|
||||||
section: TrainFilterSection.TRAIN_TYPE,
|
section: TrainFilterSection.TRAIN_TYPE,
|
||||||
isActive: true
|
isActive: true
|
||||||
},
|
},
|
||||||
@@ -117,3 +126,40 @@ export const sorterOptions: TrainSorter[] = [
|
|||||||
value: 'długość'
|
value: 'długość'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export interface TrainScheduleStop {
|
||||||
|
nameHtml: string;
|
||||||
|
nameRaw: string;
|
||||||
|
|
||||||
|
status: 'confirmed' | 'unconfirmed' | 'stopped';
|
||||||
|
type: string;
|
||||||
|
position: 'begin' | 'end' | 'en-route';
|
||||||
|
|
||||||
|
arrivalScheduled: number;
|
||||||
|
arrivalReal: number;
|
||||||
|
|
||||||
|
departureScheduled: number;
|
||||||
|
departureReal: number;
|
||||||
|
|
||||||
|
departureDelay: number;
|
||||||
|
arrivalDelay: number;
|
||||||
|
|
||||||
|
duration: number | null;
|
||||||
|
|
||||||
|
isActive: boolean;
|
||||||
|
isLastConfirmed: boolean;
|
||||||
|
isSBL: boolean;
|
||||||
|
|
||||||
|
sceneryName: string | null;
|
||||||
|
distance: number;
|
||||||
|
|
||||||
|
arrivalLine: string | null;
|
||||||
|
departureLine: string | null;
|
||||||
|
|
||||||
|
arrivalLineInfo?: StationRoutesInfo;
|
||||||
|
departureLineInfo?: StationRoutesInfo;
|
||||||
|
|
||||||
|
isExternal: boolean;
|
||||||
|
|
||||||
|
comments: string | null;
|
||||||
|
}
|
||||||
@@ -1,369 +1,4 @@
|
|||||||
{
|
{
|
||||||
"optionSections": [
|
|
||||||
"status",
|
|
||||||
"timetables",
|
|
||||||
"reality",
|
|
||||||
"package-access",
|
|
||||||
"station-type",
|
|
||||||
"access",
|
|
||||||
"control",
|
|
||||||
"blockades",
|
|
||||||
"signals",
|
|
||||||
"addons"
|
|
||||||
],
|
|
||||||
|
|
||||||
"options": [
|
|
||||||
{
|
|
||||||
"id": "real",
|
|
||||||
"name": "real",
|
|
||||||
"section": "reality",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "fictional",
|
|
||||||
"name": "fictional",
|
|
||||||
"section": "reality",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "default",
|
|
||||||
"name": "default",
|
|
||||||
"section": "package-access",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "not-default",
|
|
||||||
"name": "notDefault",
|
|
||||||
"section": "package-access",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "non-public",
|
|
||||||
"name": "nonPublic",
|
|
||||||
"section": "access",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "unavailable",
|
|
||||||
"name": "unavailable",
|
|
||||||
"section": "access",
|
|
||||||
"value": false,
|
|
||||||
"defaultValue": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "abandoned",
|
|
||||||
"name": "abandoned",
|
|
||||||
"section": "access",
|
|
||||||
"value": false,
|
|
||||||
"defaultValue": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "junction",
|
|
||||||
"name": "junction",
|
|
||||||
"section": "station-type",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "nonJunction",
|
|
||||||
"name": "nonJunction",
|
|
||||||
"section": "station-type",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "SPK",
|
|
||||||
"name": "SPK",
|
|
||||||
"section": "control",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "SCS",
|
|
||||||
"name": "SCS",
|
|
||||||
"section": "control",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "SPE",
|
|
||||||
"name": "SPE",
|
|
||||||
"section": "control",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"id": "SPK-M",
|
|
||||||
"name": "mechaniczne+SPK",
|
|
||||||
"section": "control",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "SCS-M",
|
|
||||||
"name": "mechaniczne+SCS",
|
|
||||||
"section": "control",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "mechanical",
|
|
||||||
"name": "mechaniczne",
|
|
||||||
"section": "control",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "SPK-R",
|
|
||||||
"name": "ręczne+SPK",
|
|
||||||
"section": "control",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "SCS-R",
|
|
||||||
"name": "ręczne+SCS",
|
|
||||||
"section": "control",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "manual",
|
|
||||||
"name": "ręczne",
|
|
||||||
"section": "control",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "SUP",
|
|
||||||
"name": "SUP",
|
|
||||||
"section": "addons",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "noSUP",
|
|
||||||
"name": "noSUP",
|
|
||||||
"section": "addons",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "ASDEK",
|
|
||||||
"name": "ASDEK",
|
|
||||||
"section": "addons",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "noASDEK",
|
|
||||||
"name": "noASDEK",
|
|
||||||
"section": "addons",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "SBL",
|
|
||||||
"name": "SBL",
|
|
||||||
"section": "blockades",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "PBL",
|
|
||||||
"name": "PBL",
|
|
||||||
"section": "blockades",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "modern",
|
|
||||||
"name": "współczesna",
|
|
||||||
"section": "signals",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "semaphores",
|
|
||||||
"name": "kształtowa",
|
|
||||||
"section": "signals",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "mixed",
|
|
||||||
"name": "mieszana",
|
|
||||||
"section": "signals",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "historical",
|
|
||||||
"name": "historyczna",
|
|
||||||
"section": "signals",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"id": "free",
|
|
||||||
"name": "free",
|
|
||||||
|
|
||||||
"section": "status",
|
|
||||||
"value": false,
|
|
||||||
"defaultValue": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "occupied",
|
|
||||||
"name": "occupied",
|
|
||||||
|
|
||||||
"section": "status",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "endingStatus",
|
|
||||||
"name": "endingStatus",
|
|
||||||
|
|
||||||
"section": "status",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "afkStatus",
|
|
||||||
"name": "afkStatus",
|
|
||||||
|
|
||||||
"section": "status",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "noSpaceStatus",
|
|
||||||
"name": "noSpaceStatus",
|
|
||||||
|
|
||||||
"section": "status",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "unavailableStatus",
|
|
||||||
"name": "unavailableStatus",
|
|
||||||
|
|
||||||
"section": "status",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "withActiveTimetables",
|
|
||||||
"name": "withActiveTimetables",
|
|
||||||
"section": "timetables",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "withoutActiveTimetables",
|
|
||||||
"name": "withoutActiveTimetables",
|
|
||||||
"section": "timetables",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"sliders": [
|
|
||||||
{
|
|
||||||
"id": "min-lvl",
|
|
||||||
"name": "minLevel",
|
|
||||||
"minRange": 0,
|
|
||||||
"maxRange": 20,
|
|
||||||
"step": 1,
|
|
||||||
"value": 0,
|
|
||||||
"defaultValue": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "max-lvl",
|
|
||||||
"name": "maxLevel",
|
|
||||||
"minRange": 0,
|
|
||||||
"maxRange": 20,
|
|
||||||
"step": 1,
|
|
||||||
"value": 20,
|
|
||||||
"defaultValue": 20
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "min-vmax",
|
|
||||||
"name": "minVmax",
|
|
||||||
"minRange": 0,
|
|
||||||
"maxRange": 200,
|
|
||||||
"step": 10,
|
|
||||||
"value": 0,
|
|
||||||
"defaultValue": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "max-vmax",
|
|
||||||
"name": "maxVmax",
|
|
||||||
"minRange": 0,
|
|
||||||
"maxRange": 200,
|
|
||||||
"step": 10,
|
|
||||||
"value": 200,
|
|
||||||
"defaultValue": 200
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "routes-1t-cat",
|
|
||||||
"name": "minOneWayCatenary",
|
|
||||||
"minRange": 0,
|
|
||||||
"maxRange": 5,
|
|
||||||
"step": 1,
|
|
||||||
"value": 0,
|
|
||||||
"defaultValue": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "routes-1t-other",
|
|
||||||
"name": "minOneWay",
|
|
||||||
"minRange": 0,
|
|
||||||
"maxRange": 5,
|
|
||||||
"step": 1,
|
|
||||||
"value": 0,
|
|
||||||
"defaultValue": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "routes-2t-cat",
|
|
||||||
"name": "minTwoWayCatenary",
|
|
||||||
"minRange": 0,
|
|
||||||
"maxRange": 5,
|
|
||||||
"step": 1,
|
|
||||||
"value": 0,
|
|
||||||
"defaultValue": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "routes-2t-other",
|
|
||||||
"name": "minTwoWay",
|
|
||||||
"minRange": 0,
|
|
||||||
"maxRange": 5,
|
|
||||||
"step": 1,
|
|
||||||
"value": 0,
|
|
||||||
"defaultValue": 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"modes": [
|
|
||||||
{
|
|
||||||
"id": "include-selected",
|
|
||||||
"name": "include-selected",
|
|
||||||
"section": "mode",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "save",
|
|
||||||
"name": "save",
|
|
||||||
"section": "mode",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"regions": [
|
"regions": [
|
||||||
{
|
{
|
||||||
"id": "eu",
|
"id": "eu",
|
||||||
|
|||||||
@@ -20,11 +20,16 @@
|
|||||||
"dispatcher-message": "Dispatcher supporting the Stacjownik project!",
|
"dispatcher-message": "Dispatcher supporting the Stacjownik project!",
|
||||||
"driver-message": "Driver supporting the Stacjownik project!"
|
"driver-message": "Driver supporting the Stacjownik project!"
|
||||||
},
|
},
|
||||||
|
"warnings": {
|
||||||
|
"TWR": "Train with high risk cargo",
|
||||||
|
"SKR": "Train with exceeded gauge",
|
||||||
|
"PN": "Train with extra deliveries",
|
||||||
|
"TN": "Train with dangerous cargo",
|
||||||
|
"header-title": "Freight notes:"
|
||||||
|
},
|
||||||
"general": {
|
"general": {
|
||||||
"and": " and ",
|
"and": " and ",
|
||||||
"refresh": "REFRESH",
|
"refresh": "REFRESH"
|
||||||
"TWR": "High risk freight train",
|
|
||||||
"SKR": "Train with exceeded gauge"
|
|
||||||
},
|
},
|
||||||
"update": {
|
"update": {
|
||||||
"title": "Stacjownik update!",
|
"title": "Stacjownik update!",
|
||||||
@@ -48,7 +53,50 @@
|
|||||||
"footer": {
|
"footer": {
|
||||||
"discord": "Stacjownik Discord server"
|
"discord": "Stacjownik Discord server"
|
||||||
},
|
},
|
||||||
|
"categories": {
|
||||||
|
"EI": "domestic express",
|
||||||
|
"EC": "international express",
|
||||||
|
"EN": "domestic night express",
|
||||||
|
|
||||||
|
"MP": "intervoivodeship bullet",
|
||||||
|
"MO": "intervoivodeship regio",
|
||||||
|
"MM": "international bullet",
|
||||||
|
"MH": "intervoivodeship night bullet",
|
||||||
|
|
||||||
|
"RP": "voivodeship bullet",
|
||||||
|
"RM": "international voivodeship regio",
|
||||||
|
"RO": "voivodeship regio",
|
||||||
|
"RA": "voivodeship regio (urban)",
|
||||||
|
|
||||||
|
"PW": "empty passenger",
|
||||||
|
"PX": "empty passenger test drive",
|
||||||
|
|
||||||
|
"TC": "international freight (intermodal)",
|
||||||
|
"TG": "international freight (organized cargo)",
|
||||||
|
"TR": "international freight (unorganized cargo)",
|
||||||
|
"TD": "domestic freight (intermodal)",
|
||||||
|
"TM": "domestic freight (organized cargo)",
|
||||||
|
"TN": "domestic freight (unorganized cargo)",
|
||||||
|
"TK": "freight (for stations & sidings)",
|
||||||
|
"TS": "empty freight test drive",
|
||||||
|
"TH": "locomotive rolling stock (over 3 vehicles)",
|
||||||
|
|
||||||
|
"LT": "freight locomotive only",
|
||||||
|
"LP": "passenger locomotive only",
|
||||||
|
"LS": "shunting locomotive only",
|
||||||
|
"LZ": "shunting locomotive only",
|
||||||
|
|
||||||
|
"ZN": "inspection / diagnostic type",
|
||||||
|
"ZU": "other maintenance type",
|
||||||
|
"ZG": "emergency (deprecated)",
|
||||||
|
|
||||||
|
"AP": "voivodeship regio (deprecated)",
|
||||||
|
|
||||||
|
"E": "electric loco",
|
||||||
|
"J": "EMU",
|
||||||
|
"S": "diesel loco",
|
||||||
|
"M": "DMU"
|
||||||
|
},
|
||||||
"vehicle-preview": {
|
"vehicle-preview": {
|
||||||
"loading": "Loading preview...",
|
"loading": "Loading preview...",
|
||||||
"error": "Oops! The vehicle preview seems to be missing! :/"
|
"error": "Oops! The vehicle preview seems to be missing! :/"
|
||||||
@@ -112,8 +160,8 @@
|
|||||||
"filters": "FILTERS",
|
"filters": "FILTERS",
|
||||||
"donate": "DONATE",
|
"donate": "DONATE",
|
||||||
|
|
||||||
"search-button": "Search",
|
"search-button": "SEARCH",
|
||||||
"reset-button": "Reset",
|
"reset-button": "RESET",
|
||||||
|
|
||||||
"sort-title": "SORT BY:",
|
"sort-title": "SORT BY:",
|
||||||
"filter-title": "FILTER BY:",
|
"filter-title": "FILTER BY:",
|
||||||
@@ -123,9 +171,11 @@
|
|||||||
"search-train": "Train no.",
|
"search-train": "Train no.",
|
||||||
"search-driver": "Driver name",
|
"search-driver": "Driver name",
|
||||||
"search-dispatcher": "Dispatcher name",
|
"search-dispatcher": "Dispatcher name",
|
||||||
"search-station": "Scenery name",
|
"search-station": "Scenery name / #",
|
||||||
"search-author": "Timetable author name",
|
"search-author": "Timetable author name",
|
||||||
"search-issuedFrom": "Origin scenery name",
|
"search-issuedFrom": "Issuing scenery name",
|
||||||
|
"search-via": "Via scenery name",
|
||||||
|
"search-terminatingAt": "Terminating scenery name",
|
||||||
"search-timetables-date": "Timetable date (UTC+2 / CEST)",
|
"search-timetables-date": "Timetable date (UTC+2 / CEST)",
|
||||||
"search-dispatchers-date": "Service date (UTC+2 / CEST)",
|
"search-dispatchers-date": "Service date (UTC+2 / CEST)",
|
||||||
"search-date": "Date (UTC+2 / CEST)",
|
"search-date": "Date (UTC+2 / CEST)",
|
||||||
@@ -143,15 +193,17 @@
|
|||||||
"sort-beginDate": "date",
|
"sort-beginDate": "date",
|
||||||
"sort-timetableId": "timetable ID",
|
"sort-timetableId": "timetable ID",
|
||||||
"sort-timestampFrom": "date",
|
"sort-timestampFrom": "date",
|
||||||
"sort-duration": "duration",
|
"sort-currentDuration": "duration",
|
||||||
|
|
||||||
"filter-noComments": "NO COMMENTS",
|
"filter-noComments": "NO COMMENTS",
|
||||||
"filter-withComments": "COMMENTS",
|
"filter-withComments": "COMMENTS",
|
||||||
"filter-twr": "HIGH RISK CARGO",
|
"filter-twr": "TWR",
|
||||||
"filter-skr": "EXCEEDED GAUGE",
|
"filter-skr": "SKR",
|
||||||
|
"filter-tn": "TN",
|
||||||
|
"filter-pn": "PN",
|
||||||
"filter-twr-skr": "BOTH TYPES",
|
"filter-twr-skr": "BOTH TYPES",
|
||||||
"filter-all-specials": "ALL",
|
"filter-all-specials": "ALL",
|
||||||
"filter-common": "NO WARNINGS",
|
"filter-common": "COMMON",
|
||||||
"filter-passenger": "PASSENGER",
|
"filter-passenger": "PASSENGER",
|
||||||
"filter-freight": "FREIGHT",
|
"filter-freight": "FREIGHT",
|
||||||
"filter-other": "OTHER",
|
"filter-other": "OTHER",
|
||||||
@@ -250,9 +302,11 @@
|
|||||||
"minTwoWay": "MIN. OTHER DOUBLE TRACK ROUTES"
|
"minTwoWay": "MIN. OTHER DOUBLE TRACK ROUTES"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"sceneries-search": "SCENERY SEARCH:",
|
||||||
|
"sceneries-placeholder": "Enter scenery name...",
|
||||||
"authors-search": "SEARCH BY AUTHOR NAME (other filters apply):",
|
"authors-search": "SEARCH BY AUTHOR NAME (other filters apply):",
|
||||||
"authors-placeholder": "Enter the author nickname...",
|
"authors-placeholder": "Enter the author nickname...",
|
||||||
"authors-button-title": "Search",
|
"search-button-title": "SEARCH",
|
||||||
|
|
||||||
"minimum-hours-title": "SHOW ONLY SCENERIES UNTIL:",
|
"minimum-hours-title": "SHOW ONLY SCENERIES UNTIL:",
|
||||||
"now": "NOW",
|
"now": "NOW",
|
||||||
@@ -329,6 +383,12 @@
|
|||||||
"current-signal": "at signal",
|
"current-signal": "at signal",
|
||||||
"current-track": "on track",
|
"current-track": "on track",
|
||||||
|
|
||||||
|
"vmax-tooltip": "Maximum train speed based on rolling stock vehicles - braked weight is not included",
|
||||||
|
|
||||||
|
"catenary-tooltip": "Electrified route",
|
||||||
|
"no-catenary-tooltip": "Non-electrified route",
|
||||||
|
"sbl-tooltip": "Route with SBL\n(automatic block signalling)",
|
||||||
|
|
||||||
"delayed": "Delayed: ",
|
"delayed": "Delayed: ",
|
||||||
"preponed": "Ahead of schedule: ",
|
"preponed": "Ahead of schedule: ",
|
||||||
"on-time": "On time",
|
"on-time": "On time",
|
||||||
@@ -355,7 +415,15 @@
|
|||||||
"scenery-offline": "Offline ride",
|
"scenery-offline": "Offline ride",
|
||||||
"timeout": "An error occured while trying to refresh SWDR timetable data!",
|
"timeout": "An error occured while trying to refresh SWDR timetable data!",
|
||||||
|
|
||||||
"journal-button": "DRIVER'S JOURNAL"
|
"driver-journal-link": "DRIVER JOURNAL",
|
||||||
|
"driver-return-link": "GO BACK",
|
||||||
|
|
||||||
|
"driver-not-found-header": "Train not found! :/",
|
||||||
|
"driver-not-found-desc-1": "This train has already been terminated, changed its number or is offline.",
|
||||||
|
"driver-not-found-desc-2": "You can browse timetable history in the",
|
||||||
|
"driver-not-found-journal": "TIMETABLES JOURNAL",
|
||||||
|
"driver-not-found-others": "Player {driver} is online as:",
|
||||||
|
"driver-not-found-return": "GO BACK TO THE MAIN SITE"
|
||||||
},
|
},
|
||||||
"train-stats": {
|
"train-stats": {
|
||||||
"stats-button": "STATISTICS",
|
"stats-button": "STATISTICS",
|
||||||
@@ -383,6 +451,7 @@
|
|||||||
|
|
||||||
"no-further-data": "No further data for current parameters",
|
"no-further-data": "No further data for current parameters",
|
||||||
"loading-further-data": "Loading...",
|
"loading-further-data": "Loading...",
|
||||||
|
|
||||||
|
|
||||||
"route-length": "Route length:",
|
"route-length": "Route length:",
|
||||||
"station-count": "Stations:",
|
"station-count": "Stations:",
|
||||||
@@ -400,11 +469,16 @@
|
|||||||
"minutes": "{value} min | {value} mins",
|
"minutes": "{value} min | {value} mins",
|
||||||
"seconds": "{value} s",
|
"seconds": "{value} s",
|
||||||
|
|
||||||
"stock-info": "DETAILS",
|
"entry-details": "DETAILS",
|
||||||
|
"no-entry-details": "NO DETAILS AVAILABLE",
|
||||||
|
|
||||||
"stock-length": "Length",
|
"stock-length": "Length",
|
||||||
"stock-mass": "Mass",
|
"stock-mass": "Mass",
|
||||||
"stock-max-speed": "Max. speed",
|
"stock-max-speed": "Max. speed",
|
||||||
|
|
||||||
|
"stock-dangers": "ADDITIONAL NOTES",
|
||||||
|
"stock-preview": "STOCK PREVIEW",
|
||||||
|
|
||||||
"load-data": "Load further data...",
|
"load-data": "Load further data...",
|
||||||
|
|
||||||
"last-seen-at": "Last seen at",
|
"last-seen-at": "Last seen at",
|
||||||
@@ -491,28 +565,23 @@
|
|||||||
"option-timetables-history": "Timetables history PL1",
|
"option-timetables-history": "Timetables history PL1",
|
||||||
"option-dispatchers-history": "Dispatchers history PL1",
|
"option-dispatchers-history": "Dispatchers history PL1",
|
||||||
|
|
||||||
"timetable-author-title": "Issued by",
|
"timetable-via": "ALL TIMETABLES",
|
||||||
"timetable-author-unknown": "Author unknown",
|
"timetable-issuedFrom": "BEGINS HERE",
|
||||||
|
"timetable-terminatingAt": "TERMINATES HERE",
|
||||||
|
|
||||||
"timetables-history-id": "ID",
|
"timetable-issued-date": "Issued",
|
||||||
"timetables-history-number": "Number",
|
"timetable-issued-by": " by:",
|
||||||
"timetables-history-route": "Route",
|
"timetable-issued-for": " for driver:",
|
||||||
"timetables-history-driver": "Driver",
|
|
||||||
"timetables-history-author": "TT author",
|
|
||||||
"timetables-history-date": "Date",
|
|
||||||
|
|
||||||
"dispatchers-history-hash": "Hash",
|
"dispatcher-rate": "Rate:",
|
||||||
"dispatchers-history-dispatcher": "Dispatcher",
|
"dispatcher-status-changes": "Status changes:",
|
||||||
"dispatchers-history-level": "Level",
|
|
||||||
"dispatchers-history-rate": "Rate",
|
|
||||||
"dispatchers-history-date": "Service date",
|
|
||||||
|
|
||||||
"req-level": "all dispatcher levels | dispatcher level {lvl} required | dispatcher level {lvl} required",
|
"req-level": "all dispatcher levels | dispatcher level {lvl} required | dispatcher level {lvl} required",
|
||||||
"history-list-empty": "No recorded scenery history!",
|
"history-list-empty": "No recorded scenery history!",
|
||||||
|
|
||||||
"forum-topic": "Official {name} forum topic",
|
"forum-topic": "Official {name} forum topic",
|
||||||
|
|
||||||
"pragotron-link": "Timetable pallet board (beta)",
|
"pragotron-link": "Timetable pallet board",
|
||||||
"tablice-link": "Timetable summary board (by Thundo)",
|
"tablice-link": "Timetable summary board (by Thundo)",
|
||||||
|
|
||||||
"bottom-info": "Show full history in the Journal tab"
|
"bottom-info": "Show full history in the Journal tab"
|
||||||
|
|||||||
@@ -20,11 +20,16 @@
|
|||||||
"dispatcher-message": "Dyżurny wspierający projekt Stacjownika!",
|
"dispatcher-message": "Dyżurny wspierający projekt Stacjownika!",
|
||||||
"driver-message": "Maszynista wspierający projekt Stacjownika!"
|
"driver-message": "Maszynista wspierający projekt Stacjownika!"
|
||||||
},
|
},
|
||||||
|
"warnings": {
|
||||||
|
"TWR": "Pociąg z towarami niebezpiecznie wysokiego ryzyka",
|
||||||
|
"SKR": "Pociąg z przekroczoną skrajnią",
|
||||||
|
"PN": "Pociąg z przesyłkami nadzwyczajnymi",
|
||||||
|
"TN": "Pociąg z towarami niebezpiecznymi",
|
||||||
|
"header-title": "Uwagi przewozowe:"
|
||||||
|
},
|
||||||
"general": {
|
"general": {
|
||||||
"and": " oraz ",
|
"and": " oraz ",
|
||||||
"refresh": "ODŚWIEŻ",
|
"refresh": "ODŚWIEŻ"
|
||||||
"TWR": "Towar niebezpieczny wysokiego ryzyka",
|
|
||||||
"SKR": "Przekroczona skrajnia"
|
|
||||||
},
|
},
|
||||||
"update": {
|
"update": {
|
||||||
"title": "Aktualizacja Stacjownika!",
|
"title": "Aktualizacja Stacjownika!",
|
||||||
@@ -45,6 +50,50 @@
|
|||||||
"footer": {
|
"footer": {
|
||||||
"discord": "Serwer Discord Stacjownika"
|
"discord": "Serwer Discord Stacjownika"
|
||||||
},
|
},
|
||||||
|
"categories": {
|
||||||
|
"EI": "ekspres krajowy",
|
||||||
|
"EC": "ekspres międzynarodowy",
|
||||||
|
"EN": "ekspres krajowy nocny",
|
||||||
|
|
||||||
|
"MP": "międzywojewódzki pospieszny",
|
||||||
|
"MO": "międzywojewódzki osobowy",
|
||||||
|
"MM": "międzynarodowy pospieszny",
|
||||||
|
"MH": "międzywojewódzki pospieszny (nocny)",
|
||||||
|
|
||||||
|
"RP": "wojewódzki pospieszny",
|
||||||
|
"RO": "wojewódzki osobowy",
|
||||||
|
"RM": "wojewódzki osobowy międzynarodowy",
|
||||||
|
"RA": "wojewódzki osobowy aglomeracyjny",
|
||||||
|
|
||||||
|
"PW": "pasażerski próżny - służbowy",
|
||||||
|
"PX": "pasażerski próżny próbny",
|
||||||
|
|
||||||
|
"TC": "towarowy międzynarodowy intermodalny",
|
||||||
|
"TG": "towarowy międzynarodowy masowy",
|
||||||
|
"TR": "towarowy międzynarodowy niemasowy",
|
||||||
|
"TD": "towarowy krajowy intermodalny",
|
||||||
|
"TM": "towarowy krajowy masowy",
|
||||||
|
"TN": "towarowy krajowy niemasowy",
|
||||||
|
"TK": "towarowy zdawczy",
|
||||||
|
"TS": "towarowy próżny próbny",
|
||||||
|
"TH": "skład lokomotyw (powyżej 3 pojazdów)",
|
||||||
|
|
||||||
|
"LT": "lokomotywa towarowa luzem",
|
||||||
|
"LP": "lokomotywa pasażerska luzem",
|
||||||
|
"LS": "lokomotywa manewrowa luzem",
|
||||||
|
"LZ": "lokomotywa dla poc. utrzymaniowo-naprawczych",
|
||||||
|
|
||||||
|
"ZN": "inspekcyjny / diagnostyczny",
|
||||||
|
"ZU": "inny utrzymaniowy",
|
||||||
|
"ZG": "ratunkowy (kat. wycofana)",
|
||||||
|
|
||||||
|
"AP": "wojewódzki osobowy (kat. wycofana)",
|
||||||
|
|
||||||
|
"E": "elektrowóz",
|
||||||
|
"J": "EZT",
|
||||||
|
"S": "spalinowóz",
|
||||||
|
"M": "SZT"
|
||||||
|
},
|
||||||
"vehicle-preview": {
|
"vehicle-preview": {
|
||||||
"loading": "Ładowanie podglądu...",
|
"loading": "Ładowanie podglądu...",
|
||||||
"error": "Ups! Nie znaleziono podglądu pojazdu! :/"
|
"error": "Ups! Nie znaleziono podglądu pojazdu! :/"
|
||||||
@@ -119,9 +168,11 @@
|
|||||||
"search-train": "Nr pociągu / #",
|
"search-train": "Nr pociągu / #",
|
||||||
"search-driver": "Nick maszynisty",
|
"search-driver": "Nick maszynisty",
|
||||||
"search-dispatcher": "Nick dyżurnego",
|
"search-dispatcher": "Nick dyżurnego",
|
||||||
"search-station": "Nazwa scenerii",
|
"search-station": "Nazwa scenerii / #",
|
||||||
"search-author": "Nick autora rozkładu jazdy",
|
"search-author": "Nick autora rozkładu jazdy",
|
||||||
"search-issuedFrom": "Sceneria początkowa",
|
"search-issuedFrom": "Sceneria początkowa",
|
||||||
|
"search-via": "Przez scenerię",
|
||||||
|
"search-terminatingAt": "Sceneria końcowa",
|
||||||
"search-timetables-date": "Data rozkładu jazdy (UTC+2 / CEST)",
|
"search-timetables-date": "Data rozkładu jazdy (UTC+2 / CEST)",
|
||||||
"search-dispatchers-date": "Data służby (UTC+2 / CEST)",
|
"search-dispatchers-date": "Data służby (UTC+2 / CEST)",
|
||||||
"search-date": "Data (UTC+2 / CEST)",
|
"search-date": "Data (UTC+2 / CEST)",
|
||||||
@@ -131,7 +182,7 @@
|
|||||||
"sort-beginDate": "data",
|
"sort-beginDate": "data",
|
||||||
"sort-timetableId": "ID rozkładu",
|
"sort-timetableId": "ID rozkładu",
|
||||||
"sort-timestampFrom": "data",
|
"sort-timestampFrom": "data",
|
||||||
"sort-duration": "czas dyżuru",
|
"sort-currentDuration": "czas dyżuru",
|
||||||
"sort-id": "id rozkładu",
|
"sort-id": "id rozkładu",
|
||||||
|
|
||||||
"sort-mass": "masa",
|
"sort-mass": "masa",
|
||||||
@@ -144,8 +195,10 @@
|
|||||||
|
|
||||||
"filter-withComments": "UWAGI EKSPLOATACYJNE",
|
"filter-withComments": "UWAGI EKSPLOATACYJNE",
|
||||||
"filter-noComments": "BEZ UWAG",
|
"filter-noComments": "BEZ UWAG",
|
||||||
"filter-twr": "WYS. RYZYKA",
|
"filter-twr": "TWR",
|
||||||
"filter-skr": "SKRAJNIA",
|
"filter-skr": "SKR",
|
||||||
|
"filter-tn": "TN",
|
||||||
|
"filter-pn": "PN",
|
||||||
"filter-twr-skr": "TWR/SKR",
|
"filter-twr-skr": "TWR/SKR",
|
||||||
"filter-all-statuses": "WSZYSTKIE",
|
"filter-all-statuses": "WSZYSTKIE",
|
||||||
"filter-common": "ZWYKŁE",
|
"filter-common": "ZWYKŁE",
|
||||||
@@ -247,9 +300,11 @@
|
|||||||
"minTwoWay": "SZLAKI DWUTOROWE NIEZELEKTR. (MINIMUM)"
|
"minTwoWay": "SZLAKI DWUTOROWE NIEZELEKTR. (MINIMUM)"
|
||||||
},
|
},
|
||||||
|
|
||||||
"authors-search": "SZUKAJ AUTORA (uwzględnia inne filtry):",
|
"sceneries-search": "WYSZUKAJ SCENERIĘ:",
|
||||||
|
"sceneries-placeholder": "Wpisz nazwę scenerii...",
|
||||||
|
"authors-search": "WYSZUKAJ AUTORA (uwzględnia inne filtry):",
|
||||||
"authors-placeholder": "Wpisz nick autora...",
|
"authors-placeholder": "Wpisz nick autora...",
|
||||||
"authors-button-title": "Szukaj",
|
"search-button-title": "SZUKAJ",
|
||||||
"minimum-hours-title": "POKAŻ TYLKO SCENERIE DOSTĘPNE MINIMUM DO:",
|
"minimum-hours-title": "POKAŻ TYLKO SCENERIE DOSTĘPNE MINIMUM DO:",
|
||||||
"now": "TERAZ",
|
"now": "TERAZ",
|
||||||
"hour": " godz.",
|
"hour": " godz.",
|
||||||
@@ -315,6 +370,12 @@
|
|||||||
"current-signal": "przy semaforze",
|
"current-signal": "przy semaforze",
|
||||||
"current-track": "na szlaku",
|
"current-track": "na szlaku",
|
||||||
|
|
||||||
|
"vmax-tooltip": "Maksymalna prędkość na podstawie pojazdów w składzie - nie bierze pod uwagę masy hamowania",
|
||||||
|
|
||||||
|
"catenary-tooltip": "Szlak zelektryfikowany",
|
||||||
|
"no-catenary-tooltip": "Szlak niezelektryfikowany",
|
||||||
|
"sbl-tooltip": "Szlak posiadający\nsamoczynną blokadę liniową",
|
||||||
|
|
||||||
"delayed": "Opóźniony: ",
|
"delayed": "Opóźniony: ",
|
||||||
"preponed": "Przed czasem: ",
|
"preponed": "Przed czasem: ",
|
||||||
"on-time": "Planowo",
|
"on-time": "Planowo",
|
||||||
@@ -340,7 +401,15 @@
|
|||||||
|
|
||||||
"timeout": "Wystąpił problem z aktualizacją rozkładów jazdy z SWDR",
|
"timeout": "Wystąpił problem z aktualizacją rozkładów jazdy z SWDR",
|
||||||
|
|
||||||
"journal-button": "DZIENNIK MASZYNISTY"
|
"driver-journal-link": "DZIENNIK MASZYNISTY",
|
||||||
|
"driver-return-link": "POWRÓT",
|
||||||
|
|
||||||
|
"driver-not-found-header": "Nie znaleziono pociągu! :/",
|
||||||
|
"driver-not-found-desc-1": "Ten pociąg prawdopodobnie zakończył już swój bieg, zmienił numer lub jest offline.",
|
||||||
|
"driver-not-found-desc-2": "Historię rozkładów jazdy możesz przejrzeć w",
|
||||||
|
"driver-not-found-journal": "DZIENNIKU RJ",
|
||||||
|
"driver-not-found-others": "Gracz {driver} jest online jako:",
|
||||||
|
"driver-not-found-return": "WRÓĆ NA STRONĘ GŁÓWNĄ"
|
||||||
},
|
},
|
||||||
"train-stats": {
|
"train-stats": {
|
||||||
"stats-button": "STATYSTYKI",
|
"stats-button": "STATYSTYKI",
|
||||||
@@ -384,11 +453,16 @@
|
|||||||
"timetable-abandoned": "PORZUCONY",
|
"timetable-abandoned": "PORZUCONY",
|
||||||
"timetable-online-button": "RJ ONLINE",
|
"timetable-online-button": "RJ ONLINE",
|
||||||
|
|
||||||
"stock-info": "SZCZEGÓŁY",
|
"entry-details": "SZCZEGÓŁY",
|
||||||
|
"no-entry-details": "BRAK DOSTĘPNYCH SZCZEGÓŁÓW",
|
||||||
|
|
||||||
"stock-length": "Długość",
|
"stock-length": "Długość",
|
||||||
"stock-mass": "Masa",
|
"stock-mass": "Masa",
|
||||||
"stock-max-speed": "Prędkość maks.",
|
"stock-max-speed": "Prędkość maks.",
|
||||||
|
|
||||||
|
"stock-dangers": "DODATKOWE UWAGI",
|
||||||
|
"stock-preview": "PODGLĄD SKŁADU",
|
||||||
|
|
||||||
"load-data": "Pobierz dalszą historię...",
|
"load-data": "Pobierz dalszą historię...",
|
||||||
|
|
||||||
"last-seen-at": "Ostatnio widziany na: ",
|
"last-seen-at": "Ostatnio widziany na: ",
|
||||||
@@ -474,28 +548,23 @@
|
|||||||
"option-timetables-history": "Historia rozkładów PL1",
|
"option-timetables-history": "Historia rozkładów PL1",
|
||||||
"option-dispatchers-history": "Historia dyżurów PL1",
|
"option-dispatchers-history": "Historia dyżurów PL1",
|
||||||
|
|
||||||
"timetable-author-title": "Wydany przez",
|
"timetable-via": "WSZYSTKIE RJ",
|
||||||
"timetable-author-unknown": "Autor nieznany",
|
"timetable-issuedFrom": "ROZPOCZYNA BIEG",
|
||||||
|
"timetable-terminatingAt": "KOŃCZY BIEG",
|
||||||
|
|
||||||
"timetables-history-id": "ID",
|
"timetable-issued-date": "Wystawiony",
|
||||||
"timetables-history-number": "Numer",
|
"timetable-issued-by": " przez:",
|
||||||
"timetables-history-route": "Trasa",
|
"timetable-issued-for": " dla maszynisty:",
|
||||||
"timetables-history-driver": "Maszynista",
|
|
||||||
"timetables-history-author": "Autor RJ",
|
|
||||||
"timetables-history-date": "Data",
|
|
||||||
|
|
||||||
"dispatchers-history-hash": "Hash",
|
"dispatcher-rate": "Ocena:",
|
||||||
"dispatchers-history-dispatcher": "Dyżurny",
|
"dispatcher-status-changes": "Zmiany statusów:",
|
||||||
"dispatchers-history-level": "Poziom",
|
|
||||||
"dispatchers-history-rate": "Ocena",
|
|
||||||
"dispatchers-history-date": "Data służby",
|
|
||||||
|
|
||||||
"req-level": "ogólnodostępna | minimum {lvl} poziom dyżurnego | minimum {lvl} poziom dyżurnego",
|
"req-level": "ogólnodostępna | minimum {lvl} poziom dyżurnego | minimum {lvl} poziom dyżurnego",
|
||||||
"history-list-empty": "Brak historii dla tej scenerii!",
|
"history-list-empty": "Brak historii dla tej scenerii!",
|
||||||
|
|
||||||
"forum-topic": "Oficjalny wątek scenerii {name}",
|
"forum-topic": "Oficjalny wątek scenerii {name}",
|
||||||
|
|
||||||
"pragotron-link": "Paletowa tablica informacyjna (beta)",
|
"pragotron-link": "Paletowa tablica informacyjna",
|
||||||
"tablice-link": "Tablica informacyjna zbiorcza (autorstwa Thundo)",
|
"tablice-link": "Tablica informacyjna zbiorcza (autorstwa Thundo)",
|
||||||
|
|
||||||
"bottom-info": "Pokaż pełną historię w zakładce Dziennika"
|
"bottom-info": "Pokaż pełną historię w zakładce Dziennika"
|
||||||
|
|||||||
@@ -45,8 +45,11 @@ function filterTrainList(
|
|||||||
case TrainFilterId.twr:
|
case TrainFilterId.twr:
|
||||||
return !train.timetableData?.TWR;
|
return !train.timetableData?.TWR;
|
||||||
|
|
||||||
case TrainFilterId.skr:
|
case TrainFilterId.pn:
|
||||||
return !train.timetableData?.SKR;
|
return !train.timetableData?.hasExtraDeliveries;
|
||||||
|
|
||||||
|
case TrainFilterId.tn:
|
||||||
|
return !train.timetableData?.hasDangerousCargo;
|
||||||
|
|
||||||
case TrainFilterId.common:
|
case TrainFilterId.common:
|
||||||
return train.timetableData?.SKR || train.timetableData?.TWR;
|
return train.timetableData?.SKR || train.timetableData?.TWR;
|
||||||
|
|||||||
@@ -57,6 +57,10 @@ export default defineComponent({
|
|||||||
: '';
|
: '';
|
||||||
},
|
},
|
||||||
|
|
||||||
|
dateStringToTimestamp(dateString?: string) {
|
||||||
|
return dateString ? new Date(dateString).getTime() : 0;
|
||||||
|
},
|
||||||
|
|
||||||
calculateDuration(timestampMs: number, showSeconds = false) {
|
calculateDuration(timestampMs: number, showSeconds = false) {
|
||||||
const secondsTotal = Math.floor(timestampMs / 1000);
|
const secondsTotal = Math.floor(timestampMs / 1000);
|
||||||
const minsTotal = Math.round(timestampMs / 60000);
|
const minsTotal = Math.round(timestampMs / 60000);
|
||||||
@@ -70,8 +74,8 @@ export default defineComponent({
|
|||||||
minsInHour
|
minsInHour
|
||||||
)}`
|
)}`
|
||||||
: showSeconds && secondsTotal <= 60
|
: showSeconds && secondsTotal <= 60
|
||||||
? this.$t('journal.seconds', { value: secondsTotal }, secondsTotal)
|
? this.$t('journal.seconds', { value: secondsTotal }, secondsTotal)
|
||||||
: this.$t('journal.minutes', { value: minsTotal }, minsTotal);
|
: this.$t('journal.minutes', { value: minsTotal }, minsTotal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
import { defineComponent } from 'vue';
|
|
||||||
import { useMainStore } from '../store/mainStore';
|
|
||||||
import { useTooltipStore } from '../store/tooltipStore';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
store: useMainStore(),
|
|
||||||
tooltipStore: useTooltipStore()
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
selectModalTrain(trainId: string, target?: EventTarget | null) {
|
|
||||||
this.store.chosenModalTrainId = trainId;
|
|
||||||
document.body.classList.add('no-scroll');
|
|
||||||
if (target) this.store.modalLastClickedTarget = target;
|
|
||||||
},
|
|
||||||
|
|
||||||
closeModal() {
|
|
||||||
this.store.chosenModalTrainId = undefined;
|
|
||||||
this.tooltipStore.hide();
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
(this.store.modalLastClickedTarget as any)?.focus();
|
|
||||||
document.body.classList.remove('no-scroll');
|
|
||||||
}, 150);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
methods: {
|
||||||
|
getCategoryExplanation(categoryCode: string) {
|
||||||
|
const categoryKey = categoryCode.slice(0, 2);
|
||||||
|
const vehicleTypeKey = categoryCode.slice(-1);
|
||||||
|
|
||||||
|
return `${this.$t('categories.' + categoryKey)}\n(${this.$t('categories.' + vehicleTypeKey)})`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -75,18 +75,18 @@ export default defineComponent({
|
|||||||
return positionString.charAt(0).toUpperCase() + positionString.slice(1);
|
return positionString.charAt(0).toUpperCase() + positionString.slice(1);
|
||||||
},
|
},
|
||||||
|
|
||||||
displayStopList(stops: TrainStop[]): string | undefined {
|
getTrainStopsHtml(stops: TrainStop[]): string {
|
||||||
if (!stops) return '';
|
if (!stops) return '';
|
||||||
|
|
||||||
return stops
|
return stops
|
||||||
.reduce((acc: string[], stop: TrainStop, i: number) => {
|
.reduce((acc: string[], stop: TrainStop, i: number) => {
|
||||||
if (stop.stopType.includes('ph') && !stop.stopNameRAW.includes('po.'))
|
if (stop.stopType.includes('ph'))
|
||||||
acc.push(
|
acc.push(
|
||||||
`<strong style='color:${stop.confirmed ? 'springgreen' : 'white'}'>${
|
`<strong style='color:${stop.confirmed ? 'springgreen' : 'white'}'>${
|
||||||
stop.stopName
|
stop.stopName
|
||||||
}</strong>`
|
}</strong>`
|
||||||
);
|
);
|
||||||
else if (i > 0 && i < stops.length - 1 && !/po\.|sbl/gi.test(stop.stopNameRAW))
|
else if (i > 0 && i < stops.length - 1 && !/(, po$|sbl)/gi.test(stop.stopNameRAW))
|
||||||
acc.push(
|
acc.push(
|
||||||
`<span style='color:${stop.confirmed ? 'springgreen' : 'lightgray'}'>${
|
`<span style='color:${stop.confirmed ? 'springgreen' : 'lightgray'}'>${
|
||||||
stop.stopName
|
stop.stopName
|
||||||
@@ -108,16 +108,19 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
currentDelay(stops: TrainStop[]) {
|
currentDelay(stops: TrainStop[]) {
|
||||||
const delay =
|
const lastConfirmedStop = stops.find(
|
||||||
stops.find(
|
(stop, i) =>
|
||||||
(stop, i) =>
|
(i == 0 && !stop.confirmed) ||
|
||||||
(i == 0 && !stop.confirmed) || (i > 0 && stops[i - 1].confirmed && !stop.confirmed)
|
(i > 0 && stops[i - 1].confirmed && !stop.confirmed) ||
|
||||||
)?.departureDelay || 0;
|
(stops[i + 1] == undefined && stop.confirmed)
|
||||||
|
);
|
||||||
|
|
||||||
if (delay > 0)
|
const lastDelay = lastConfirmedStop?.departureDelay ?? lastConfirmedStop?.arrivalDelay ?? 0;
|
||||||
return `<span style='color: salmon'>${this.$t('trains.delayed')} ${delay} min</span>`;
|
|
||||||
else if (delay < 0)
|
if (lastDelay > 0)
|
||||||
return `<span style='color: lightgreen'>${this.$t('trains.preponed')} ${delay} min</span>`;
|
return `<span style='color: salmon'>${this.$t('trains.delayed')} ${lastDelay} min</span>`;
|
||||||
|
else if (lastDelay < 0)
|
||||||
|
return `<span style='color: lightgreen'>${this.$t('trains.preponed')} ${lastDelay} min</span>`;
|
||||||
else return this.$t('trains.on-time');
|
else return this.$t('trains.on-time');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,15 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
region: route.query.region
|
region: route.query.region
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/driver',
|
||||||
|
name: 'DriverView',
|
||||||
|
component: () => import('../views/DriverView.vue'),
|
||||||
|
props: (route) => ({
|
||||||
|
trainId: route.query.trainId,
|
||||||
|
modalId: route.query.modalId
|
||||||
|
})
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/scenery',
|
path: '/scenery',
|
||||||
name: 'SceneryView',
|
name: 'SceneryView',
|
||||||
@@ -57,8 +66,13 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
scrollBehavior(to, from, savedPosition) {
|
scrollBehavior(to, from, savedPosition) {
|
||||||
if (to.name == 'SceneryView' && from.name !== to.name && from.query['view'] === undefined)
|
if (
|
||||||
return { el: `.app_main` };
|
(to.name == 'SceneryView' || to.name == 'DriverView') &&
|
||||||
|
from.name !== to.name &&
|
||||||
|
from.query['view'] === undefined &&
|
||||||
|
!savedPosition
|
||||||
|
)
|
||||||
|
return { el: `.app_main`, top: -15 };
|
||||||
|
|
||||||
if (savedPosition) return savedPosition;
|
if (savedPosition) return savedPosition;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,24 +4,22 @@ import { Status } from '../typings/common';
|
|||||||
import { StationJSONData } from './typings';
|
import { StationJSONData } from './typings';
|
||||||
import axios, { AxiosInstance } from 'axios';
|
import axios, { AxiosInstance } from 'axios';
|
||||||
|
|
||||||
export enum APIMode {
|
|
||||||
PRODUCTION = 0,
|
|
||||||
DEV = 1,
|
|
||||||
MOCK = 2
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useApiStore = defineStore('apiStore', {
|
export const useApiStore = defineStore('apiStore', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
dataStatuses: {
|
dataStatuses: {
|
||||||
connection: Status.Data.Loading,
|
connection: Status.Data.Loading,
|
||||||
sceneries: Status.Data.Loading
|
sceneries: Status.Data.Loading,
|
||||||
|
vehicles: Status.Data.Loading
|
||||||
},
|
},
|
||||||
|
|
||||||
activeData: undefined as API.ActiveData.Response | undefined,
|
activeData: undefined as API.ActiveData.Response | undefined,
|
||||||
|
vehiclesData: undefined as API.Vehicles.Response | undefined,
|
||||||
|
|
||||||
donatorsData: [] as API.Donators.Response,
|
donatorsData: [] as API.Donators.Response,
|
||||||
sceneryData: [] as StationJSONData[],
|
sceneryData: [] as StationJSONData[],
|
||||||
|
|
||||||
lastFetchData: new Date(),
|
nextUpdateTime: 0,
|
||||||
|
nextDataCheckTime: 0,
|
||||||
|
|
||||||
client: undefined as AxiosInstance | undefined,
|
client: undefined as AxiosInstance | undefined,
|
||||||
|
|
||||||
@@ -51,21 +49,37 @@ export const useApiStore = defineStore('apiStore', {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async connectToAPI() {
|
async connectToAPI() {
|
||||||
// Static data
|
window.requestAnimationFrame(this.updateTick);
|
||||||
this.fetchDonatorsData();
|
},
|
||||||
this.fetchStationsGeneralInfo();
|
|
||||||
|
updateTick(t: number) {
|
||||||
|
if (this.dataStatuses.connection == Status.Data.Offline) return;
|
||||||
|
|
||||||
|
// Static data refresh
|
||||||
|
if (t >= this.nextDataCheckTime) {
|
||||||
|
this.fetchDonatorsData();
|
||||||
|
this.fetchVehiclesInfo();
|
||||||
|
this.fetchStationsGeneralInfo();
|
||||||
|
|
||||||
|
this.nextDataCheckTime = t + 3600000;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Active data fefresh
|
||||||
|
if (t >= this.nextUpdateTime) {
|
||||||
|
this.fetchActiveData();
|
||||||
|
this.nextUpdateTime = t + 20000;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.requestAnimationFrame(this.updateTick);
|
||||||
},
|
},
|
||||||
|
|
||||||
async fetchActiveData() {
|
async fetchActiveData() {
|
||||||
if (!this.activeData) this.dataStatuses.connection = Status.Data.Loading;
|
if (!this.activeData) this.dataStatuses.connection = Status.Data.Loading;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log('Fetching active data at ' + new Date().toLocaleTimeString('pl-PL'));
|
|
||||||
|
|
||||||
const response = await this.client!.get<API.ActiveData.Response>('api/getActiveData');
|
const response = await this.client!.get<API.ActiveData.Response>('api/getActiveData');
|
||||||
|
|
||||||
this.activeData = response.data;
|
this.activeData = response.data;
|
||||||
this.lastFetchData = new Date();
|
|
||||||
this.dataStatuses.connection = Status.Data.Loaded;
|
this.dataStatuses.connection = Status.Data.Loaded;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.dataStatuses.connection = Status.Data.Error;
|
this.dataStatuses.connection = Status.Data.Error;
|
||||||
@@ -84,17 +98,29 @@ export const useApiStore = defineStore('apiStore', {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async fetchStationsGeneralInfo() {
|
async fetchStationsGeneralInfo() {
|
||||||
const sceneryData: StationJSONData[] = (
|
try {
|
||||||
await this.client!.get<StationJSONData[]>('api/getSceneries')
|
const sceneryData: StationJSONData[] = (
|
||||||
).data;
|
await this.client!.get<StationJSONData[]>(`api/getSceneries`)
|
||||||
|
).data;
|
||||||
|
|
||||||
if (!sceneryData) {
|
this.dataStatuses.sceneries = Status.Data.Loaded;
|
||||||
|
this.sceneryData = sceneryData;
|
||||||
|
} catch (error) {
|
||||||
this.dataStatuses.sceneries = Status.Data.Error;
|
this.dataStatuses.sceneries = Status.Data.Error;
|
||||||
return;
|
console.error('Ups! Wystąpił błąd podczas pobierania informacji o sceneriach:', error);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
this.dataStatuses.sceneries = Status.Data.Loaded;
|
async fetchVehiclesInfo() {
|
||||||
this.sceneryData = sceneryData;
|
try {
|
||||||
|
const response = await this.client!.get<API.Vehicles.Response>('api/getVehicles');
|
||||||
|
|
||||||
|
this.vehiclesData = response.data;
|
||||||
|
this.dataStatuses.vehicles = response.data ? Status.Data.Loaded : Status.Data.Warning;
|
||||||
|
} catch (error) {
|
||||||
|
this.dataStatuses.vehicles = Status.Data.Error;
|
||||||
|
console.error('Ups! Wystąpił błąd podczas pobierania informacji o pojazdach:', error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -36,17 +36,13 @@ export const useMainStore = defineStore('mainStore', {
|
|||||||
}) as MainStoreState,
|
}) as MainStoreState,
|
||||||
|
|
||||||
getters: {
|
getters: {
|
||||||
checkpointsTrains() {
|
|
||||||
return checkpointsTrains;
|
|
||||||
},
|
|
||||||
|
|
||||||
trainList(): Train[] {
|
trainList(): Train[] {
|
||||||
const apiStore = useApiStore();
|
const apiStore = useApiStore();
|
||||||
|
|
||||||
checkpointsTrains.clear();
|
checkpointsTrains.clear();
|
||||||
sceneriesTrains.clear();
|
sceneriesTrains.clear();
|
||||||
|
|
||||||
const x = (apiStore.activeData?.trains ?? [])
|
return (apiStore.activeData?.trains ?? [])
|
||||||
.filter((train) => train.timetable || train.online)
|
.filter((train) => train.timetable || train.online)
|
||||||
.map((train) => {
|
.map((train) => {
|
||||||
const stock = train.stockString.split(';');
|
const stock = train.stockString.split(';');
|
||||||
@@ -54,17 +50,9 @@ export const useMainStore = defineStore('mainStore', {
|
|||||||
|
|
||||||
const timetable = train.timetable;
|
const timetable = train.timetable;
|
||||||
|
|
||||||
const sceneryNames =
|
|
||||||
train.timetable?.sceneries?.map(
|
|
||||||
(sceneryHash) =>
|
|
||||||
apiStore.activeData?.activeSceneries?.find((st) => st.stationHash === sceneryHash)
|
|
||||||
?.stationName ??
|
|
||||||
apiStore.sceneryData.find((sd) => sd.hash === sceneryHash)?.name ??
|
|
||||||
sceneryHash
|
|
||||||
) ?? [];
|
|
||||||
|
|
||||||
const trainObj = {
|
const trainObj = {
|
||||||
id: train.id,
|
id: train.id,
|
||||||
|
modalId: `${train.driverName}${train.trainNo}`, // simplified id for train modal
|
||||||
|
|
||||||
trainNo: train.trainNo,
|
trainNo: train.trainNo,
|
||||||
mass: train.mass,
|
mass: train.mass,
|
||||||
@@ -89,17 +77,37 @@ export const useMainStore = defineStore('mainStore', {
|
|||||||
isSupporter: train.driverIsSupporter,
|
isSupporter: train.driverIsSupporter,
|
||||||
driverLevel: train.driverLevel,
|
driverLevel: train.driverLevel,
|
||||||
|
|
||||||
|
driverRouteLocation: {
|
||||||
|
name: 'DriverView',
|
||||||
|
query: {
|
||||||
|
trainId: train.id
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
timetableData: timetable
|
timetableData: timetable
|
||||||
? {
|
? {
|
||||||
timetableId: timetable.timetableId,
|
timetableId: timetable.timetableId,
|
||||||
SKR: timetable.SKR,
|
|
||||||
TWR: timetable.TWR,
|
|
||||||
route: timetable.route,
|
route: timetable.route,
|
||||||
category: timetable.category,
|
category: timetable.category,
|
||||||
followingStops: timetable.stopList,
|
followingStops: timetable.stopList,
|
||||||
routeDistance: timetable.stopList[timetable.stopList.length - 1].stopDistance,
|
routeDistance: timetable.stopList[timetable.stopList.length - 1].stopDistance,
|
||||||
sceneries: timetable.sceneries,
|
sceneries: timetable.sceneries,
|
||||||
sceneryNames: sceneryNames.reverse()
|
TWR: timetable.TWR,
|
||||||
|
SKR: timetable.SKR,
|
||||||
|
warningNotes: timetable.warningNotes,
|
||||||
|
hasDangerousCargo: timetable.hasDangerousCargo,
|
||||||
|
hasExtraDeliveries: timetable.hasExtraDeliveries,
|
||||||
|
|
||||||
|
timetablePath: timetable.path.split(';').map((pathElementString) => {
|
||||||
|
const [arrival, station, departure] = pathElementString.split(',');
|
||||||
|
|
||||||
|
return {
|
||||||
|
arrivalRouteExt: arrival,
|
||||||
|
departureRouteExt: departure,
|
||||||
|
stationName: station.split(' ').slice(0, -1).join(' '),
|
||||||
|
stationHash: station.split(' ').slice(-1).join(' ').replace('.sc', '')
|
||||||
|
};
|
||||||
|
})
|
||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
} as Train;
|
} as Train;
|
||||||
@@ -113,26 +121,42 @@ export const useMainStore = defineStore('mainStore', {
|
|||||||
} else sceneriesTrains.set(train.currentStationName, [trainObj]);
|
} else sceneriesTrains.set(train.currentStationName, [trainObj]);
|
||||||
|
|
||||||
// Checkpoints trains map
|
// Checkpoints trains map
|
||||||
timetable?.stopList.forEach((stop, i) => {
|
if (trainObj.timetableData) {
|
||||||
if (/strong|podg\.|pe\./.test(stop.stopName)) {
|
let currentSceneryIndex = 0;
|
||||||
const checkpointTrain: CheckpointTrain = {
|
const timetablePath = trainObj.timetableData.timetablePath;
|
||||||
train: trainObj,
|
|
||||||
checkpointStop: stop
|
|
||||||
};
|
|
||||||
|
|
||||||
if (checkpointsTrains.has(stop.stopNameRAW.toLowerCase())) {
|
trainObj.timetableData.followingStops.forEach((stop, i) => {
|
||||||
checkpointsTrains.set(stop.stopNameRAW.toLowerCase(), [
|
if (/strong|podg|pe/.test(stop.stopName)) {
|
||||||
...checkpointsTrains.get(stop.stopNameRAW.toLowerCase())!,
|
const checkpointTrain: CheckpointTrain = {
|
||||||
checkpointTrain
|
train: trainObj,
|
||||||
]);
|
checkpointStop: stop,
|
||||||
} else checkpointsTrains.set(stop.stopNameRAW.toLowerCase(), [checkpointTrain]);
|
|
||||||
}
|
previousSceneryElement:
|
||||||
});
|
currentSceneryIndex > 0 ? timetablePath[currentSceneryIndex - 1] : null,
|
||||||
|
|
||||||
|
nextSceneryElement:
|
||||||
|
currentSceneryIndex < timetablePath.length - 1
|
||||||
|
? timetablePath[currentSceneryIndex + 1]
|
||||||
|
: null,
|
||||||
|
|
||||||
|
timetablePathElement: timetablePath[currentSceneryIndex]
|
||||||
|
};
|
||||||
|
|
||||||
|
if (checkpointsTrains.has(stop.stopNameRAW.toLowerCase())) {
|
||||||
|
checkpointsTrains.set(stop.stopNameRAW.toLowerCase(), [
|
||||||
|
...checkpointsTrains.get(stop.stopNameRAW.toLowerCase())!,
|
||||||
|
checkpointTrain
|
||||||
|
]);
|
||||||
|
} else checkpointsTrains.set(stop.stopNameRAW.toLowerCase(), [checkpointTrain]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timetablePath[currentSceneryIndex].departureRouteExt == stop.departureLine)
|
||||||
|
currentSceneryIndex++;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return trainObj;
|
return trainObj;
|
||||||
});
|
});
|
||||||
|
|
||||||
return x;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// computed active sceneries
|
// computed active sceneries
|
||||||
@@ -143,16 +167,18 @@ export const useMainStore = defineStore('mainStore', {
|
|||||||
|
|
||||||
if (!apiStore.activeData?.activeSceneries) return [];
|
if (!apiStore.activeData?.activeSceneries) return [];
|
||||||
|
|
||||||
console.time('activeSceneryList');
|
|
||||||
const offlineActiveSceneries = this.trainList.reduce((acc, train) => {
|
const offlineActiveSceneries = this.trainList.reduce((acc, train) => {
|
||||||
if (!train.timetableData) return acc;
|
if (!train.timetableData) return acc;
|
||||||
|
|
||||||
train.timetableData.sceneryNames.forEach((name) => {
|
train.timetableData.timetablePath.forEach((p) => {
|
||||||
if (
|
if (
|
||||||
acc.findIndex((v) => v.name == name && v.region == train.region) != -1 ||
|
acc.findIndex(
|
||||||
|
(v) =>
|
||||||
|
(v.name == p.stationName || v.hash == p.stationHash) && v.region == train.region
|
||||||
|
) != -1 ||
|
||||||
apiStore.activeData?.activeSceneries?.findIndex(
|
apiStore.activeData?.activeSceneries?.findIndex(
|
||||||
(sc) =>
|
(sc) =>
|
||||||
sc.stationName === name &&
|
(sc.stationName == p.stationName || sc.stationHash == p.stationHash) &&
|
||||||
sc.region == train.region &&
|
sc.region == train.region &&
|
||||||
Date.now() - sc.lastSeen < 1000 * 60 * 2
|
Date.now() - sc.lastSeen < 1000 * 60 * 2
|
||||||
) != -1
|
) != -1
|
||||||
@@ -160,7 +186,7 @@ export const useMainStore = defineStore('mainStore', {
|
|||||||
return acc;
|
return acc;
|
||||||
|
|
||||||
acc.push({
|
acc.push({
|
||||||
name: name,
|
name: p.stationName,
|
||||||
hash: '',
|
hash: '',
|
||||||
region: train.region,
|
region: train.region,
|
||||||
maxUsers: 0,
|
maxUsers: 0,
|
||||||
@@ -238,9 +264,16 @@ export const useMainStore = defineStore('mainStore', {
|
|||||||
|
|
||||||
const station = this.stationList.find((s) => s.name === scenery.name);
|
const station = this.stationList.find((s) => s.name === scenery.name);
|
||||||
|
|
||||||
const checkpoints = [scenery.name];
|
let checkpointsSet: Set<string> = new Set();
|
||||||
if (station?.generalInfo?.checkpoints && station.generalInfo.checkpoints.length > 0)
|
|
||||||
checkpoints.push(...station.generalInfo.checkpoints.filter((cp) => cp !== station.name));
|
// Add checkpoints to active scenery data
|
||||||
|
checkpointsSet.add(scenery.name.toLowerCase());
|
||||||
|
|
||||||
|
station?.generalInfo?.checkpoints.forEach((cpName) => {
|
||||||
|
checkpointsSet.add(cpName.toLowerCase());
|
||||||
|
});
|
||||||
|
|
||||||
|
const checkpoints = Array.from(checkpointsSet);
|
||||||
|
|
||||||
scenery.stationTrains =
|
scenery.stationTrains =
|
||||||
sceneriesTrains.get(scenery.name)?.filter((sc) => sc.region == this.region.id) ?? [];
|
sceneriesTrains.get(scenery.name)?.filter((sc) => sc.region == this.region.id) ?? [];
|
||||||
@@ -251,8 +284,14 @@ export const useMainStore = defineStore('mainStore', {
|
|||||||
|
|
||||||
if (!scheduledTrains) return;
|
if (!scheduledTrains) return;
|
||||||
|
|
||||||
scheduledTrains.forEach(({ train, checkpointStop }) => {
|
scheduledTrains.forEach(({ train, checkpointStop, timetablePathElement, ...v }) => {
|
||||||
scenery.scheduledTrains.push({ train, checkpointStop });
|
if (
|
||||||
|
scenery.name != timetablePathElement.stationName &&
|
||||||
|
scenery.hash != timetablePathElement.stationHash
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
|
scenery.scheduledTrains.push({ train, checkpointStop, timetablePathElement, ...v });
|
||||||
|
|
||||||
if (uniqueTrainIds.includes(train.id) || train.region != this.region.id) return;
|
if (uniqueTrainIds.includes(train.id) || train.region != this.region.id) return;
|
||||||
|
|
||||||
@@ -266,8 +305,6 @@ export const useMainStore = defineStore('mainStore', {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
console.timeEnd('activeSceneryList');
|
|
||||||
|
|
||||||
return allActiveSceneries;
|
return allActiveSceneries;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
@import 'responsive.scss';
|
@import 'responsive.scss';
|
||||||
@import 'animations.scss';
|
@import 'animations.scss';
|
||||||
|
|
||||||
|
.journal-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5em;
|
||||||
|
text-align: left;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
.list_wrapper {
|
.list_wrapper {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
height: 90vh;
|
height: calc(100vh - 12.5em);
|
||||||
min-height: 550px;
|
min-height: 500px;
|
||||||
margin-top: 0.5em;
|
margin-top: 0.5em;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
@@ -12,7 +20,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.journal_wrapper {
|
.journal_wrapper {
|
||||||
max-width: 1500px;
|
max-width: var(--max-container-width);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
@@ -38,16 +46,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.journal_item {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.journal_item,
|
.journal_item,
|
||||||
.journal_warning {
|
.journal_warning {
|
||||||
background-color: #1a1a1a;
|
background-color: #1a1a1a;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
margin-bottom: 1em;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.journal_top-bar {
|
.journal_top-bar {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
@import 'variables.scss';
|
@import 'variables.scss';
|
||||||
@import 'responsive.scss';
|
@import 'responsive.scss';
|
||||||
|
@import 'badge.scss';
|
||||||
|
|
||||||
.stats-tab {
|
.stats-tab {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -7,7 +8,6 @@
|
|||||||
z-index: 99;
|
z-index: 99;
|
||||||
|
|
||||||
transform: translateY(1em);
|
transform: translateY(1em);
|
||||||
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
background-color: #1a1a1a;
|
background-color: #1a1a1a;
|
||||||
@@ -29,26 +29,10 @@ hr.section-separator {
|
|||||||
.info-stats {
|
.info-stats {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: center;
|
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-badge {
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
span {
|
|
||||||
background-color: $accentCol;
|
|
||||||
color: black;
|
|
||||||
font-weight: bold;
|
|
||||||
padding: 0.2em 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
span:first-child {
|
|
||||||
background-color: #333;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include smallScreen {
|
@include smallScreen {
|
||||||
.journal-stats {
|
.journal-stats {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|||||||
@@ -41,11 +41,11 @@ $animType: ease-in-out;
|
|||||||
}
|
}
|
||||||
|
|
||||||
&-enter-active {
|
&-enter-active {
|
||||||
transition: all $animDuration ease-out;
|
transition: all $animDuration ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-leave-active {
|
&-leave-active {
|
||||||
transition: all $animDuration ease-out;
|
transition: all $animDuration ease-in-out;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
@import 'variables.scss';
|
||||||
|
@import 'responsive.scss';
|
||||||
|
|
||||||
.badge {
|
.badge {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
|
||||||
@@ -76,20 +79,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.train-badge {
|
.train-badge {
|
||||||
display: flex;
|
display: inline-block;
|
||||||
align-items: center;
|
gap: 0.3em;
|
||||||
gap: 0.5em;
|
|
||||||
|
|
||||||
padding: 0.1em 0.3em;
|
padding: 0.1em 0.3em;
|
||||||
border-radius: 0.2em;
|
border-radius: 0.2em;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
user-select: none;
|
||||||
font-size: 0.9em;
|
|
||||||
|
|
||||||
&.twr {
|
&.twr {
|
||||||
background-color: var(--clr-twr);
|
background-color: var(--clr-twr);
|
||||||
box-shadow: 0 0 5px 1px var(--clr-twr);
|
box-shadow: 0 0 5px 1px var(--clr-twr);
|
||||||
color: black;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.skr {
|
&.skr {
|
||||||
@@ -97,6 +97,17 @@
|
|||||||
box-shadow: 0 0 5px 1px var(--clr-skr);
|
box-shadow: 0 0 5px 1px var(--clr-skr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.tn {
|
||||||
|
background-color: var(--clr-tn);
|
||||||
|
box-shadow: 0 0 5px 1px var(--clr-tn);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.pn {
|
||||||
|
background-color: var(--clr-pn);
|
||||||
|
box-shadow: 0 0 5px 1px var(--clr-pn);
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
&.offline {
|
&.offline {
|
||||||
background-color: #be3728;
|
background-color: #be3728;
|
||||||
}
|
}
|
||||||
@@ -114,3 +125,13 @@
|
|||||||
background-color: #007599;
|
background-color: #007599;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stat-badge {
|
||||||
|
margin: 0;
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
& > span:first-child {
|
||||||
|
background-color: $accentCol;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,7 +13,9 @@
|
|||||||
--clr-accent2: #ff3d5d;
|
--clr-accent2: #ff3d5d;
|
||||||
|
|
||||||
--clr-skr: #ff5100;
|
--clr-skr: #ff5100;
|
||||||
--clr-twr: #ffbb00;
|
--clr-twr: #ee503e;
|
||||||
|
--clr-tn: #cb4dcf;
|
||||||
|
--clr-pn: #ffd000;
|
||||||
|
|
||||||
--clr-error: #fa3636;
|
--clr-error: #fa3636;
|
||||||
--clr-warning: #c59429;
|
--clr-warning: #c59429;
|
||||||
@@ -124,10 +126,12 @@ input {
|
|||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
display: inline-block;
|
|
||||||
|
|
||||||
color: white;
|
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:not(.a-block):not(.a-button):not(.a-row) {
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
transition: color 0.3s;
|
transition: color 0.3s;
|
||||||
|
|
||||||
@@ -138,6 +142,14 @@ a {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.a-block {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.a-row {
|
||||||
|
display: table-row;
|
||||||
|
}
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
@@ -184,6 +196,7 @@ a.a-button {
|
|||||||
color: white;
|
color: white;
|
||||||
background: none;
|
background: none;
|
||||||
border-radius: 0.25em;
|
border-radius: 0.25em;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -214,7 +227,11 @@ a.a-button {
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: #555;
|
background-color: #424242;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.75;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,6 +245,10 @@ a.a-button {
|
|||||||
|
|
||||||
background-color: #3c3c3c;
|
background-color: #3c3c3c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #555;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.btn--image {
|
&.btn--image {
|
||||||
@@ -283,6 +304,32 @@ a.a-button {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Basic tooltip
|
||||||
|
[data-tooltip] {
|
||||||
|
cursor: help;
|
||||||
|
line-height: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-tooltip]:hover::after,
|
||||||
|
[data-tooltip]:focus::after {
|
||||||
|
position: absolute;
|
||||||
|
transform: translate(0, -50%);
|
||||||
|
|
||||||
|
content: attr(data-tooltip);
|
||||||
|
color: white;
|
||||||
|
background-color: #333;
|
||||||
|
box-shadow: 0 0 5px 2px #aaa;
|
||||||
|
border-radius: 0.5em;
|
||||||
|
padding: 0.5em;
|
||||||
|
margin: 0 0.5em;
|
||||||
|
max-width: 300px;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip-help {
|
||||||
|
cursor: help;
|
||||||
|
}
|
||||||
|
|
||||||
@include smallScreen {
|
@include smallScreen {
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 0.5em;
|
width: 0.5em;
|
||||||
@@ -296,24 +343,19 @@ a.a-button {
|
|||||||
background-color: #777;
|
background-color: #777;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-tooltip]:hover::after,
|
||||||
|
[data-tooltip]:focus::after {
|
||||||
|
transform: translate(-50%, 2em);
|
||||||
|
left: 50%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Basic tooltip
|
.g-separator {
|
||||||
[data-tooltip]:hover::after,
|
display: block;
|
||||||
[data-tooltip]:focus::after {
|
width: 100%;
|
||||||
position: absolute;
|
height: 2px;
|
||||||
transform: translate(10px, -50%);
|
background-color: #aaa;
|
||||||
|
margin: 0.5em 0;
|
||||||
content: attr(data-tooltip);
|
|
||||||
color: white;
|
|
||||||
background-color: #171717;
|
|
||||||
border-radius: 0.5em;
|
|
||||||
padding: 0.5em;
|
|
||||||
margin: 0 0.25em;
|
|
||||||
max-width: 300px;
|
|
||||||
z-index: 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-tooltip] {
|
|
||||||
cursor: help;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
.scenery-table-section {
|
|
||||||
position: relative;
|
|
||||||
height: 100%;
|
|
||||||
overflow-y: scroll;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.scenery-history-table {
|
table.scenery-history-table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
table-layout: fixed;
|
||||||
|
min-width: 900px;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
|
|
||||||
thead {
|
thead {
|
||||||
@@ -25,7 +21,7 @@ table.scenery-history-table {
|
|||||||
|
|
||||||
td {
|
td {
|
||||||
padding: 0.75em;
|
padding: 0.75em;
|
||||||
border-bottom: solid 5px #111;
|
border-bottom: solid 5px #181818;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,3 @@ $warningCol: #ffe15b;
|
|||||||
|
|
||||||
$accentCol: #ffc014;
|
$accentCol: #ffc014;
|
||||||
$accent2Col: #ff3d5d;
|
$accent2Col: #ff3d5d;
|
||||||
|
|
||||||
$skr: #ff5100;
|
|
||||||
$twr: #ffbb00;
|
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
import { Status } from './common';
|
import { Status, VehicleData } from './common';
|
||||||
|
|
||||||
export enum APIDataStatus {
|
export enum APIDataStatus {
|
||||||
OK = 'OK',
|
OK = 'OK',
|
||||||
WARNING = 'WARNING'
|
WARNING = 'WARNING'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface APICache {
|
||||||
|
lastModified: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
export namespace API {
|
export namespace API {
|
||||||
export namespace ActiveData {
|
export namespace ActiveData {
|
||||||
export interface APIStatuses {
|
export interface APIStatuses {
|
||||||
@@ -17,13 +23,15 @@ export namespace API {
|
|||||||
activeSceneries?: API.ActiveSceneries.Response;
|
activeSceneries?: API.ActiveSceneries.Response;
|
||||||
trains?: API.ActiveTrains.Response;
|
trains?: API.ActiveTrains.Response;
|
||||||
apiStatuses?: APIStatuses;
|
apiStatuses?: APIStatuses;
|
||||||
|
caches: APICache[];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace DispatcherHistory {
|
export namespace DispatcherHistory {
|
||||||
export type Response = Data[];
|
export type Response = Data[];
|
||||||
|
|
||||||
export interface Data {
|
export interface Data {
|
||||||
id: string;
|
id: number;
|
||||||
currentDuration: number;
|
currentDuration: number;
|
||||||
dispatcherId: number;
|
dispatcherId: number;
|
||||||
dispatcherName: string;
|
dispatcherName: string;
|
||||||
@@ -38,6 +46,7 @@ export namespace API {
|
|||||||
stationName: string;
|
stationName: string;
|
||||||
timestampFrom: number;
|
timestampFrom: number;
|
||||||
timestampTo?: number;
|
timestampTo?: number;
|
||||||
|
statusHistory: string[];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,7 +201,11 @@ export namespace API {
|
|||||||
|
|
||||||
TWR: boolean;
|
TWR: boolean;
|
||||||
SKR: boolean;
|
SKR: boolean;
|
||||||
|
hasDangerousCargo: boolean;
|
||||||
|
hasExtraDeliveries: boolean;
|
||||||
|
warningNotes: string | null;
|
||||||
sceneries: string[];
|
sceneries: string[];
|
||||||
|
path: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,8 +248,6 @@ export namespace API {
|
|||||||
authorName?: string;
|
authorName?: string;
|
||||||
authorId?: number;
|
authorId?: number;
|
||||||
|
|
||||||
stopsString?: string;
|
|
||||||
|
|
||||||
stockString?: string;
|
stockString?: string;
|
||||||
stockHistory: string[];
|
stockHistory: string[];
|
||||||
|
|
||||||
@@ -244,19 +255,21 @@ export namespace API {
|
|||||||
stockLength?: number;
|
stockLength?: number;
|
||||||
maxSpeed?: number;
|
maxSpeed?: number;
|
||||||
|
|
||||||
hashesString?: string;
|
|
||||||
currentSceneryName?: string;
|
currentSceneryName?: string;
|
||||||
currentSceneryHash?: string;
|
currentSceneryHash?: string;
|
||||||
|
routeSceneries: string;
|
||||||
routeSceneries?: string;
|
checkpointArrivals: string[];
|
||||||
|
checkpointDepartures: string[];
|
||||||
checkpointArrivals?: string[];
|
checkpointArrivalsScheduled: string[];
|
||||||
checkpointDepartures?: string[];
|
checkpointDeparturesScheduled: string[];
|
||||||
|
checkpointStopTypes: string[];
|
||||||
checkpointArrivalsScheduled?: string[];
|
checkpointComments: string[];
|
||||||
checkpointDeparturesScheduled?: string[];
|
visitedSceneries: string[];
|
||||||
|
sceneryNames: string[];
|
||||||
checkpointStopTypes?: string[];
|
path: string;
|
||||||
|
warningNotes: string | null;
|
||||||
|
hasDangerousCargo: boolean;
|
||||||
|
hasExtraDeliveries: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Response = Data[];
|
export type Response = Data[];
|
||||||
@@ -316,6 +329,10 @@ export namespace API {
|
|||||||
export namespace Donators {
|
export namespace Donators {
|
||||||
export type Response = string[];
|
export type Response = string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export namespace Vehicles {
|
||||||
|
export type Response = VehicleData[];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace GithubAPI {
|
export namespace GithubAPI {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { RouteLocationRaw } from 'vue-router';
|
||||||
|
|
||||||
export type Availability = 'default' | 'unavailable' | 'nonPublic' | 'abandoned' | 'nonDefault';
|
export type Availability = 'default' | 'unavailable' | 'nonPublic' | 'abandoned' | 'nonDefault';
|
||||||
export type ScenerySpawnType = 'passenger' | 'freight' | 'loco' | 'all';
|
export type ScenerySpawnType = 'passenger' | 'freight' | 'loco' | 'all';
|
||||||
|
|
||||||
@@ -39,8 +41,16 @@ export interface RegionCounters {
|
|||||||
timetablesCount: number;
|
timetablesCount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TimetablePathElement {
|
||||||
|
arrivalRouteExt?: string;
|
||||||
|
departureRouteExt?: string;
|
||||||
|
stationName: string;
|
||||||
|
stationHash: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Train {
|
export interface Train {
|
||||||
id: string;
|
id: string;
|
||||||
|
modalId: string;
|
||||||
mass: number;
|
mass: number;
|
||||||
length: number;
|
length: number;
|
||||||
speed: number;
|
speed: number;
|
||||||
@@ -62,17 +72,24 @@ export interface Train {
|
|||||||
isTimeout: boolean;
|
isTimeout: boolean;
|
||||||
isSupporter: boolean;
|
isSupporter: boolean;
|
||||||
|
|
||||||
timetableData?: {
|
driverRouteLocation: RouteLocationRaw;
|
||||||
timetableId: number;
|
|
||||||
category: string;
|
timetableData?: TrainTimetableData;
|
||||||
route: string;
|
}
|
||||||
followingStops: TrainStop[];
|
|
||||||
TWR: boolean;
|
export interface TrainTimetableData {
|
||||||
SKR: boolean;
|
timetableId: number;
|
||||||
routeDistance: number;
|
category: string;
|
||||||
sceneries: string[];
|
route: string;
|
||||||
sceneryNames: string[];
|
followingStops: TrainStop[];
|
||||||
};
|
TWR: boolean;
|
||||||
|
SKR: boolean;
|
||||||
|
hasDangerousCargo: boolean;
|
||||||
|
hasExtraDeliveries: boolean;
|
||||||
|
warningNotes: string | null;
|
||||||
|
routeDistance: number;
|
||||||
|
sceneries: string[];
|
||||||
|
timetablePath: TimetablePathElement[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Station {
|
export interface Station {
|
||||||
@@ -187,4 +204,37 @@ export interface TrainStop {
|
|||||||
export interface CheckpointTrain {
|
export interface CheckpointTrain {
|
||||||
checkpointStop: TrainStop;
|
checkpointStop: TrainStop;
|
||||||
train: Train;
|
train: Train;
|
||||||
|
timetablePathElement: TimetablePathElement;
|
||||||
|
previousSceneryElement: TimetablePathElement | null;
|
||||||
|
nextSceneryElement: TimetablePathElement | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vehicles Data
|
||||||
|
|
||||||
|
export interface VehicleData {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
cabinName: string | null;
|
||||||
|
restrictions: Record<string, any> | null;
|
||||||
|
vehicleGroupsId: number;
|
||||||
|
group: VehiclesGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VehiclesGroup {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
speed: number;
|
||||||
|
length: number;
|
||||||
|
weight: number;
|
||||||
|
cargoTypes: VehicleCargo[] | null;
|
||||||
|
locoProps: {
|
||||||
|
coldStart: boolean;
|
||||||
|
doubleManned: boolean;
|
||||||
|
} | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VehicleCargo {
|
||||||
|
id: string;
|
||||||
|
weight: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,166 @@
|
|||||||
|
<template>
|
||||||
|
<section class="driver-view">
|
||||||
|
<div class="view-wrapper">
|
||||||
|
<div v-if="chosenTrain">
|
||||||
|
<div class="actions">
|
||||||
|
<a class="a-button btn--image" @click="$router.back()">
|
||||||
|
<img src="/images/icon-back.svg" alt="train icon" />
|
||||||
|
<span>
|
||||||
|
{{ $t('trains.driver-return-link') }}
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<router-link
|
||||||
|
:to="`/journal/timetables?search-driver=${chosenTrain.driverName}`"
|
||||||
|
class="a-button btn--image"
|
||||||
|
>
|
||||||
|
<span class="hidable">
|
||||||
|
{{ $t('trains.driver-journal-link') }}
|
||||||
|
</span>
|
||||||
|
<img src="/images/icon-train.svg" alt="train icon" />
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="train-card">
|
||||||
|
<TrainInfo :train="chosenTrain" :extended="true" ref="trainInfo" />
|
||||||
|
<TrainSchedule :train="chosenTrain" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Loading v-else-if="apiStore.dataStatuses.connection == Status.Data.Loading" />
|
||||||
|
|
||||||
|
<div v-else class="driver-not-found">
|
||||||
|
<h2>⦻ {{ $t('trains.driver-not-found-header') }}</h2>
|
||||||
|
|
||||||
|
<p class="text--grayed">
|
||||||
|
{{ $t('trains.driver-not-found-desc-1') }} <br />
|
||||||
|
{{ $t('trains.driver-not-found-desc-2') }}
|
||||||
|
<router-link to="/journal/timetables"
|
||||||
|
>{{ $t('trains.driver-not-found-journal') }} </router-link
|
||||||
|
>!
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p v-if="props.trainId && otherDriverTrains.length > 0">
|
||||||
|
<i18n-t keypath="trains.driver-not-found-others">
|
||||||
|
<template v-slot:driver>
|
||||||
|
<b>{{ otherDriverTrains[0].driverName }}</b>
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="other-driver-trains">
|
||||||
|
<template v-for="(train, i) in otherDriverTrains">
|
||||||
|
<router-link :to="`/driver?trainId=${train.id}`">
|
||||||
|
{{ train.trainNo }}
|
||||||
|
| {{ regionsJSON.find((r) => r.id == train.region)?.name ?? 'PL1' }}
|
||||||
|
</router-link>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="margin-top: 1em">
|
||||||
|
<router-link to="/"><< {{ $t('trains.driver-not-found-return') }}</router-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import TrainInfo from '../components/TrainsView/TrainInfo.vue';
|
||||||
|
import TrainSchedule from '../components/TrainsView/TrainSchedule.vue';
|
||||||
|
import Loading from '../components/Global/Loading.vue';
|
||||||
|
import { useMainStore } from '../store/mainStore';
|
||||||
|
import { useApiStore } from '../store/apiStore';
|
||||||
|
import { Status } from '../typings/common';
|
||||||
|
import { regions as regionsJSON } from '../data/options.json';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
trainId: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
|
||||||
|
modalId: {
|
||||||
|
type: String
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const mainStore = useMainStore();
|
||||||
|
const apiStore = useApiStore();
|
||||||
|
|
||||||
|
const chosenTrain = computed(() =>
|
||||||
|
mainStore.trainList.find((train) => train.id == props.trainId || train.modalId == props.modalId)
|
||||||
|
);
|
||||||
|
|
||||||
|
const otherDriverTrains = computed(() => {
|
||||||
|
return mainStore.trainList.filter(
|
||||||
|
(train) =>
|
||||||
|
train.driverId == Number(props.trainId?.split('|')[0]) &&
|
||||||
|
(train.timetableData || train.online || train.lastSeen >= Date.now() - 60000)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '../styles/responsive';
|
||||||
|
|
||||||
|
$viewBgCol: #1a1a1a;
|
||||||
|
|
||||||
|
.driver-view {
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 1em 0;
|
||||||
|
max-width: var(--max-container-width);
|
||||||
|
min-height: calc(100vh - 7em);
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions > a {
|
||||||
|
background-color: $viewBgCol;
|
||||||
|
padding: 0.5em;
|
||||||
|
border-radius: 0.5em 0.5em 0 0;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: lighten($viewBgCol, 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.train-card {
|
||||||
|
background-color: $viewBgCol;
|
||||||
|
border-radius: 0 0 0.5em 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.driver-not-found {
|
||||||
|
background-color: $viewBgCol;
|
||||||
|
text-align: center;
|
||||||
|
padding: 1em;
|
||||||
|
border-radius: 0.5em 0.5em;
|
||||||
|
|
||||||
|
p {
|
||||||
|
padding: 0.5em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: underline;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.other-driver-trains {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include smallScreen {
|
||||||
|
.actions > a > span.hidable {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -7,8 +7,8 @@
|
|||||||
<JournalOptions
|
<JournalOptions
|
||||||
@on-search-confirm="fetchHistoryData"
|
@on-search-confirm="fetchHistoryData"
|
||||||
@on-options-reset="resetOptions"
|
@on-options-reset="resetOptions"
|
||||||
@on-refresh-data="fetchHistoryData(true)"
|
@on-refresh-data="fetchHistoryData"
|
||||||
:sorter-option-ids="['timestampFrom', 'duration']"
|
:sorter-option-ids="['timestampFrom', 'currentDuration']"
|
||||||
:data-status="dataStatus"
|
:data-status="dataStatus"
|
||||||
:current-options-active="currentOptionsActive"
|
:current-options-active="currentOptionsActive"
|
||||||
optionsType="dispatchers"
|
optionsType="dispatchers"
|
||||||
@@ -59,6 +59,28 @@ const statsButtons: Journal.StatsButton[] = [
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
interface DispatchersQueryParams {
|
||||||
|
dispatcherName?: string;
|
||||||
|
stationName?: string;
|
||||||
|
stationHash?: string;
|
||||||
|
timestampFrom?: number;
|
||||||
|
timestampTo?: number;
|
||||||
|
countFrom?: number;
|
||||||
|
countLimit?: number;
|
||||||
|
sortBy?: Journal.DispatcherSorter['id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultQueryParams: DispatchersQueryParams = {
|
||||||
|
countLimit: 30,
|
||||||
|
sortBy: 'timestampFrom',
|
||||||
|
countFrom: undefined,
|
||||||
|
dispatcherName: undefined,
|
||||||
|
stationHash: undefined,
|
||||||
|
stationName: undefined,
|
||||||
|
timestampFrom: undefined,
|
||||||
|
timestampTo: undefined
|
||||||
|
};
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
JournalOptions,
|
JournalOptions,
|
||||||
@@ -83,9 +105,8 @@ export default defineComponent({
|
|||||||
data: () => ({
|
data: () => ({
|
||||||
statsButtons,
|
statsButtons,
|
||||||
|
|
||||||
currentQuery: '',
|
|
||||||
currentQueryArray: [] as string[],
|
|
||||||
dataRefreshedAt: null as Date | null,
|
dataRefreshedAt: null as Date | null,
|
||||||
|
currentQueryParams: {} as DispatchersQueryParams,
|
||||||
|
|
||||||
scrollDataLoaded: true,
|
scrollDataLoaded: true,
|
||||||
scrollNoMoreData: false,
|
scrollNoMoreData: false,
|
||||||
@@ -109,9 +130,6 @@ export default defineComponent({
|
|||||||
'search-date': ''
|
'search-date': ''
|
||||||
} as Journal.DispatcherSearchType);
|
} as Journal.DispatcherSearchType);
|
||||||
|
|
||||||
const countFromIndex = ref(0);
|
|
||||||
const countLimit = 15;
|
|
||||||
|
|
||||||
provide('sorterActive', sorterActive);
|
provide('sorterActive', sorterActive);
|
||||||
provide('journalFilterActive', journalFilterActive);
|
provide('journalFilterActive', journalFilterActive);
|
||||||
provide('searchersValues', searchersValues);
|
provide('searchersValues', searchersValues);
|
||||||
@@ -126,19 +144,17 @@ export default defineComponent({
|
|||||||
sorterActive,
|
sorterActive,
|
||||||
searchersValues,
|
searchersValues,
|
||||||
|
|
||||||
countFromIndex,
|
scrollElement
|
||||||
countLimit,
|
|
||||||
|
|
||||||
scrollElement,
|
|
||||||
maxCount: ref(15)
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
currentQueryArray(q: string[]) {
|
currentQueryParams(queryParams: DispatchersQueryParams) {
|
||||||
this.currentOptionsActive =
|
this.currentOptionsActive = Object.keys(queryParams).some(
|
||||||
q.length > 2 ||
|
(k) =>
|
||||||
q.some((qv) => qv.startsWith('sortBy=') && qv.split('=')[1] != 'timestampFrom');
|
queryParams[k as keyof DispatchersQueryParams] !=
|
||||||
|
defaultQueryParams[k as keyof DispatchersQueryParams]
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
'mainStore.dispatcherStatsData'(stats) {
|
'mainStore.dispatcherStatsData'(stats) {
|
||||||
@@ -234,13 +250,10 @@ export default defineComponent({
|
|||||||
|
|
||||||
async addHistoryData() {
|
async addHistoryData() {
|
||||||
this.scrollDataLoaded = false;
|
this.scrollDataLoaded = false;
|
||||||
|
this.currentQueryParams['countFrom'] = this.historyList.length;
|
||||||
this.countFromIndex = this.historyList.length;
|
|
||||||
|
|
||||||
const responseData: API.DispatcherHistory.Response = await (
|
const responseData: API.DispatcherHistory.Response = await (
|
||||||
await this.apiStore.client!.get(
|
await this.apiStore.client!.get(`api/getDispatchers`, { params: this.currentQueryParams })
|
||||||
`api/getDispatchers?${this.currentQuery}&countFrom=${this.countFromIndex}`
|
|
||||||
)
|
|
||||||
).data;
|
).data;
|
||||||
|
|
||||||
if (!responseData) return;
|
if (!responseData) return;
|
||||||
@@ -254,40 +267,38 @@ export default defineComponent({
|
|||||||
this.scrollDataLoaded = true;
|
this.scrollDataLoaded = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
async fetchHistoryData(reset = false) {
|
async fetchHistoryData() {
|
||||||
const queries: string[] = [];
|
const queryParams: DispatchersQueryParams = {};
|
||||||
|
|
||||||
const dispatcher = this.searchersValues['search-dispatcher'].trim();
|
const dispatcherName = this.searchersValues['search-dispatcher'].trim() || undefined;
|
||||||
const station = this.searchersValues['search-station'].trim();
|
const stationName = this.searchersValues['search-station'].trim() || undefined;
|
||||||
const dateString = this.searchersValues['search-date'].trim();
|
const dateString = this.searchersValues['search-date'].trim() || undefined;
|
||||||
|
|
||||||
const timestampFrom = dateString
|
const timestampFrom = dateString
|
||||||
? Date.parse(new Date(dateString).toISOString()) - 120 * 60 * 1000
|
? Date.parse(new Date(dateString).toISOString()) - 120 * 60 * 1000
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const timestampTo = timestampFrom ? timestampFrom + 86400000 : undefined;
|
const timestampTo = timestampFrom ? timestampFrom + 86400000 : undefined;
|
||||||
|
|
||||||
if (dispatcher) queries.push(`dispatcherName=${dispatcher}`);
|
queryParams['dispatcherName'] = dispatcherName;
|
||||||
if (station) queries.push(`stationName=${station}`);
|
queryParams['timestampFrom'] = timestampFrom;
|
||||||
if (timestampFrom && timestampTo)
|
queryParams['timestampTo'] = timestampTo;
|
||||||
queries.push(`timestampFrom=${timestampFrom}`, `timestampTo=${timestampTo}`);
|
queryParams['countLimit'] = 30;
|
||||||
|
|
||||||
// API: const SORT_TYPES = ['allStopsCount', 'endDate', 'beginDate', 'routeDistance'];
|
if (stationName && stationName.startsWith('#'))
|
||||||
if (this.sorterActive.id == 'timestampFrom') queries.push('sortBy=timestampFrom');
|
queryParams['stationHash'] = stationName.slice(1);
|
||||||
else if (this.sorterActive.id == 'duration') queries.push('sortBy=currentDuration');
|
else queryParams['stationName'] = stationName;
|
||||||
else queries.push('sortBy=timestampFrom');
|
|
||||||
|
|
||||||
queries.push('countLimit=30');
|
queryParams['sortBy'] = this.sorterActive.id;
|
||||||
|
|
||||||
if (this.currentQuery != queries.join('&')) this.dataStatus = Status.Data.Loading;
|
if (JSON.stringify(this.currentQueryParams) != JSON.stringify(queryParams))
|
||||||
|
this.dataStatus = Status.Data.Loading;
|
||||||
|
|
||||||
this.currentQuery = queries.join('&');
|
this.currentQueryParams = queryParams;
|
||||||
this.currentQueryArray = queries;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (reset) this.dataStatus = Status.Data.Loading;
|
|
||||||
|
|
||||||
const responseData: API.DispatcherHistory.Response = await (
|
const responseData: API.DispatcherHistory.Response = await (
|
||||||
await this.apiStore.client!.get(`api/getDispatchers?${this.currentQuery}`)
|
await this.apiStore.client!.get(`api/getDispatchers`, { params: this.currentQueryParams })
|
||||||
).data;
|
).data;
|
||||||
|
|
||||||
if (!responseData) {
|
if (!responseData) {
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ import { defineComponent, provide, reactive, Ref, ref } from 'vue';
|
|||||||
|
|
||||||
import dateMixin from '../mixins/dateMixin';
|
import dateMixin from '../mixins/dateMixin';
|
||||||
import routerMixin from '../mixins/routerMixin';
|
import routerMixin from '../mixins/routerMixin';
|
||||||
import modalTrainMixin from '../mixins/modalTrainMixin';
|
|
||||||
|
|
||||||
import JournalOptions from '../components/JournalView/JournalOptions.vue';
|
import JournalOptions from '../components/JournalView/JournalOptions.vue';
|
||||||
import JournalStats from '../components/JournalView/JournalStats.vue';
|
import JournalStats from '../components/JournalView/JournalStats.vue';
|
||||||
@@ -106,7 +105,13 @@ export const journalTimetableFilters: Journal.TimetableFilter[] = [
|
|||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: Journal.TimetableFilterId.TWR_SKR,
|
id: Journal.TimetableFilterId.TN,
|
||||||
|
filterSection: Journal.FilterSection.SPECIAL,
|
||||||
|
isActive: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: Journal.TimetableFilterId.PN,
|
||||||
filterSection: Journal.FilterSection.SPECIAL,
|
filterSection: Journal.FilterSection.SPECIAL,
|
||||||
isActive: false,
|
isActive: false,
|
||||||
default: false
|
default: false
|
||||||
@@ -119,13 +124,13 @@ interface TimetablesQueryParams {
|
|||||||
timetableId?: string;
|
timetableId?: string;
|
||||||
|
|
||||||
authorName?: string;
|
authorName?: string;
|
||||||
// timestampFrom?: number;
|
|
||||||
// timestampTo?: number;
|
|
||||||
|
|
||||||
dateFrom?: string;
|
dateFrom?: string;
|
||||||
dateTo?: string;
|
dateTo?: string;
|
||||||
|
|
||||||
issuedFrom?: string;
|
issuedFrom?: string;
|
||||||
|
terminatingAt?: string;
|
||||||
|
via?: string;
|
||||||
|
|
||||||
countFrom?: number;
|
countFrom?: number;
|
||||||
countLimit?: number;
|
countLimit?: number;
|
||||||
@@ -135,6 +140,8 @@ interface TimetablesQueryParams {
|
|||||||
|
|
||||||
twr?: number;
|
twr?: number;
|
||||||
skr?: number;
|
skr?: number;
|
||||||
|
pn?: number;
|
||||||
|
tn?: number;
|
||||||
|
|
||||||
sortBy?: Journal.TimetableSorter['id'];
|
sortBy?: Journal.TimetableSorter['id'];
|
||||||
}
|
}
|
||||||
@@ -146,7 +153,7 @@ export default defineComponent({
|
|||||||
JournalHeader,
|
JournalHeader,
|
||||||
JournalTimetablesList
|
JournalTimetablesList
|
||||||
},
|
},
|
||||||
mixins: [dateMixin, routerMixin, modalTrainMixin],
|
mixins: [dateMixin, routerMixin],
|
||||||
|
|
||||||
name: 'JournalTimetables',
|
name: 'JournalTimetables',
|
||||||
|
|
||||||
@@ -206,6 +213,8 @@ export default defineComponent({
|
|||||||
'search-driver': '',
|
'search-driver': '',
|
||||||
'search-dispatcher': '',
|
'search-dispatcher': '',
|
||||||
'search-issuedFrom': '',
|
'search-issuedFrom': '',
|
||||||
|
'search-via': '',
|
||||||
|
'search-terminatingAt': '',
|
||||||
'search-date': ''
|
'search-date': ''
|
||||||
} as Journal.TimetableSearchType);
|
} as Journal.TimetableSearchType);
|
||||||
|
|
||||||
@@ -299,11 +308,9 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
setOptions(options: { [key: string]: string }) {
|
setOptions(options: { [key: string]: string }) {
|
||||||
this.searchersValues['search-date'] = options['search-date'] ?? '';
|
Object.keys(this.searchersValues).forEach((v) => {
|
||||||
this.searchersValues['search-driver'] = options['search-driver'] ?? '';
|
this.searchersValues[v as Journal.TimetableSearchKey] = options[v] ?? '';
|
||||||
this.searchersValues['search-train'] = options['search-train'] ?? '';
|
});
|
||||||
this.searchersValues['search-dispatcher'] = options['search-dispatcher'] ?? '';
|
|
||||||
this.searchersValues['search-issuedFrom'] = options['search-issuedFrom'] ?? '';
|
|
||||||
|
|
||||||
this.sorterActive.id =
|
this.sorterActive.id =
|
||||||
(options['sorter-active'] as Journal.TimetableSorterKey) ?? 'timetableId';
|
(options['sorter-active'] as Journal.TimetableSorterKey) ?? 'timetableId';
|
||||||
@@ -326,7 +333,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
const responseData: API.TimetableHistory.Response = await (
|
const responseData: API.TimetableHistory.Response = await (
|
||||||
await this.apiStore.client!.get('api/getTimetables', {
|
await this.apiStore.client!.get('api/getTimetables', {
|
||||||
params: { ...this.currentQueryParams }
|
params: this.currentQueryParams
|
||||||
})
|
})
|
||||||
).data;
|
).data;
|
||||||
|
|
||||||
@@ -347,6 +354,8 @@ export default defineComponent({
|
|||||||
const authorName = this.searchersValues['search-dispatcher'].trim() || undefined;
|
const authorName = this.searchersValues['search-dispatcher'].trim() || undefined;
|
||||||
const dateFrom = this.searchersValues['search-date'].trim() || undefined;
|
const dateFrom = this.searchersValues['search-date'].trim() || undefined;
|
||||||
const issuedFrom = this.searchersValues['search-issuedFrom'].trim() || undefined;
|
const issuedFrom = this.searchersValues['search-issuedFrom'].trim() || undefined;
|
||||||
|
const via = this.searchersValues['search-via'].trim() || undefined;
|
||||||
|
const terminatingAt = this.searchersValues['search-terminatingAt'].trim() || undefined;
|
||||||
|
|
||||||
let dateTo: string | undefined = undefined;
|
let dateTo: string | undefined = undefined;
|
||||||
|
|
||||||
@@ -388,21 +397,24 @@ export default defineComponent({
|
|||||||
case Journal.TimetableFilterId.ALL_SPECIALS:
|
case Journal.TimetableFilterId.ALL_SPECIALS:
|
||||||
queryParams['twr'] = undefined;
|
queryParams['twr'] = undefined;
|
||||||
queryParams['skr'] = undefined;
|
queryParams['skr'] = undefined;
|
||||||
|
queryParams['pn'] = undefined;
|
||||||
|
queryParams['tn'] = undefined;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Journal.TimetableFilterId.TWR:
|
case Journal.TimetableFilterId.TWR:
|
||||||
queryParams['twr'] = 1;
|
queryParams['twr'] = 1;
|
||||||
queryParams['skr'] = 0;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Journal.TimetableFilterId.SKR:
|
case Journal.TimetableFilterId.SKR:
|
||||||
queryParams['twr'] = 0;
|
|
||||||
queryParams['skr'] = 1;
|
queryParams['skr'] = 1;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Journal.TimetableFilterId.TWR_SKR:
|
case Journal.TimetableFilterId.TN:
|
||||||
queryParams['twr'] = 1;
|
queryParams['tn'] = 1;
|
||||||
queryParams['skr'] = 1;
|
break;
|
||||||
|
|
||||||
|
case Journal.TimetableFilterId.PN:
|
||||||
|
queryParams['pn'] = 1;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -418,6 +430,10 @@ export default defineComponent({
|
|||||||
queryParams['authorName'] = authorName;
|
queryParams['authorName'] = authorName;
|
||||||
queryParams['dateFrom'] = dateFrom;
|
queryParams['dateFrom'] = dateFrom;
|
||||||
queryParams['dateTo'] = dateTo;
|
queryParams['dateTo'] = dateTo;
|
||||||
|
queryParams['issuedFrom'] = issuedFrom;
|
||||||
|
queryParams['terminatingAt'] = terminatingAt;
|
||||||
|
queryParams['via'] = via;
|
||||||
|
|
||||||
queryParams['issuedFrom'] = issuedFrom;
|
queryParams['issuedFrom'] = issuedFrom;
|
||||||
queryParams['sortBy'] =
|
queryParams['sortBy'] =
|
||||||
this.sorterActive.id != 'timetableId' ? this.sorterActive.id : undefined;
|
this.sorterActive.id != 'timetableId' ? this.sorterActive.id : undefined;
|
||||||
@@ -446,7 +462,7 @@ export default defineComponent({
|
|||||||
this.timetableHistory = responseData;
|
this.timetableHistory = responseData;
|
||||||
|
|
||||||
// Stats display
|
// Stats display
|
||||||
this.store.driverStatsName =
|
this.mainStore.driverStatsName =
|
||||||
this.timetableHistory.length > 0 && this.searchersValues['search-driver'].trim()
|
this.timetableHistory.length > 0 && this.searchersValues['search-driver'].trim()
|
||||||
? this.timetableHistory[0].driverName
|
? this.timetableHistory[0].driverName
|
||||||
: '';
|
: '';
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
:station="stationInfo"
|
:station="stationInfo"
|
||||||
:onlineScenery="onlineSceneryInfo"
|
:onlineScenery="onlineSceneryInfo"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SceneryInfo :station="stationInfo" :onlineScenery="onlineSceneryInfo" />
|
<SceneryInfo :station="stationInfo" :onlineScenery="onlineSceneryInfo" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -22,8 +23,8 @@
|
|||||||
v-for="(viewMode, i) in viewModes"
|
v-for="(viewMode, i) in viewModes"
|
||||||
:key="i"
|
:key="i"
|
||||||
class="btn btn--option"
|
class="btn btn--option"
|
||||||
|
:class="{ checked: currentMode == viewMode.component }"
|
||||||
@click="setViewMode(viewMode.component)"
|
@click="setViewMode(viewMode.component)"
|
||||||
:data-checked="currentMode == viewMode.component"
|
|
||||||
>
|
>
|
||||||
{{ $t(viewMode.id) }}
|
{{ $t(viewMode.id) }}
|
||||||
</button>
|
</button>
|
||||||
@@ -121,10 +122,6 @@ export default defineComponent({
|
|||||||
Status: Status.Data
|
Status: Status.Data
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// activated() {
|
|
||||||
// this.loadSelectedCheckpoint();
|
|
||||||
// },
|
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
@@ -215,11 +212,10 @@ button.back-btn {
|
|||||||
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
max-width: var(--max-container-width);
|
max-width: var(--max-container-width);
|
||||||
min-height: 100vh;
|
width: 100%;
|
||||||
|
|
||||||
margin: 1rem 0;
|
padding: 1rem 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
&[data-timetable-only='true'] {
|
&[data-timetable-only='true'] {
|
||||||
@@ -228,30 +224,28 @@ button.back-btn {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.scenery-left {
|
.scenery-left,
|
||||||
|
.scenery-right {
|
||||||
position: relative;
|
position: relative;
|
||||||
background-color: #181818;
|
|
||||||
padding: 1em 0.5em;
|
|
||||||
|
|
||||||
height: 100vh;
|
|
||||||
min-height: 750px;
|
|
||||||
max-height: 1000px;
|
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|
||||||
|
background-color: #181818;
|
||||||
|
border-radius: 0.5em;
|
||||||
|
padding: 1em 0.5em;
|
||||||
|
|
||||||
|
height: calc(100vh - 0.5em);
|
||||||
|
min-height: 500px;
|
||||||
|
max-height: 2000px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scenery-left {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scenery-right {
|
.scenery-right {
|
||||||
background: #181818;
|
|
||||||
padding: 1em 0.5em;
|
|
||||||
|
|
||||||
height: 100vh;
|
|
||||||
min-height: 750px;
|
|
||||||
max-height: 1000px;
|
|
||||||
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: auto 1fr auto;
|
grid-template-rows: auto 1fr;
|
||||||
gap: 1em;
|
gap: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,18 +255,12 @@ button.back-btn {
|
|||||||
|
|
||||||
.info-actions {
|
.info-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
gap: 0.75em;
|
gap: 0.75em;
|
||||||
|
|
||||||
.btn {
|
button {
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
box-shadow: 0 0 10px 4px #242424;
|
|
||||||
|
|
||||||
&[data-checked='true'] {
|
|
||||||
color: var(--clr-primary);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
ref="filterCardRef"
|
ref="filterCardRef"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<StationStats />
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="btn-donation btn--image"
|
class="btn-donation btn--image"
|
||||||
ref="btn"
|
ref="btn"
|
||||||
@@ -21,7 +23,6 @@
|
|||||||
|
|
||||||
<DonationCard :is-card-open="isDonationCardOpen" @toggle-card="toggleDonationCard" />
|
<DonationCard :is-card-open="isDonationCardOpen" @toggle-card="toggleDonationCard" />
|
||||||
<StationTable @toggle-donation-card="toggleDonationCard" />
|
<StationTable @toggle-donation-card="toggleDonationCard" />
|
||||||
<StationStats />
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
@@ -96,14 +97,16 @@ export default defineComponent({
|
|||||||
|
|
||||||
.stations-options {
|
.stations-options {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
margin-bottom: 0.5em;
|
margin-bottom: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
button.btn-donation {
|
button.btn-donation {
|
||||||
|
margin-left: auto;
|
||||||
|
|
||||||
$btnColor: #254069;
|
$btnColor: #254069;
|
||||||
|
|
||||||
background-color: $btnColor;
|
background-color: $btnColor;
|
||||||
@@ -118,4 +121,8 @@ button.btn-donation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.count {
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -19,7 +19,6 @@
|
|||||||
import { computed, ComputedRef, defineComponent, provide, reactive, ref, watch } from 'vue';
|
import { computed, ComputedRef, defineComponent, provide, reactive, ref, watch } from 'vue';
|
||||||
import TrainOptions from '../components/TrainsView/TrainOptions.vue';
|
import TrainOptions from '../components/TrainsView/TrainOptions.vue';
|
||||||
import TrainTable from '../components/TrainsView/TrainTable.vue';
|
import TrainTable from '../components/TrainsView/TrainTable.vue';
|
||||||
import modalTrainMixin from '../mixins/modalTrainMixin';
|
|
||||||
import { useMainStore } from '../store/mainStore';
|
import { useMainStore } from '../store/mainStore';
|
||||||
import { TrainFilter, trainFilters } from '../components/TrainsView/typings';
|
import { TrainFilter, trainFilters } from '../components/TrainsView/typings';
|
||||||
import { filteredTrainList } from '../managers/trainFilterManager';
|
import { filteredTrainList } from '../managers/trainFilterManager';
|
||||||
@@ -33,8 +32,6 @@ export default defineComponent({
|
|||||||
TrainStats
|
TrainStats
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [modalTrainMixin],
|
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
train: {
|
train: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -102,16 +99,16 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
activated() {
|
activated() {
|
||||||
|
// Backwards compatibility with external links leading to train modal
|
||||||
|
if (this.trainId) {
|
||||||
|
this.$router.replace(`/driver?modalId=${this.trainId}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.train) {
|
if (this.train) {
|
||||||
this.searchedTrain = this.train;
|
this.searchedTrain = this.train;
|
||||||
this.searchedDriver = this.driver || '';
|
this.searchedDriver = this.driver || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$nextTick(() => {
|
|
||||||
if (this.trainId) {
|
|
||||||
this.selectModalTrain(this.trainId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@@ -120,13 +117,12 @@ export default defineComponent({
|
|||||||
@import '../styles/responsive.scss';
|
@import '../styles/responsive.scss';
|
||||||
|
|
||||||
.trains-view {
|
.trains-view {
|
||||||
min-height: 600px;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.trains_wrapper {
|
.trains_wrapper {
|
||||||
margin: 1rem auto;
|
margin: 1rem auto;
|
||||||
max-width: 1500px;
|
max-width: var(--max-container-width);
|
||||||
}
|
}
|
||||||
|
|
||||||
.trains_topbar {
|
.trains_topbar {
|
||||||
|
|||||||