Migracja z wersji Vue 2 na Vue 3

This commit is contained in:
2021-06-29 02:26:36 +02:00
parent 6391b997b1
commit 26ae065837
49 changed files with 2906 additions and 3279 deletions
+492 -127
View File
@@ -1055,6 +1055,74 @@
"postcss": "^7.0.0"
}
},
"@intlify/core-base": {
"version": "9.1.6",
"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.1.6.tgz",
"integrity": "sha512-d5GDPpsQbqPkisSJA5b6nJFEkalY/IHAd7vOLNd/Sj4YaNRzXtInu2FoqKiOv8e/lQnXGTpurdCZg5Jxq1Gsxw==",
"requires": {
"@intlify/devtools-if": "9.1.6",
"@intlify/message-compiler": "9.1.6",
"@intlify/message-resolver": "9.1.6",
"@intlify/runtime": "9.1.6",
"@intlify/shared": "9.1.6",
"@intlify/vue-devtools": "9.1.6"
}
},
"@intlify/devtools-if": {
"version": "9.1.6",
"resolved": "https://registry.npmjs.org/@intlify/devtools-if/-/devtools-if-9.1.6.tgz",
"integrity": "sha512-m8Api+kh+BtFa2FZ/JjIdr1ibsGGqBjdKCzWo5BZecEUxBquIeOQZwpokPh/0K5j+/PZleFXkVAMC5mNt+9WdA==",
"requires": {
"@intlify/shared": "9.1.6"
}
},
"@intlify/message-compiler": {
"version": "9.1.6",
"resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.1.6.tgz",
"integrity": "sha512-DR8645VOrVK6x/8tkaCpHnckMAIcoOgeNS5j0wB12RfZoXYQp7vAXMaOP511KMll2mXCREgIB0ojpajiof7yzQ==",
"requires": {
"@intlify/message-resolver": "9.1.6",
"@intlify/shared": "9.1.6",
"source-map": "0.6.1"
},
"dependencies": {
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
}
}
},
"@intlify/message-resolver": {
"version": "9.1.6",
"resolved": "https://registry.npmjs.org/@intlify/message-resolver/-/message-resolver-9.1.6.tgz",
"integrity": "sha512-UUnbawQa5U9sffd5wRIscqtyY1xWlwJbyfwCLPEWLvBhyAnCwPYlvaHGnnO0CSi0fzJTVwlV9DYzobh3agDeMA=="
},
"@intlify/runtime": {
"version": "9.1.6",
"resolved": "https://registry.npmjs.org/@intlify/runtime/-/runtime-9.1.6.tgz",
"integrity": "sha512-U1QZ+TPf3kQQvWo4BA2mj3cHAxMRHXNTBhu2u+deh6ubTqXdZ19XGBTMSasrXG6RE+zSio9oM+ndoLja7JGtPg==",
"requires": {
"@intlify/message-compiler": "9.1.6",
"@intlify/message-resolver": "9.1.6",
"@intlify/shared": "9.1.6"
}
},
"@intlify/shared": {
"version": "9.1.6",
"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.1.6.tgz",
"integrity": "sha512-6MtsKulyfZxdD7OuxjaODjj8QWoHCnLFAk4wkWiHqBCa6UCTC0qXjtEeZ1MxpQihvFmmJZauBUu25EvtngW5qQ=="
},
"@intlify/vue-devtools": {
"version": "9.1.6",
"resolved": "https://registry.npmjs.org/@intlify/vue-devtools/-/vue-devtools-9.1.6.tgz",
"integrity": "sha512-UdNovg4OML9rIr1sOGZzTfNr1nUy4UQpDf5ni4dNC93T6FIkVJz0n1Np7Vp7e6gDjcmufRYcV99tEwjQSN9+5A==",
"requires": {
"@intlify/message-resolver": "9.1.6",
"@intlify/runtime": "9.1.6",
"@intlify/shared": "9.1.6"
}
},
"@mrmlnc/readdir-enhanced": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz",
@@ -1135,6 +1203,12 @@
"@types/node": "*"
}
},
"@types/estree": {
"version": "0.0.48",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.48.tgz",
"integrity": "sha512-LfZwXoGUDo0C3me81HXgkBg5CTQYb6xzEl+fNmbO4JdRiSKQ8A0GD1OBBvKAIsbCUgoyAty7m99GqqMQe784ew==",
"dev": true
},
"@types/express": {
"version": "4.17.11",
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.11.tgz",
@@ -1597,6 +1671,52 @@
"integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
"dev": true
},
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"optional": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"optional": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true,
"optional": true
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"optional": true
},
"loader-utils": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
"dev": true,
"optional": true,
"requires": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^2.1.2"
}
},
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
@@ -1611,6 +1731,41 @@
"requires": {
"minipass": "^3.1.1"
}
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"optional": true,
"requires": {
"has-flag": "^4.0.0"
}
},
"vue-loader-v16": {
"version": "npm:vue-loader@16.2.0",
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.2.0.tgz",
"integrity": "sha512-TitGhqSQ61RJljMmhIGvfWzJ2zk9m1Qug049Ugml6QP3t0e95o0XJjk29roNEiPKJQBEi8Ord5hFuSuELzSp8Q==",
"dev": true,
"optional": true,
"requires": {
"chalk": "^4.1.0",
"hash-sum": "^2.0.0",
"loader-utils": "^2.0.0"
},
"dependencies": {
"chalk": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
"integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
"dev": true,
"optional": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
}
}
}
}
},
@@ -1642,6 +1797,143 @@
}
}
},
"@vue/compat": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@vue/compat/-/compat-3.1.2.tgz",
"integrity": "sha512-6pS22V01LmvkPMtpZvgk0udtR3pNTfxvFR+E4Ut+H9HHusyGf7Gx+PnQwnmawGOxuATNGzfasMaxIkQpbdT8jQ=="
},
"@vue/compiler-core": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.1.2.tgz",
"integrity": "sha512-nHmq7vLjq/XM2IMbZUcKWoH5sPXa2uR/nIKZtjbK5F3TcbnYE/zKsrSUR9WZJ03unlwotNBX1OyxVt9HbWD7/Q==",
"requires": {
"@babel/parser": "^7.12.0",
"@babel/types": "^7.12.0",
"@vue/shared": "3.1.2",
"estree-walker": "^2.0.1",
"source-map": "^0.6.1"
},
"dependencies": {
"@babel/helper-validator-identifier": {
"version": "7.14.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz",
"integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg=="
},
"@babel/parser": {
"version": "7.14.7",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.7.tgz",
"integrity": "sha512-X67Z5y+VBJuHB/RjwECp8kSl5uYi0BvRbNeWqkaJCVh+LiTPl19WBUfG627psSgp9rSf6ojuXghQM3ha6qHHdA=="
},
"@babel/types": {
"version": "7.14.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz",
"integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==",
"requires": {
"@babel/helper-validator-identifier": "^7.14.5",
"to-fast-properties": "^2.0.0"
}
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
}
}
},
"@vue/compiler-dom": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.1.2.tgz",
"integrity": "sha512-k2+SWcWH0jL6WQAX7Or2ONqu5MbtTgTO0dJrvebQYzgqaKMXNI90RNeWeCxS4BnNFMDONpHBeFgbwbnDWIkmRg==",
"requires": {
"@vue/compiler-core": "3.1.2",
"@vue/shared": "3.1.2"
}
},
"@vue/compiler-sfc": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.1.2.tgz",
"integrity": "sha512-SeG/2+DvwejQ7oAiSx8BrDh5qOdqCYHGClPiTvVIHTfSIHiS2JjMbCANdDCjHkTOh/O7WZzo2JhdKm98bRBxTw==",
"dev": true,
"requires": {
"@babel/parser": "^7.13.9",
"@babel/types": "^7.13.0",
"@types/estree": "^0.0.48",
"@vue/compiler-core": "3.1.2",
"@vue/compiler-dom": "3.1.2",
"@vue/compiler-ssr": "3.1.2",
"@vue/shared": "3.1.2",
"consolidate": "^0.16.0",
"estree-walker": "^2.0.1",
"hash-sum": "^2.0.0",
"lru-cache": "^5.1.1",
"magic-string": "^0.25.7",
"merge-source-map": "^1.1.0",
"postcss": "^8.1.10",
"postcss-modules": "^4.0.0",
"postcss-selector-parser": "^6.0.4",
"source-map": "^0.6.1"
},
"dependencies": {
"@babel/helper-validator-identifier": {
"version": "7.14.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz",
"integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==",
"dev": true
},
"@babel/parser": {
"version": "7.14.7",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.7.tgz",
"integrity": "sha512-X67Z5y+VBJuHB/RjwECp8kSl5uYi0BvRbNeWqkaJCVh+LiTPl19WBUfG627psSgp9rSf6ojuXghQM3ha6qHHdA==",
"dev": true
},
"@babel/types": {
"version": "7.14.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.5.tgz",
"integrity": "sha512-M/NzBpEL95I5Hh4dwhin5JlE7EzO5PHMAuzjxss3tiOBD46KfQvVedN/3jEPZvdRvtsK2222XfdHogNIttFgcg==",
"dev": true,
"requires": {
"@babel/helper-validator-identifier": "^7.14.5",
"to-fast-properties": "^2.0.0"
}
},
"consolidate": {
"version": "0.16.0",
"resolved": "https://registry.npmjs.org/consolidate/-/consolidate-0.16.0.tgz",
"integrity": "sha512-Nhl1wzCslqXYTJVDyJCu3ODohy9OfBMB5uD2BiBTzd7w+QY0lBzafkR8y8755yMYHAaMD4NuzbAw03/xzfw+eQ==",
"dev": true,
"requires": {
"bluebird": "^3.7.2"
}
},
"postcss": {
"version": "8.3.5",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.5.tgz",
"integrity": "sha512-NxTuJocUhYGsMiMFHDUkmjSKT3EdH4/WbGF6GCi1NDGk+vbcUTun4fpbOqaPtD8IIsztA2ilZm2DhYCuyN58gA==",
"dev": true,
"requires": {
"colorette": "^1.2.2",
"nanoid": "^3.1.23",
"source-map-js": "^0.6.2"
}
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
}
}
},
"@vue/compiler-ssr": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.1.2.tgz",
"integrity": "sha512-BwXo9LFk5OSWdMyZQ4bX1ELHX0Z/9F+ld/OaVnpUPzAZCHslBYLvyKUVDwv2C/lpLjRffpC2DOUEdl1+RP1aGg==",
"dev": true,
"requires": {
"@vue/compiler-dom": "3.1.2",
"@vue/shared": "3.1.2"
}
},
"@vue/component-compiler-utils": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/@vue/component-compiler-utils/-/component-compiler-utils-3.2.0.tgz",
@@ -1689,12 +1981,49 @@
}
}
},
"@vue/devtools-api": {
"version": "6.0.0-beta.15",
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.0.0-beta.15.tgz",
"integrity": "sha512-quBx4Jjpexo6KDiNUGFr/zF/2A4srKM9S9v2uHgMXSU//hjgq1eGzqkIFql8T9gfX5ZaVOUzYBP3jIdIR3PKIA=="
},
"@vue/preload-webpack-plugin": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@vue/preload-webpack-plugin/-/preload-webpack-plugin-1.1.2.tgz",
"integrity": "sha512-LIZMuJk38pk9U9Ur4YzHjlIyMuxPlACdBIHH9/nGYVTsaGKOSnSuELiE8vS9wa+dJpIYspYUOqk+L1Q4pgHQHQ==",
"dev": true
},
"@vue/reactivity": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.1.2.tgz",
"integrity": "sha512-glJzJoN2xE7I2lRvwKM5u1BHRPTd1yc8iaf//Lai/78/uYAvE5DXp5HzWRFOwMlbRvMGJHIQjOqoxj87cDAaag==",
"requires": {
"@vue/shared": "3.1.2"
}
},
"@vue/runtime-core": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.1.2.tgz",
"integrity": "sha512-gsPZG4dRIkixuuKmoj4P9IHgfT0yaFLcqWOM5F/bCk0nxQn1XtxH8oUehWuET726KhbukvDoJfe9G2CKviy80w==",
"requires": {
"@vue/reactivity": "3.1.2",
"@vue/shared": "3.1.2"
}
},
"@vue/runtime-dom": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.1.2.tgz",
"integrity": "sha512-QvINxjLucEZFzp5f0NVu7JqWYCv5TKQfkH2FDs/N6QNE4iKcYtKrWdT0HKfABnVXG28Znqv6rIH0dH4ZAOwxpA==",
"requires": {
"@vue/runtime-core": "3.1.2",
"@vue/shared": "3.1.2",
"csstype": "^2.6.8"
}
},
"@vue/shared": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.1.2.tgz",
"integrity": "sha512-EmH/poaDWBPJaPILXNI/1fvUbArJQmmTyVCwvvyDYDFnkPoTclAbHRAtyIvqfez7jybTDn077HTNILpxlsoWhg=="
},
"@vue/web-component-wrapper": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@vue/web-component-wrapper/-/web-component-wrapper-1.3.0.tgz",
@@ -3876,6 +4205,11 @@
}
}
},
"csstype": {
"version": "2.6.17",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.17.tgz",
"integrity": "sha512-u1wmTI1jJGzCJzWndZo8mk4wnPTZd1eOIYTYvuEyOQGfmDl3TrabCCfKnOC86FZwW/9djqTl933UF/cS425i9A=="
},
"cyclist": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz",
@@ -3891,12 +4225,6 @@
"assert-plus": "^1.0.0"
}
},
"de-indent": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",
"integrity": "sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=",
"dev": true
},
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
@@ -4583,6 +4911,11 @@
"integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
"dev": true
},
"estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
},
"esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
@@ -5033,14 +5366,16 @@
"integrity": "sha1-jHyF2gcTtAYFVrTXHAF3XuEmnrk=",
"requires": {
"websocket-extensions": ">=0.1.1"
},
"dependencies": {
"websocket-extensions": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.1.tgz",
"integrity": "sha1-domUmcGEtu91Q3fC27DNbLVdKec="
}
}
}
}
},
"websocket-extensions": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz",
"integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg=="
}
}
},
@@ -5194,6 +5529,15 @@
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"dev": true
},
"generic-names": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/generic-names/-/generic-names-2.0.1.tgz",
"integrity": "sha512-kPCHWa1m9wGG/OwQpeweTwM/PYiQLrUIxXbt/P4Nic3LbGjCP0YwrALHW1uNLKZ0LIMg+RF+XRlj2ekT9ZlZAQ==",
"dev": true,
"requires": {
"loader-utils": "^1.1.0"
}
},
"gensync": {
"version": "1.0.0-beta.1",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz",
@@ -5787,6 +6131,12 @@
"safer-buffer": ">= 2.1.2 < 3"
}
},
"icss-replace-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz",
"integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=",
"dev": true
},
"icss-utils": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-4.1.1.tgz",
@@ -6496,6 +6846,12 @@
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
},
"lodash.camelcase": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
"integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=",
"dev": true
},
"lodash.defaultsdeep": {
"version": "4.6.1",
"resolved": "https://registry.npmjs.org/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.1.tgz",
@@ -6571,6 +6927,15 @@
"yallist": "^3.0.2"
}
},
"magic-string": {
"version": "0.25.7",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz",
"integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==",
"dev": true,
"requires": {
"sourcemap-codec": "^1.4.4"
}
},
"make-dir": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
@@ -6918,6 +7283,12 @@
"thenify-all": "^1.0.0"
}
},
"nanoid": {
"version": "3.1.23",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz",
"integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==",
"dev": true
},
"nanomatch": {
"version": "1.2.13",
"resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
@@ -7871,6 +8242,65 @@
}
}
},
"postcss-modules": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/postcss-modules/-/postcss-modules-4.1.3.tgz",
"integrity": "sha512-dBT39hrXe4OAVYJe/2ZuIZ9BzYhOe7t+IhedYeQ2OxKwDpAGlkEN/fR0fGnrbx4BvgbMReRX4hCubYK9cE/pJQ==",
"dev": true,
"requires": {
"generic-names": "^2.0.1",
"icss-replace-symbols": "^1.1.0",
"lodash.camelcase": "^4.3.0",
"postcss-modules-extract-imports": "^3.0.0",
"postcss-modules-local-by-default": "^4.0.0",
"postcss-modules-scope": "^3.0.0",
"postcss-modules-values": "^4.0.0",
"string-hash": "^1.1.1"
},
"dependencies": {
"icss-utils": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz",
"integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==",
"dev": true
},
"postcss-modules-extract-imports": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz",
"integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==",
"dev": true
},
"postcss-modules-local-by-default": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz",
"integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==",
"dev": true,
"requires": {
"icss-utils": "^5.0.0",
"postcss-selector-parser": "^6.0.2",
"postcss-value-parser": "^4.1.0"
}
},
"postcss-modules-scope": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz",
"integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==",
"dev": true,
"requires": {
"postcss-selector-parser": "^6.0.4"
}
},
"postcss-modules-values": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz",
"integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==",
"dev": true,
"requires": {
"icss-utils": "^5.0.0"
}
}
}
},
"postcss-modules-extract-imports": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz",
@@ -9209,6 +9639,12 @@
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
"dev": true
},
"source-map-js": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-0.6.2.tgz",
"integrity": "sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==",
"dev": true
},
"source-map-resolve": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz",
@@ -9246,6 +9682,12 @@
"integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=",
"dev": true
},
"sourcemap-codec": {
"version": "1.4.8",
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
"dev": true
},
"spdx-correct": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz",
@@ -9443,6 +9885,12 @@
"integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=",
"dev": true
},
"string-hash": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/string-hash/-/string-hash-1.1.3.tgz",
"integrity": "sha1-6Kr8CsGFW0Zmkp7X3RJ1311sgRs=",
"dev": true
},
"string-width": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
@@ -9724,8 +10172,7 @@
"to-fast-properties": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
"integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=",
"dev": true
"integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4="
},
"to-object-path": {
"version": "0.3.0",
@@ -9947,9 +10394,9 @@
"dev": true
},
"typescript": {
"version": "3.9.9",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.9.tgz",
"integrity": "sha512-kdMjTiekY+z/ubJCATUPlRDl39vXYiMV9iyeMuEuXZh2we6zz80uovNN2WlAxmmdE/Z/YQe+EbOEXB5RHEED3w==",
"version": "4.1.6",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.6.tgz",
"integrity": "sha512-pxnwLxeb/Z5SP80JDRzVjh58KsM6jZHRAOtTpS7sXLS4ogXNKC9ANxHHZqLLeVHZN35jCtI4JdmLLbLiC1kBow==",
"dev": true
},
"uglify-js": {
@@ -10289,9 +10736,14 @@
"dev": true
},
"vue": {
"version": "2.6.12",
"resolved": "https://registry.npmjs.org/vue/-/vue-2.6.12.tgz",
"integrity": "sha512-uhmLFETqPPNyuLLbsKz6ioJ4q7AZHzD8ZVFNATNyICSZouqP2Sz0rotWQC8UNBF6VGSCs5abnKJoStA6JbCbfg=="
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.1.2.tgz",
"integrity": "sha512-q/rbKpb7aofax4ugqu2k/uj7BYuNPcd6Z5/qJtfkJQsE0NkwVoCyeSh7IZGH61hChwYn3CEkh4bHolvUPxlQ+w==",
"requires": {
"@vue/compiler-dom": "3.1.2",
"@vue/runtime-dom": "3.1.2",
"@vue/shared": "3.1.2"
}
},
"vue-class-component": {
"version": "7.2.6",
@@ -10305,9 +10757,15 @@
"dev": true
},
"vue-i18n": {
"version": "8.24.4",
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-8.24.4.tgz",
"integrity": "sha512-RZE94WUAGxEiBAANxQ0pptbRwDkNKNSXl3fnJslpFOxVMF6UkUtMDSuYGuW2blDrVgweIXVpethOVkYoNNT9xw=="
"version": "9.1.6",
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.1.6.tgz",
"integrity": "sha512-FEC4HZkTH6QRIu/A0wlo0VS/GH3w/fuCC6xfvoC8IyhhtbG9A+go9NfW+HZ1ZXdAcO4EWcVQi04M+iSwuxgixw==",
"requires": {
"@intlify/core-base": "9.1.6",
"@intlify/shared": "9.1.6",
"@intlify/vue-devtools": "9.1.6",
"@vue/devtools-api": "^6.0.0-beta.7"
}
},
"vue-loader": {
"version": "15.9.7",
@@ -10330,99 +10788,13 @@
}
}
},
"vue-loader-v16": {
"version": "npm:vue-loader@16.2.0",
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.2.0.tgz",
"integrity": "sha512-TitGhqSQ61RJljMmhIGvfWzJ2zk9m1Qug049Ugml6QP3t0e95o0XJjk29roNEiPKJQBEi8Ord5hFuSuELzSp8Q==",
"dev": true,
"optional": true,
"requires": {
"chalk": "^4.1.0",
"hash-sum": "^2.0.0",
"loader-utils": "^2.0.0"
},
"dependencies": {
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"optional": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"chalk": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
"integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
"dev": true,
"optional": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"optional": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true,
"optional": true
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"optional": true
},
"loader-utils": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
"dev": true,
"optional": true,
"requires": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^2.1.2"
}
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"optional": true,
"requires": {
"has-flag": "^4.0.0"
}
}
}
},
"vue-property-decorator": {
"version": "8.5.1",
"resolved": "https://registry.npmjs.org/vue-property-decorator/-/vue-property-decorator-8.5.1.tgz",
"integrity": "sha512-O6OUN2OMsYTGPvgFtXeBU3jPnX5ffQ9V4I1WfxFQ6dqz6cOUbR3Usou7kgFpfiXDvV7dJQSFcJ5yUPgOtPPm1Q==",
"requires": {
"vue-class-component": "^7.1.0"
}
},
"vue-router": {
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.5.1.tgz",
"integrity": "sha512-RRQNLT8Mzr8z7eL4p7BtKvRaTSGdCbTy2+Mm5HTJvLGYSSeG9gDzNasJPP/yOYKLy+/cLG/ftrqq5fvkFwBJEw=="
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.0.10.tgz",
"integrity": "sha512-YbPf6QnZpyyWfnk7CUt2Bme+vo7TLfg1nGZNkvYqKYh4vLaFw6Gn8bPGdmt5m4qrGnKoXLqc4htAsd3dIukICA==",
"requires": {
"@vue/devtools-api": "^6.0.0-beta.14"
}
},
"vue-style-loader": {
"version": "4.1.3",
@@ -10442,16 +10814,6 @@
}
}
},
"vue-template-compiler": {
"version": "2.6.12",
"resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.12.tgz",
"integrity": "sha512-OzzZ52zS41YUbkCBfdXShQTe69j1gQDZ9HIX8miuC9C3rBCk9wIRjLiZZLrmX9V+Ftq/YEyv1JaVr5Y/hNtByg==",
"dev": true,
"requires": {
"de-indent": "^1.0.2",
"he": "^1.1.0"
}
},
"vue-template-es2015-compiler": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz",
@@ -10459,9 +10821,12 @@
"dev": true
},
"vuex": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/vuex/-/vuex-3.6.2.tgz",
"integrity": "sha512-ETW44IqCgBpVomy520DT5jf8n0zoCac+sxWnn+hMe/CzaSejb/eVw2YToiXYX+Ex/AuHHia28vWTq4goAexFbw=="
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/vuex/-/vuex-4.0.2.tgz",
"integrity": "sha512-M6r8uxELjZIK8kTKDGgZTYX/ahzblnzC4isU1tpmEuOIIKmV+TRdc+H4s8ds2NuZ7wpUTdGRzJRtoj+lI+pc0Q==",
"requires": {
"@vue/devtools-api": "^6.0.0-beta.11"
}
},
"vuex-class": {
"version": "0.3.2",
+2 -4
View File
@@ -12,10 +12,8 @@
"dotenv": "^8.6.0",
"firestore": "^1.1.6",
"howler": "^2.2.1",
"vue": "^2.6.12",
"vue": "^3.1.2",
"vue-class-component": "^7.2.6",
"vue-property-decorator": "^8.5.1",
"@vue/compat": "^3.1.0",
"vue-i18n": "^9.1.6",
"vue-router": "^4.0.0-0",
"vuex": "^4.0.0-0"
@@ -29,7 +27,7 @@
"axios": "^0.21.1",
"sass": "^1.32.13",
"sass-loader": "^8.0.2",
"typescript": "^3.9.9",
"typescript": "~4.1.5",
"@vue/compiler-sfc": "^3.1.0",
"vuex-class": "^0.3.2",
"vuex-module-decorators": "^0.17.0"
+162
View File
@@ -0,0 +1,162 @@
@import "./styles/responsive.scss";
@import "./styles/variables.scss";
@import "./styles/global.scss";
@import "./styles/scenery_status.scss";
:root {
--clr-primary: #ffc014;
--clr-secondary: #2f2f2f;
--clr-bg: #333;
--clr-accent: #1085b3;
--clr-accent2: #ff3d5d;
--clr-skr: #ff5100;
--clr-twr: #ffbb00;
}
// VUE ROUTE CHANGE ANIMATION
.view-anim {
&-enter-from,
&-leave-to {
opacity: 0.02;
}
&-enter-active,
&-leave-active {
transition: all $animDuration $animType;
min-height: 100%;
}
}
.route {
margin: 0 0.2em;
&-active {
color: $accentCol;
font-weight: bold;
}
}
// APP
.app {
background: $bgCol;
color: white;
overflow: hidden;
font-size: 1rem;
@include smallScreen() {
font-size: calc(0.45rem + 1vw);
}
}
// CONTAINER
.app_container {
display: flex;
flex-flow: column;
min-width: 0;
min-height: 100vh;
header {
flex: 0 0 auto;
}
main {
flex: 1 1 auto;
}
footer {
flex: 0 1 0.2em;
}
}
// HEADER
.app_header {
background: $primaryCol;
padding: 0.15em;
border-radius: 0 0 1em 1em;
display: flex;
justify-content: center;
}
.header {
&_brand {
position: relative;
width: 100%;
font-size: 4.25em;
text-align: center;
img {
width: 0.8em;
}
.brand_lang {
position: absolute;
right: 0;
transform: translate(110%, -35%);
img {
width: 0.6em;
}
cursor: pointer;
}
}
&_info {
display: flex;
justify-content: space-between;
font-size: 1.25em;
margin: 0 0.3em;
padding: 0.2em;
}
&_links {
display: flex;
justify-content: center;
border-radius: 0.7em;
font-size: 1.25em;
padding: 0.5em;
}
}
// COUNTER
.info_counter {
display: flex;
align-items: center;
color: $accentCol;
span {
margin: 0 0.15em;
}
img {
width: 1.35em;
}
}
// FOOTER
footer.app_footer {
max-width: 100%;
padding: 0.5em;
z-index: 10;
background: #111;
color: white;
text-align: center;
vertical-align: middle;
}
+213 -379
View File
@@ -1,379 +1,213 @@
git <template>
<div class="app">
<UpdateModal
:currentVersion="VERSION"
@toggleUpdateModal="toggleUpdateModal"
v-if="updateModalVisible"
/>
<div class="app_container">
<header class="app_header">
<div class="header_body">
<span class="header_brand">
<span>
<span>Stacj</span>
<img
src="@/assets/trainlogo.png"
alt="trainlogo"
/>
<span>wnik</span>
</span>
<span class="brand_lang">
<span
class="lang pl"
@click="changeLang('en')"
:class="{ current: currentLang == 'pl' }"
v-if="currentLang == 'pl'"
>
<img
:src="iconPL"
alt="icon-pl"
/>
</span>
<span
class="lang en"
@click="changeLang('pl')"
:class="{ current: currentLang == 'en' }"
v-if="currentLang == 'en'"
>
<img
:src="iconEN"
alt="icon-en"
/>
</span>
</span>
</span>
<span class="header_info">
<Clock />
<div class="info_counter">
<img
src="@/assets/icon-dispatcher.svg"
alt="icon dispatcher"
/>
<span>{{ data.activeStationCount }}</span>
<span>{{ data.activeTrainCount }}</span>
<img
src="@/assets/icon-train.svg"
alt="icon train"
/>
</div>
</span>
<span class="header_links">
<router-link
class="route"
active-class="route-active"
to="/"
exact
>{{ $t("app.sceneries") }}
</router-link>
/
<router-link
class="route"
active-class="route-active"
to="/trains"
>{{ $t("app.trains") }}
</router-link>
</span>
</div>
</header>
<main class="app_main">
<transition
name="view-anim"
mode="out-in"
>
<keep-alive>
<router-view />
</keep-alive>
</transition>
</main>
<footer class="app_footer">
&copy;
<a
href="https://td2.info.pl/profile/?u=20777"
target="_blank"
>
Spythere
</a>
2021 | v{{ VERSION }}
</footer>
</div>
</div>
</template>
<script lang="ts">
import { Vue, Component } from "vue-property-decorator";
import { Action, Getter } from "vuex-class";
import UpdateModal from "@/components/Global/UpdateModal.vue";
import Clock from "@/components/App/Clock.vue";
import StorageManager from "@/scripts/managers/storageManager";
import { StoreData } from "./scripts/interfaces/StoreData";
@Component({
components: { Clock, UpdateModal },
})
export default class App extends Vue {
@Action("synchronizeData") synchronizeData;
@Getter("getAllData") data!: StoreData;
private VERSION = "1.4.7";
hasReleaseNotes = false;
updateModalVisible = false;
currentLang = "pl";
iconEN = require("@/assets/icon-en.jpg");
iconPL = require("@/assets/icon-pl.svg");
toggleUpdateModal() {
this.updateModalVisible = !this.updateModalVisible;
StorageManager.setBooleanValue("version_notes_read", true);
}
changeLang(lang: string) {
this.$i18n.locale = lang;
this.currentLang = lang;
StorageManager.setStringValue("lang", lang);
}
loadLang() {
const storageLang = StorageManager.getStringValue("lang");
if (storageLang) {
this.changeLang(storageLang);
return;
}
if (!window.navigator.language) {
this.changeLang("pl");
return;
}
switch (window.navigator.language) {
case "pl-PL":
this.changeLang("pl");
break;
case "en-EN":
default:
this.changeLang("en");
break;
}
return;
}
created() {
this.loadLang();
this.synchronizeData();
}
mounted() {
if (this.detectIEVersion() != -1)
alert(
"Stacjownik nie wspiera reliktów przeszłości. Przesiądź się na nowszą przeglądarkę!"
);
if (StorageManager.getStringValue("version") != this.VERSION) {
StorageManager.setStringValue("version", this.VERSION);
if (this.hasReleaseNotes)
StorageManager.setBooleanValue("version_notes_read", false);
}
this.updateModalVisible =
this.hasReleaseNotes &&
!StorageManager.getBooleanValue("version_notes_read");
}
detectIEVersion() {
var rv = -1;
if (navigator.appName == "Microsoft Internet Explorer") {
var ua = navigator.userAgent;
var re = new RegExp("MSIE ([0-9]{1,}[\\.0-9]{0,})");
if (re.exec(ua) != null) rv = parseFloat(RegExp.$1);
} else if (navigator.appName == "Netscape") {
var ua = navigator.userAgent;
var re = new RegExp("Trident/.*rv:([0-9]{1,}[\\.0-9]{0,})");
if (re.exec(ua) != null) rv = parseFloat(RegExp.$1);
}
return rv;
}
}
</script>
<style lang="scss">
@import "./styles/responsive.scss";
@import "./styles/variables.scss";
@import "./styles/global.scss";
@import "./styles/scenery_status.scss";
:root {
--clr-primary: #ffc014;
--clr-secondary: #2f2f2f;
--clr-bg: #333;
--clr-accent: #1085b3;
--clr-accent2: #ff3d5d;
--clr-skr: #ff5100;
--clr-twr: #ffbb00;
}
// VUE ROUTE CHANGE ANIMATION
.view-anim {
&-enter,
&-leave-to {
opacity: 0.02;
}
&-enter-active,
&-leave-active {
transition: all $animDuration $animType;
min-height: 100%;
}
}
.route {
margin: 0 0.2em;
&-active {
color: $accentCol;
font-weight: bold;
}
}
// APP
.app {
background: $bgCol;
color: white;
overflow: hidden;
font-size: 1rem;
@include smallScreen() {
font-size: calc(0.45rem + 1vw);
}
}
// CONTAINER
.app_container {
display: flex;
flex-flow: column;
min-width: 0;
min-height: 100vh;
header {
flex: 0 0 auto;
}
main {
flex: 1 1 auto;
}
footer {
flex: 0 1 0.2em;
}
}
// HEADER
.app_header {
background: $primaryCol;
padding: 0.15em;
border-radius: 0 0 1em 1em;
display: flex;
justify-content: center;
}
.header {
&_brand {
position: relative;
width: 100%;
font-size: 4.25em;
text-align: center;
img {
width: 0.8em;
}
.brand_lang {
position: absolute;
right: 0;
transform: translate(110%, -35%);
img {
width: 0.6em;
}
cursor: pointer;
}
}
&_info {
display: flex;
justify-content: space-between;
font-size: 1.25em;
margin: 0 0.3em;
padding: 0.2em;
}
&_links {
display: flex;
justify-content: center;
border-radius: 0.7em;
font-size: 1.25em;
padding: 0.5em;
}
}
// COUNTER
.info_counter {
display: flex;
align-items: center;
color: $accentCol;
span {
margin: 0 0.15em;
}
img {
width: 1.35em;
}
}
// FOOTER
footer.app_footer {
max-width: 100%;
padding: 0.5em;
z-index: 10;
background: #111;
color: white;
text-align: center;
vertical-align: middle;
}
</style>
<template>
<div class="app">
<!-- <UpdateModal
:currentVersion="VERSION"
@toggleUpdateModal="toggleUpdateModal"
v-if="updateModalVisible"
/> -->
<div class="app_container">
<header class="app_header">
<div class="header_body">
<span class="header_brand">
<span>
<span>Stacj</span>
<img
src="@/assets/trainlogo.png"
alt="trainlogo"
/>
<span>wnik</span>
</span>
<span class="brand_lang">
<span
class="lang pl"
@click="changeLang('en')"
:class="{ current: currentLang == 'pl' }"
v-if="currentLang == 'pl'"
>
<img
:src="iconPL"
alt="icon-pl"
/>
</span>
<span
class="lang en"
@click="changeLang('pl')"
:class="{ current: currentLang == 'en' }"
v-if="currentLang == 'en'"
>
<img
:src="iconEN"
alt="icon-en"
/>
</span>
</span>
</span>
<span class="header_info">
<Clock />
<div class="info_counter">
<img
src="@/assets/icon-dispatcher.svg"
alt="icon dispatcher"
/>
<span>{{ data.activeStationCount }}</span>
<span>{{ data.activeTrainCount }}</span>
<img
src="@/assets/icon-train.svg"
alt="icon train"
/>
</div>
</span>
<span class="header_links">
<router-link
class="route"
active-class="route-active"
to="/"
exact
>{{ $t("app.sceneries") }}
</router-link>
/
<router-link
class="route"
active-class="route-active"
to="/trains"
>{{ $t("app.trains") }}
</router-link>
</span>
</div>
</header>
<main class="app_main">
<router-view v-slot="{ Component }">
<transition
name="view-anim"
mode="out-in"
>
<keep-alive>
<component :is="Component" />
</keep-alive>
</transition>
</router-view>
</main>
<footer class="app_footer">
&copy;
<a
href="https://td2.info.pl/profile/?u=20777"
target="_blank"
>
Spythere
</a>
2021 | v{{ VERSION }}
</footer>
</div>
</div>
</template>
<script lang="ts">
// import UpdateModal from "@/components/Global/UpdateModal.vue";
import Clock from "@/components/App/Clock.vue";
import StorageManager from "@/scripts/managers/storageManager";
import { computed, ComputedRef, defineComponent } from "vue";
import { GETTERS } from "./constants/storeConstants";
import { StoreData } from "./scripts/interfaces/StoreData";
import { useStore } from "./store";
// import { StoreData } from "./scripts/interfaces/StoreData";
export default defineComponent({
components: {
Clock,
},
setup() {
const store = useStore();
store.dispatch("synchronizeData");
const data: ComputedRef<StoreData> = computed(
() => store.getters[GETTERS.allData]
);
return {
data,
};
},
data: () => ({
VERSION: "1.4.7",
updateModalVisible: false,
hasReleaseNotes: false,
currentLang: "pl",
iconEN: require("@/assets/icon-en.jpg"),
iconPL: require("@/assets/icon-pl.svg"),
}),
created() {
this.loadLang();
},
mounted() {
if (StorageManager.getStringValue("version") != this.VERSION) {
StorageManager.setStringValue("version", this.VERSION);
if (this.hasReleaseNotes)
StorageManager.setBooleanValue("version_notes_read", false);
}
this.updateModalVisible =
this.hasReleaseNotes &&
!StorageManager.getBooleanValue("version_notes_read");
},
methods: {
toggleUpdateModal() {
this.updateModalVisible = !this.updateModalVisible;
StorageManager.setBooleanValue("version_notes_read", true);
},
changeLang(lang: string) {
this.$i18n.locale = lang;
this.currentLang = lang;
StorageManager.setStringValue("lang", lang);
},
loadLang() {
const storageLang = StorageManager.getStringValue("lang");
if (storageLang) {
this.changeLang(storageLang);
return;
}
if (!window.navigator.language) {
this.changeLang("pl");
return;
}
switch (window.navigator.language) {
case "pl-PL":
this.changeLang("pl");
break;
case "en-EN":
default:
this.changeLang("en");
break;
}
return;
},
},
});
</script>
<style lang="scss" src="./App.scss"></style>
+13 -12
View File
@@ -1,27 +1,28 @@
<template>
<div class="clock">{{ formattedDate }}</div>
<div class="clock">{{ computedDate }}</div>
</template>
<script lang="ts">
import Vue from "vue";
export default Vue.extend({
import { computed, defineComponent, ref } from "vue";
export default defineComponent({
name: "clock",
data: () => ({
timestamp: Date.now(),
}),
computed: {
formattedDate() {
return new Date(this.timestamp).toLocaleString("pl-PL", {
setup() {
let timestamp = ref(Date.now());
const computedDate = computed(() => new Date(timestamp.value).toLocaleString("pl-PL", {
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
});
},
},
mounted() {
setInterval(() => (this.timestamp = Date.now()), 1000);
},
}));
setInterval(() => (timestamp.value = Date.now()), 1000);
return { computedDate }
}
});
</script>
+4 -5
View File
@@ -3,12 +3,11 @@
</template>
<script lang="ts">
import { Vue, Component, Prop } from "vue-property-decorator";
import { defineComponent } from "@vue/runtime-core";
@Component
export default class Loading extends Vue {
@Prop() readonly message!: string;
}
export default defineComponent({
props: ["message"],
});
</script>
<style lang="scss" scoped>
+16 -5
View File
@@ -1,14 +1,23 @@
<template>
<section class="updates card" v-if="cardOpen">
<section
class="updates card"
v-if="cardOpen"
>
<h2>Ostatnie aktualizacje w Stacjowniku</h2>
<p>Tutaj będą pojawiać się informacje o kolejnych nowościach na stronie :)</p>
<ul>
<li v-for="(update, i) in updates" :key="i">
<li
v-for="(update, i) in updates"
:key="i"
>
<div>{{update.date}}</div>
<div>
<span v-for="(line, l) in content" :key="l">{{line}}</span>
<span
v-for="(line, l) in content"
:key="l"
>{{line}}</span>
</div>
</li>
</ul>
@@ -16,7 +25,9 @@
</template>
<script>
export default {
import { defineComponent } from "@vue/runtime-core";
export default defineComponent({
data() {
return {
updates: {
@@ -29,7 +40,7 @@ export default {
},
};
},
};
});
</script>
<style lang="scss" scoped>
+3 -7
View File
@@ -5,13 +5,12 @@
</template>
<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import { defineComponent } from "vue";
@Component
export default class ActionButton extends Vue {}
export default defineComponent({});
</script>
<style lang="scss" scoped>
<style lang="scss">
@import "../../styles/variables";
@import "../../styles/responsive";
@@ -40,15 +39,12 @@ export default class ActionButton extends Vue {}
img {
width: 1.25em;
vertical-align: middle;
margin-right: 0.35em;
}
p {
font-size: 1em;
overflow: hidden;
transition: max-width 0.35s ease-in-out;
}
&.open {
-66
View File
@@ -1,66 +0,0 @@
<template>
<div class="search-box">
<input v-model="searchedItem" :placeholder="title" />
<img
class="search-exit"
:src="exitIcon"
alt="exit-icon"
@click="() => (searchedItem = '')"
/>
</div>
</template>
<script lang="ts">
import { Component, Vue, Prop, Model } from "vue-property-decorator";
@Component
export default class SearchBox extends Vue {
@Prop({ required: true }) title!: string;
@Model("changed") readonly searchedItem!: string;
// @Emit("changed")
// onItemChanged() {
// return this.searchedItem;
// }
exitIcon = require("@/assets/icon-exit.svg");
// searchedItem: string = "";
// @Watch("searchedItem")
// watchSelectedItem(item) {
// this.onItemChanged();
// }
}
</script>
<style lang="scss" scoped>
.search {
&-box {
position: relative;
background: #333;
border-radius: 0.5em;
min-width: 200px;
}
&-exit {
position: absolute;
cursor: pointer;
top: 50%;
right: 10px;
transform: translateY(-50%);
width: 1em;
}
}
input {
border: none;
min-width: 85%;
padding: 0.35em 0.5em;
}
</style>
+81 -43
View File
@@ -1,12 +1,22 @@
<template>
<div class="select-box">
<div class="select-box_content">
<button class="selected" @click="toggleBox">
{{ selectedItemComp ? selectedItemComp.value : "" }}
<button
class="selected"
@click="toggleBox"
>
{{ computedSelectedItem.value }}
</button>
<div class="options" v-if="boxVisible">
<div class="option" v-for="item in itemList" :key="item.id">
<div
class="options"
v-if="boxVisible"
>
<div
class="option"
v-for="item in itemList"
:key="item.id"
>
<label :for="item.id">
<input
type="button"
@@ -15,7 +25,7 @@
@click="selectOption(item)"
/>
<span :style="selectedItemComp.id == item.id ? 'color: gold;' : ''">
<span :style="computedSelectedItem.id == item.id ? 'color: gold;' : ''">
{{ item.value }}
</span>
</label>
@@ -24,51 +34,79 @@
</div>
<div class="arrow">
<img :src="boxVisible ? ascIcon : descIcon" alt="arrow-icon" />
<img
:src="boxVisible ? ascIcon : descIcon"
alt="arrow-icon"
/>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue, Prop, Emit } from "vue-property-decorator";
import {
computed,
defineComponent,
defineEmit,
Ref,
ref,
} from "@vue/runtime-core";
@Component
export default class SelectBox extends Vue {
@Prop({ required: true }) itemList!: { id: string | number; value: string }[];
@Prop({ default: 0 }) defaultItemIndex!: number;
boxVisible = false;
test() {
console.log("test");
}
@Emit("selected")
onItemSelected() {
return this.selectedItem;
}
ascIcon = require("@/assets/icon-arrow-asc.svg");
descIcon = require("@/assets/icon-arrow-desc.svg");
selectedItem: { id: string | number; value: string } | null = null;
get selectedItemComp() {
if (!this.selectedItem) return this.itemList[this.defaultItemIndex];
return this.itemList.find((item) => item.id === this.selectedItem?.id);
}
toggleBox() {
this.boxVisible = !this.boxVisible;
}
selectOption(item: { id: string | number; value: string }) {
this.selectedItem = item;
this.boxVisible = false;
this.onItemSelected();
}
interface Item {
id: string | number;
value: string;
}
export default defineComponent({
emits: ["selected"],
props: {
itemList: {
type: Array as () => Item[],
required: true,
},
defaultItemIndex: {
type: Number,
default: 0,
},
},
data: () => ({
ascIcon: require("@/assets/icon-arrow-asc.svg"),
descIcon: require("@/assets/icon-arrow-desc.svg"),
}),
setup(props) {
let boxVisible = ref(false);
let selectedItem: Ref<Item> = ref(props.itemList[props.defaultItemIndex]);
const computedSelectedItem = computed(() => {
return (
props.itemList.find((item) => item.id === selectedItem.value.id) ||
props.itemList[props.defaultItemIndex]
);
});
return {
computedSelectedItem,
boxVisible,
selectedItem,
};
},
methods: {
selectOption(item: Item) {
this.selectedItem = item;
this.boxVisible = false;
this.$emit("selected", item);
},
toggleBox() {
this.boxVisible = !this.boxVisible;
},
},
});
</script>
<style lang="scss" scoped>
-97
View File
@@ -1,97 +0,0 @@
<template>
<div class="modal">
<div class="header">
<span>Stacj</span>
<img src="@/assets/trainlogo.png" alt="trainlogo" />
<span>wnik</span>
<sup style="font-size: 0.5em; margin-left: 10px;" class="title">1.4</sup>
</div>
<div class="title">Dziennik Aktywności Scenerii dostępny w wersji beta!</div>
<div class="content">
Do użytku został oddany Dziennik Aktywności Scenerii, który pozwala na dostęp do informacji kto i kiedy dyżurował na danej stacji.
Aby przejść do zakładki z dziennikiem wystarczy wybrać opcję "DZIENNIK" w menu na górze strony. Funkcjonalność ta jest nadal w trakcie prac,
więc informacje, które pokazuje, mogą być niepoprawne, a dane kasowane w ramach dalszych testów.
<div style="text-align: center; font-weight: bold; margin: 0.5em 0;">Miłego korzystania!</div>
</div>
<button class="button" @click="toggleUpdateModal">PRZYJĄŁEM!</button>
</div>
</template>
<script lang="ts">
import { Component, Vue, Prop } from "vue-property-decorator";
@Component
export default class UpdateModal extends Vue {
@Prop() currentVersion!: string;
STORAGE_ID = "modal_update";
toggleUpdateModal(type: string) {
this.$emit("toggleUpdateModal");
}
}
</script>
<style lang="scss" scoped>
@import "../../styles/responsive";
.modal {
z-index: 100;
padding: 1em;
border-radius: 1em;
position: fixed;
top: 50%;
left: 50%;
width: 65%;
max-width: 950px;
max-height: 95vh;
overflow: auto;
transform: translate(-50%, -50%);
background: rgba(black, 0.85);
color: white;
text-align: center;
@include smallScreen() {
font-size: 0.8em;
width: 95%;
}
}
.header {
font-size: 4.5em;
img {
width: 0.8em;
}
}
.title {
font-size: 2em;
}
.content {
font-size: 1.4em;
text-align: justify;
ul {
list-style: square inside;
}
}
.button {
font-size: 1.25em;
margin: 0 auto;
}
</style>
+79 -41
View File
@@ -7,12 +7,14 @@
:href="stationInfo.stationURL"
target="_blank"
rel="noopener noreferrer"
>{{ stationInfo.stationName }}</a
>
>{{ stationInfo.stationName }}</a>
<span v-else>{{ stationInfo.stationName }}</span>
</div>
<div class="scenery-hash" v-if="stationInfo.stationHash">
<div
class="scenery-hash"
v-if="stationInfo.stationHash"
>
#{{ stationInfo.stationHash }}
</div>
</div>
@@ -23,24 +25,36 @@
:class="!stationInfo.stationHash ? 'no-stats' : ''"
>
<span class="likes">
<img :src="likeIcon" alt="icon-like" />
<img
:src="likeIcon"
alt="icon-like"
/>
<span>{{ stationInfo.dispatcherRate }}</span>
</span>
<span class="users">
<img :src="userIcon" alt="icon-user" />
<img
:src="userIcon"
alt="icon-user"
/>
<span>{{ stationInfo.currentUsers }}</span>
/
<span>{{ stationInfo.maxUsers }}</span>
</span>
<span class="spawns">
<img :src="spawnIcon" alt="icon-spawn" />
<img
:src="spawnIcon"
alt="icon-spawn"
/>
<span>{{ stationInfo.spawns.length }}</span>
</span>
<span class="schedules">
<img :src="timetableIcon" alt="icon-timetable" />
<img
:src="timetableIcon"
alt="icon-timetable"
/>
<span v-if="stationInfo.scheduledTrains">
<span style="color: #eee">{{
stationInfo.scheduledTrains.length
@@ -111,7 +125,10 @@
</div>
<div class="info-dispatcher">
<div class="dispatcher" v-if="stationInfo.stationHash">
<div
class="dispatcher"
v-if="stationInfo.stationHash"
>
<span
class="dispatcher_level"
:style="
@@ -129,7 +146,10 @@
<span class="dispatcher_name">{{ stationInfo.dispatcherName }}</span>
</div>
<span class="status-badge" :class="stationInfo.statusID">
<span
class="status-badge"
:class="stationInfo.statusID"
>
{{ $t(`status.${stationInfo.statusID}`) }}
{{
stationInfo.statusID == "online" ? stationInfo.statusTimeString : ""
@@ -141,7 +161,10 @@
<div class="user-list">
<h3 class="user-header">
{{ $t("scenery.users") }}
<img :src="userIcon" alt="icon-user" />
<img
:src="userIcon"
alt="icon-user"
/>
</h3>
<div
@@ -166,7 +189,10 @@
<div class="spawn-list">
<h3 class="spawn-header">
{{ $t("scenery.spawns") }}
<img :src="spawnIcon" alt="icon-spawn" />
<img
:src="spawnIcon"
alt="icon-spawn"
/>
</h3>
<span
@@ -181,7 +207,7 @@
<span
class="spawn none"
v-if="!stationInfo.spawns || stationInfo.spawns.length == 0"
>{{ $t("scenery.no-spawns") }}
>{{ $t("scenery.no-spawns") }}
</span>
</div>
</div>
@@ -190,44 +216,56 @@
</template>
<script lang="ts">
import { Component, Prop } from "vue-property-decorator";
import Station from "@/scripts/interfaces/Station";
import styleMixin from "@/mixins/styleMixin";
import { computed, defineComponent } from "@vue/runtime-core";
@Component
export default class SceneryInfo extends styleMixin {
@Prop() readonly stationInfo!: Station;
@Prop() readonly timetableOnly!: boolean;
export default defineComponent({
props: {
stationInfo: {
type: Object as () => Station,
},
likeIcon: string = require("@/assets/icon-like.svg");
spawnIcon: string = require("@/assets/icon-spawn.svg");
timetableIcon: string = require("@/assets/icon-timetable.svg");
userIcon: string = require("@/assets/icon-user.svg");
timetableOnly: Boolean,
},
get computedStationTrains() {
if (!this.stationInfo) return null;
mixins: [styleMixin],
return this.stationInfo.stationTrains.map((stationTrain) => {
const scheduledData = this.stationInfo?.scheduledTrains.find(
(scheduledTrain) => scheduledTrain.trainNo === stationTrain.trainNo
);
data: () => ({
likeIcon: require("@/assets/icon-like.svg"),
spawnIcon: require("@/assets/icon-spawn.svg"),
timetableIcon: require("@/assets/icon-timetable.svg"),
userIcon: require("@/assets/icon-user.svg"),
}),
return {
...stationTrain,
stopStatus: scheduledData?.stopStatus || "no-timetable",
};
setup(props) {
const computedStationTrains = computed(() => {
if (!props.stationInfo) return [];
return props.stationInfo.stationTrains.map((train) => {
const scheduledTrainStatus = props.stationInfo?.scheduledTrains.find(
(st) => st.trainNo === train.trainNo
);
return {
...train,
stopStatus: scheduledTrainStatus?.stopStatus || "no-timetable",
};
});
});
}
navigateToTrain(trainNo: number) {
this.$router.push({
name: "TrainsView",
params: { queryTrain: trainNo.toString() },
});
}
}
return { computedStationTrains };
},
methods: {
navigateToTrain(trainNo: number) {
this.$router.push({
name: "TrainsView",
params: { queryTrain: trainNo.toString() },
});
},
},
});
</script>
<style lang="scss" scoped>
+100 -72
View File
@@ -25,11 +25,14 @@
value: cp.checkpointName,
}))
"
@selected="chooseOption"
@selected="selectCheckpoint"
></select-box>
</div>
<span class="timetable-item loading" v-if="dataStatus == 0">{{
<span
class="timetable-item loading"
v-if="dataStatus == 0"
>{{
$t("app.loading")
}}</span>
@@ -48,14 +51,12 @@
>
<span class="timetable-general">
<span class="general-info">
<router-link
:to="{
<router-link :to="{
name: 'TrainsView',
params: {
queryTrain: scheduledTrain.trainNo.toString(),
},
}"
>
}">
<span>
<strong>{{ scheduledTrain.category }}</strong>
{{ scheduledTrain.trainNo }}
@@ -68,21 +69,17 @@
'https://td2.info.pl/profile/?u=' + scheduledTrain.driverId
"
target="_blank"
>{{ scheduledTrain.driverName }}</a
>
>{{ scheduledTrain.driverName }}</a>
</span>
<div class="info-route">
<strong
>{{ scheduledTrain.beginsAt }} -
{{ scheduledTrain.terminatesAt }}</strong
>
<strong>{{ scheduledTrain.beginsAt }} -
{{ scheduledTrain.terminatesAt }}</strong>
</div>
</span>
<span class="general-status">
<span :class="scheduledTrain.stopStatus"
>{{ $t(`timetables.${scheduledTrain.stopStatus}`) }}
<span :class="scheduledTrain.stopStatus">{{ $t(`timetables.${scheduledTrain.stopStatus}`) }}
</span>
</span>
</span>
@@ -95,7 +92,10 @@
v-html="$t('timetables.begins')"
>
</span>
<span class="arrival-time" v-else>
<span
class="arrival-time"
v-else
>
{{ scheduledTrain.stopInfo.arrivalTimeString }} ({{
scheduledTrain.stopInfo.arrivalDelay
}})
@@ -103,7 +103,10 @@
</span>
<span class="schedule-stop">
<span class="stop-time" v-if="scheduledTrain.stopInfo.stopTime">
<span
class="stop-time"
v-if="scheduledTrain.stopInfo.stopTime"
>
{{ scheduledTrain.stopInfo.stopTime }}
{{ scheduledTrain.stopInfo.stopType }}
</span>
@@ -116,7 +119,10 @@
v-html="$t('timetables.terminates')"
>
</span>
<span class="departure-time" v-else>
<span
class="departure-time"
v-else
>
{{ scheduledTrain.stopInfo.departureTimeString }} ({{
scheduledTrain.stopInfo.departureDelay
}})
@@ -129,74 +135,96 @@
</template>
<script lang="ts">
import { Component, Vue, Prop } from "vue-property-decorator";
import Station from "@/scripts/interfaces/Station";
import ScheduledTrain from "@/scripts/interfaces/ScheduledTrain";
import SelectBox from "../Global/SelectBox.vue";
import { computed, defineComponent, ref } from "@vue/runtime-core";
import { useRoute } from "vue-router";
@Component({ components: { SelectBox } })
export default class SceneryTimetable extends Vue {
@Prop() readonly stationInfo!: Station;
@Prop() readonly timetableOnly!: boolean;
@Prop() readonly dataStatus!: number;
export default defineComponent({
components: { SelectBox },
viewIcon: string = require("@/assets/icon-view.svg");
props: {
stationInfo: {
type: Object as () => Station,
},
timetableOnly: {
type: Boolean,
},
dataStatus: {
type: Number,
},
},
listOpen: boolean = false;
selectedOption: string = "";
data: () => ({
viewIcon: require("@/assets/icon-view.svg"),
listOpen: false,
}),
loadSelectedOption() {
if (!this.stationInfo) return;
if (!this.stationInfo.checkpoints) return;
if (this.selectedOption != "") return;
setup(props) {
const route = useRoute();
const currentURL = computed(() => `${location.origin}${route.fullPath}`);
this.selectedOption = this.stationInfo.checkpoints[0].checkpointName;
}
const selectedCheckpoint = ref("");
const computedScheduledTrains = computed(() => {
if (!props.stationInfo) return [];
let scheduledTrains = props.stationInfo.checkpoints?.find(
(cp) => cp.checkpointName === selectedCheckpoint.value
)?.scheduledTrains;
// if (props.stationInfo.checkpoints)
// scheduledTrains = props.stationInfo.checkpoints.find(
// (cp) => cp.checkpointName === selectedCheckpoint.value
// )?.scheduledTrains;
// else scheduledTrains = props.stationInfo.scheduledTrains;
return (
scheduledTrains?.sort((a, b) => {
if (a.stopStatusID > b.stopStatusID) return 1;
else if (a.stopStatusID < b.stopStatusID) return -1;
if (a.stopInfo.arrivalTimestamp > b.stopInfo.arrivalTimestamp)
return 1;
else if (a.stopInfo.arrivalTimestamp < b.stopInfo.arrivalTimestamp)
return -1;
return a.stopInfo.departureTimestamp > b.stopInfo.departureTimestamp
? 1
: -1;
}) || []
);
});
return {
currentURL,
selectedCheckpoint,
computedScheduledTrains,
};
},
methods: {
loadSelectedOption() {
if (!this.stationInfo) return;
if (!this.stationInfo.checkpoints) return;
if (this.selectedCheckpoint != "") return;
this.selectedCheckpoint = this.stationInfo.checkpoints[0].checkpointName;
},
selectCheckpoint(item: { id: number | string; value: string }) {
this.selectedCheckpoint = item.value;
},
},
mounted() {
this.loadSelectedOption();
}
},
activated() {
this.loadSelectedOption();
}
chooseOption(item: { id: number | string; value: string }) {
this.selectedOption = item.value;
}
get currentURL() {
return `${location.origin}${this.$route.fullPath}`;
}
get computedScheduledTrains() {
if (!this.stationInfo) return [];
let scheduledTrains: ScheduledTrain[] | undefined;
if (this.stationInfo.checkpoints)
scheduledTrains = this.stationInfo.checkpoints.find(
(cp) => cp.checkpointName === this.selectedOption
)?.scheduledTrains;
else scheduledTrains = this.stationInfo.scheduledTrains;
return (
scheduledTrains?.sort((a, b) => {
if (a.stopStatusID > b.stopStatusID) return 1;
else if (a.stopStatusID < b.stopStatusID) return -1;
if (a.stopInfo.arrivalTimestamp > b.stopInfo.arrivalTimestamp) return 1;
else if (a.stopInfo.arrivalTimestamp < b.stopInfo.arrivalTimestamp)
return -1;
return a.stopInfo.departureTimestamp > b.stopInfo.departureTimestamp
? 1
: -1;
}) || []
);
}
}
},
});
</script>
<style lang="scss" scoped>
+64 -59
View File
@@ -1,5 +1,8 @@
<template>
<section class="card">
<section
class="card"
v-if="showCard"
>
<div
class="card-exit"
@click="exit"
@@ -79,13 +82,13 @@
<div class="card-actions flex">
<action-button
class="outlined"
@click.native="resetFilters"
@click="resetFilters"
>
{{ $t("filters.reset") }}
</action-button>
<action-button
class="outlined"
@click.native="exit"
@click="exit"
>{{
$t("filters.close")
}}</action-button>
@@ -94,83 +97,85 @@
</template>
<script lang="ts">
import { Vue, Component, Prop } from "vue-property-decorator";
import inputData from "@/data/options.json";
import StorageManager from "@/scripts/managers/storageManager";
import { defineComponent } from "@vue/runtime-core";
import ActionButton from "../Global/ActionButton.vue";
@Component({ components: { ActionButton } })
export default class FilterCard extends Vue {
inputs = { ...inputData };
saveOptions: boolean = false;
STORAGE_KEY: string = "options_saved";
export default defineComponent({
components: { ActionButton },
props: ["showCard", "exit"],
@Prop() exit!: () => void;
data: () => ({
inputs: { ...inputData },
saveOptions: false,
STORAGE_KEY: "options_saved",
}),
mounted() {
this.saveOptions = StorageManager.isRegistered(this.STORAGE_KEY);
}
},
handleChange(e: Event): void {
const target = <HTMLInputElement>e.target;
methods: {
handleChange(e: Event) {
const target = e.target as HTMLInputElement;
this.$emit("changeFilterValue", {
name: target.name,
value: !target.checked,
});
this.$emit("changeFilterValue", {
name: target.name,
value: !target.checked,
});
if (this.saveOptions)
StorageManager.setBooleanValue(target.name, target.checked);
},
if (this.saveOptions)
StorageManager.setBooleanValue(target.name, target.checked);
}
handleInput(e: Event) {
const target = e.target as HTMLInputElement;
handleInput(e: Event): void {
const target = <HTMLInputElement>e.target;
this.$emit("changeFilterValue", {
name: target.name,
value: target.value,
});
this.$emit("changeFilterValue", {
name: target.name,
value: target.value,
});
if (this.saveOptions)
StorageManager.setStringValue(target.name, target.value);
},
if (this.saveOptions)
StorageManager.setStringValue(target.name, target.value);
}
saveFilters() {
if (!this.saveOptions) {
StorageManager.unregisterStorage(this.STORAGE_KEY);
return;
}
saveFilters(): void {
if (!this.saveOptions) {
StorageManager.unregisterStorage(this.STORAGE_KEY);
return;
}
StorageManager.registerStorage(this.STORAGE_KEY);
StorageManager.registerStorage(this.STORAGE_KEY);
this.inputs.options.forEach((option) =>
StorageManager.setBooleanValue(option.name, option.value)
);
this.inputs.options.forEach((option) =>
StorageManager.setBooleanValue(option.name, option.value)
);
this.inputs.sliders.forEach((slider) =>
StorageManager.setNumericValue(slider.name, slider.value)
);
},
this.inputs.sliders.forEach((slider) =>
StorageManager.setNumericValue(slider.name, slider.value)
);
}
resetFilters() {
this.inputs.options.forEach((option) => {
option.value = option.defaultValue;
StorageManager.setBooleanValue(option.name, option.value);
});
resetFilters(): void {
this.inputs.options.forEach((option) => {
option.value = option.defaultValue;
StorageManager.setBooleanValue(option.name, option.value);
});
this.inputs.sliders.forEach((slider) => {
slider.value = slider.defaultValue;
StorageManager.setNumericValue(slider.name, slider.value);
});
this.inputs.sliders.forEach((slider) => {
slider.value = slider.defaultValue;
StorageManager.setNumericValue(slider.name, slider.value);
});
this.$emit("resetFilters");
},
this.$emit("resetFilters");
}
closeCard(): void {
this.exit();
}
}
closeCard() {
this.exit();
},
},
});
</script>
<style lang="scss" scoped>
-139
View File
@@ -1,139 +0,0 @@
<template>
<div class="options">
<div class="options-actions">
<button
class="action-btn"
:class="{'open': filterCardOpen}"
@click="() => toggleCardsState('filter')"
>
<img :src="require('@/assets/icon-filter2.svg')" alt="icon-filter" />
<p>FILTRY</p>
</button>
<button
class="action-btn"
:class="{'open': legendCardOpen}"
@click="() => toggleCardsState('legend')"
>
<img :src="require('@/assets/icon-legend.svg')" alt="icon legend" />
<p>LEGENDA</p>
</button>
</div>
<div class="options-content">
<transition name="card-anim"></transition>
</div>
</div>
</template>
<script lang="ts">
import { Vue, Component } from "vue-property-decorator";
@Component({})
export default class Options extends Vue {
filterCardOpen: boolean = false;
legendCardOpen: boolean = false;
toggleCardsState(name: string): void {
if (name == "filter") {
this.legendCardOpen = false;
this.filterCardOpen = !this.filterCardOpen;
}
if (name == "legend") {
this.filterCardOpen = false;
this.legendCardOpen = !this.legendCardOpen;
}
}
toggleCardState(): void {
this.filterCardOpen = !this.filterCardOpen;
}
toggleLegendCardState(): void {
this.legendCardOpen = !this.legendCardOpen;
}
}
</script>
<style lang="scss" scoped>
@import "../../styles/variables.scss";
@import "../../styles/responsive.scss";
.options {
display: flex;
z-index: 5;
}
.card-anim {
&-enter-active,
&-leave-active {
transition: all 0.25s ease-in-out;
}
&-enter,
&-leave-to {
transform: translate(-45%, -50%);
opacity: 0;
}
}
.options {
&-actions {
display: flex;
}
}
.action-btn {
display: flex;
align-items: center;
background: #333;
border: none;
color: #e0e0e0;
font-size: 0.4em;
padding: 0.3em;
outline: none;
cursor: pointer;
transition: all 0.3s;
img {
width: 1.3em;
margin-right: 0.2em;
}
p {
max-width: 0;
font-size: 1em;
overflow: hidden;
transition: max-width 0.35s ease-in-out;
}
&:hover > p,
&.open > p {
max-width: 500px;
color: $accentCol;
}
&:hover {
background: rgba(#e0e0e0, 0.4);
}
}
@include smallScreen {
.options {
display: flex;
justify-content: center;
}
.action-btn {
font-size: 0.8rem;
}
}
</style>
+69 -60
View File
@@ -160,6 +160,7 @@
$t('desc.signals-type') + $t(`signals.${station.signalType}`)
"
/>
<img
v-if="station.SBL && station.SBL !== ''"
:src="require(`@/assets/icon-SBL.svg`)"
@@ -236,80 +237,87 @@
</template>
<script lang="ts">
import { Component, Prop } from "vue-property-decorator";
import Station from "@/scripts/interfaces/Station";
import styleMixin from "@/mixins/styleMixin";
import { Getter } from "vuex-class";
import Options from "@/components/StationsView/Options.vue";
import { StoreData } from "@/scripts/interfaces/StoreData";
import { DataStatus } from "@/scripts/enums/DataStatus";
import { computed, ComputedRef, defineComponent } from "@vue/runtime-core";
import { useStore } from "@/store";
import { GETTERS } from "@/constants/storeConstants";
import Station from "@/scripts/interfaces/Station";
@Component({
components: { Options },
})
export default class StationTable extends styleMixin {
@Prop() readonly stations!: Station[];
@Prop() readonly sorterActive!: number;
export default defineComponent({
props: {
stations: {
type: Array as () => Station[],
required: true,
},
@Prop() readonly setFocusedStation!: () => void;
@Prop() readonly changeSorter!: () => void;
sorterActive: {
type: Object as () => { id: string; dir: number },
required: true,
},
@Getter("getAllData") storeAPIData!: StoreData;
setFocusedStation: Function,
changeSorter: Function,
},
likeIcon: string = require("@/assets/icon-like.svg");
spawnIcon: string = require("@/assets/icon-spawn.svg");
timetableIcon: string = require("@/assets/icon-timetable.svg");
userIcon: string = require("@/assets/icon-user.svg");
trainIcon: string = require("@/assets/icon-train.svg");
mixins: [styleMixin],
ascIcon: string = require("@/assets/icon-arrow-asc.svg");
descIcon: string = require("@/assets/icon-arrow-desc.svg");
data: () => ({
likeIcon: require("@/assets/icon-like.svg"),
spawnIcon: require("@/assets/icon-spawn.svg"),
timetableIcon: require("@/assets/icon-timetable.svg"),
userIcon: require("@/assets/icon-user.svg"),
trainIcon: require("@/assets/icon-train.svg"),
headIds = [
"station",
"min-lvl",
"status",
"dispatcher",
"dispatcher-lvl",
"routes",
"general",
];
ascIcon: require("@/assets/icon-arrow-asc.svg"),
descIcon: require("@/assets/icon-arrow-desc.svg"),
headIconsIds = ["user", "spawn", "timetable"];
headIds: [
"station",
"min-lvl",
"status",
"dispatcher",
"dispatcher-lvl",
"routes",
"general",
],
headTitles: string[][] = [
["Stacja"],
["Min. poziom", "dyżurnego"],
["Status"],
["Dyżurny"],
["Poziom", "dyżurnego"],
["Szlaki", "2tor | 1tor"],
["Informacje", "ogólne"],
[this.userIcon, "Mechanicy online"],
[this.spawnIcon, "Otwarte spawny"],
[this.timetableIcon, "Aktywne RJ"],
];
headIconsIds: ["user", "spawn", "timetable"],
}),
setScenery(name: string) {
const station = this.stations.find(
(station) => station.stationName === name
setup() {
const store = useStore();
const dataConnectionStatus: ComputedRef<DataStatus> = computed(
() => store.getters[GETTERS.dataStatus]
);
if (!station) return;
const isDataLoaded = () =>
computed(() => {
dataConnectionStatus.value == DataStatus.Loaded;
});
this.$router.push({
name: "SceneryView",
query: { station: station.stationName.replaceAll(" ", "_") },
});
}
return {
isDataLoaded,
};
},
get isDataLoaded() {
return this.storeAPIData.dataConnectionStatus == DataStatus.Loaded;
}
}
methods: {
setScenery(name: string) {
const station = this.stations.find(
(station) => station.stationName === name
);
if (!station) return;
this.$router.push({
name: "SceneryView",
query: { station: station.stationName.replaceAll(" ", "_") },
});
},
},
});
</script>
<style lang="scss" scoped>
@@ -358,6 +366,7 @@ table {
padding: 0.5em;
background-color: $primaryCol;
white-space: pre-wrap;
cursor: pointer;
user-select: none;
@@ -442,8 +451,8 @@ td.station {
}
.track {
margin: 0 0.3em;
padding: 0.35em 0.1em;
margin: 0 0.35em;
padding: 0.35em;
font-size: 1.05em;
white-space: pre-wrap;
}
@@ -1,286 +0,0 @@
<template>
<div class="station-timetable">
<div class="timetable-wrapper">
<div class="timetable-title title">
<div style="font-size: 1.5em">{{ stationName.toUpperCase() }}</div>
<div style="font-size: 0.7em">AKTYWNE ROZKŁADY JAZDY</div>
</div>
<div class="timetable-content">
<div
class="timetable-item"
v-for="(scheduledTrain, i) in computedScheduledTrains"
:key="i"
>
<span class="timetable-general">
<span class="general-info">
<router-link
:to="{
name: 'TrainsView',
params: {
passedSearchedTrain: scheduledTrain.trainNo.toString(),
},
}"
>
<span>
<strong>{{ scheduledTrain.category }}</strong>
{{ scheduledTrain.trainNo }}
</span>
</router-link>
|
<span>
<a
:href="
'https://td2.info.pl/profile/?u=' + scheduledTrain.driverId
"
target="_blank"
>{{ scheduledTrain.driverName }}</a
>
</span>
</span>
<span class="general-status">
<span :class="scheduledTrain.stopStatus">{{
scheduledTrain.stopLabel
}}</span>
</span>
</span>
<span class="timetable-schedule">
<span class="schedule-arrival">
<span
class="arrival-time begins"
v-if="scheduledTrain.stopInfo.beginsHere"
>ROZPOCZYNA BIEG</span
>
<span class="arrival-time" v-else
>{{ scheduledTrain.stopInfo.arrivalTimeString }} ({{
scheduledTrain.stopInfo.arrivalDelay
}})</span
>
</span>
<span class="schedule-stop">
<span class="stop-time" v-if="scheduledTrain.stopInfo.stopTime"
>{{ scheduledTrain.stopInfo.stopTime }}
{{ scheduledTrain.stopInfo.stopType }}</span
>
<span class="stop-arrow arrow"></span>
</span>
<span class="schedule-departure">
<span
class="departure-time terminates"
v-if="scheduledTrain.stopInfo.terminatesHere"
>KOŃCZY BIEG</span
>
<span class="departure-time" v-else
>{{ scheduledTrain.stopInfo.departureTimeString }} ({{
scheduledTrain.stopInfo.departureDelay
}})</span
>
</span>
</span>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue, Prop } from "vue-property-decorator";
@Component
export default class StationTimetable extends Vue {
@Prop() readonly scheduledTrains;
@Prop() readonly stationName;
get computedScheduledTrains() {
return this.scheduledTrains.sort((a, b) => {
if (a.stopInfo.arrivalTimestamp > b.stopInfo.arrivalTimestamp) return 1;
else if (a.stopInfo.arrivalTimestamp < b.stopInfo.arrivalTimestamp)
return -1;
return a.stopInfo.departureTimestamp > b.stopInfo.departureTimestamp
? 1
: -1;
});
}
}
</script>
<style lang="scss" scoped>
@import "../../styles/variables.scss";
@import "../../styles/responsive.scss";
.station-timetable {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: hidden;
transform: translateY(-100%);
-webikit-transform: translateY(-100%);
&.show {
transform: translateY(0);
-webkit-transform: translateY(0);
}
transition: transform 150ms ease-out;
background: #333;
@include smallScreen() {
font-size: 1.3em;
}
}
.timetable {
&-content {
width: 100%;
height: 100%;
overflow: auto;
}
&-title {
padding-top: 2rem;
padding-bottom: 0.3rem;
font-size: 1.6em;
}
&-wrapper {
height: 100%;
display: flex;
flex-direction: column;
}
&-item {
margin: 1em auto;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
padding: 0 1rem;
@include smallScreen() {
display: flex;
flex-direction: column;
align-items: center;
}
}
}
.timetable {
&-general {
padding: 0.3rem 0.7rem;
border: 2px solid white;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: space-between;
@include smallScreen() {
width: 95%;
font-size: 0.85em;
}
}
&-schedule {
@include smallScreen() {
width: 80%;
margin: 0.7em 0;
font-size: 0.9em;
}
display: grid;
grid-template-columns: repeat(auto-fit, minmax(50px, 1fr));
font-size: 1.35em;
}
}
.arrow {
border: solid white;
border-width: 0 2px 2px 0;
display: inline-block;
padding: 2px;
margin-left: 50px;
position: relative;
transform: rotate(-45deg);
&::before {
content: "";
position: absolute;
display: block;
width: 55px;
height: 3px;
top: 4px;
left: 4px;
transform: translate(-100%, -1px) rotate(45deg);
transform-origin: right bottom;
background: white;
}
}
.general-info {
span {
color: $accentCol;
}
}
.general-status {
span.arriving {
color: #aaa;
}
span.departed {
color: lime;
}
span.stopped {
color: #ffa600;
}
span.online {
color: gold;
}
span.terminated {
color: red;
}
}
.schedule {
&-arrival,
&-stop,
&-departure {
display: flex;
justify-content: center;
align-items: center;
margin: 0 0.3rem;
}
&-stop {
display: flex;
flex-direction: column;
.stop-time {
font-size: 0.7em;
}
}
}
.arrival-time.begins,
.departure-time.terminates {
font-size: 0.75em;
}
</style>
+73 -71
View File
@@ -42,96 +42,98 @@
</template>
<script lang="ts">
import { Component, Vue, Watch, Prop, Emit } from "vue-property-decorator";
import { computed, defineComponent } from "vue";
import { useI18n } from "vue-i18n";
import SelectBox from "../Global/SelectBox.vue";
@Component({ components: { SelectBox } })
export default class TrainOptions extends Vue {
// Passed as component parameters
@Prop() readonly queryTrain!: string;
export default defineComponent({
components: { SelectBox },
props: ["queryTrain"],
emits: ["changeSearchedTrain", "changeSearchedDriver", "changeSorter"],
exitIcon = require("@/assets/icon-exit.svg");
data: () => ({
exitIcon: require("@/assets/icon-exit.svg"),
searchedTrain: "",
searchedDriver: "",
}),
searchedTrain = "";
searchedDriver = "";
setup() {
const { t } = useI18n();
sorterOptions: { id: string; value: string }[] = [
{
id: "mass",
value: "masa",
},
{
id: "speed",
value: "prędkość",
},
{
id: "length",
value: "długość",
},
{
id: "distance",
value: "kilometraż",
},
{
id: "timetable",
value: "numer pociągu",
},
];
const sorterOptions = [
{
id: "mass",
value: "masa",
},
{
id: "speed",
value: "prędkość",
},
{
id: "length",
value: "długość",
},
{
id: "distance",
value: "kilometraż",
},
{
id: "timetable",
value: "numer pociągu",
},
];
get translatedSorterOptions() {
return this.sorterOptions.map((option) => ({
id: option.id,
value: this.$t(`trains.option-${option.id}`),
}));
}
const translatedSorterOptions = computed(() =>
sorterOptions.map(({ id }) => ({
id,
value: t(`trains.option-${id}`),
}))
);
return {
translatedSorterOptions,
};
},
mounted() {
if (this.queryTrain) {
this.searchedTrain = this.queryTrain;
this.searchedDriver = "";
}
}
},
/* Emitters to TrainsView managing variables */
methods: {
chooseTrain(train: string) {
this.$emit("changeSearchedTrain", train);
},
@Emit("changeSearchedTrain")
chooseTrain(train: string) {
return train;
}
chooseDriver(driverName: string) {
this.$emit("changeSearchedDriver", driverName);
},
@Emit("changeSearchedDriver")
chooseDriver(driverName: string) {
return driverName;
}
changeSorter(item: { id: string | number; value: string }) {
this.$emit("changeSorter", { id: item.id, dir: -1 });
},
},
@Emit()
changeSorter(item: { id: string | number; value: string }) {
return { id: item.id, dir: -1 };
}
watch: {
searchedTrain(value: string) {
this.chooseTrain(value);
},
/* Watchers for search boxes */
searchedDriver(value: string) {
this.chooseDriver(value);
},
@Watch("searchedTrain")
watchSearchedTrain(train: string) {
this.chooseTrain(train);
}
queryTrain(train: string) {
if (!train) return;
if (train == "") return;
@Watch("searchedDriver")
watchSearchedDriver(driver: string) {
this.chooseDriver(driver);
}
/* Watcher for train no passed in link params */
@Watch("queryTrain")
onQueryTrainChanged(train: string | undefined) {
if (!train) return;
if (train == "") return;
this.searchedTrain = train;
this.searchedDriver = "";
}
}
this.searchedTrain = train;
this.searchedDriver = "";
},
},
});
</script>
<style lang="scss" scoped>
+28 -30
View File
@@ -1,5 +1,8 @@
<template>
<div class="train-schedule" @click="click">
<div
class="train-schedule"
@click="this.$emit('click')"
>
<div class="schedule-wrapper">
<ul class="stop_list">
<li
@@ -19,11 +22,17 @@
<div class="stop-bar"></div>
<span class="distance" v-if="stop.stopDistance">
<span
class="distance"
v-if="stop.stopDistance"
>
{{ Math.floor(stop.stopDistance) }}
</span>
<span class="stop-name" v-html="stop.stopName"></span>
<span
class="stop-name"
v-html="stop.stopName"
></span>
<span class="stop-date">
<span
class="date arrival"
@@ -79,9 +88,7 @@
<div class="progress-bar"></div>
<span v-if="i < followingStops.length - 1">
<span
v-if="stop.departureLine == followingStops[i + 1].arrivalLine"
>
<span v-if="stop.departureLine == followingStops[i + 1].arrivalLine">
{{ stop.departureLine }}
</span>
@@ -98,32 +105,23 @@
</template>
<script lang="ts">
import { Component, Vue, Prop } from "vue-property-decorator";
import { defineComponent } from "@vue/runtime-core";
import TrainStop from "@/scripts/interfaces/TrainStop";
export default defineComponent({
props: ["followingStops", "currentStationName"],
emits: ["click"],
@Component
export default class TrainSchedule extends Vue {
@Prop() readonly followingStops!: TrainStop[];
@Prop() readonly currentStationName!: string;
stylizeTime(timeString: string, delay: number, confirmed: boolean) {
return (
timeString +
(delay != 0 && confirmed
? " (" + (delay > 0 ? "+" : "") + delay.toString() + ")"
: "")
);
}
click() {
this.$emit("click");
}
mounted() {
console.log("mounted");
}
}
methods: {
stylizeTime(timeString: string, delay: number, confirmed: boolean) {
return (
timeString +
(delay != 0 && confirmed
? " (" + (delay > 0 ? "+" : "") + delay.toString() + ")"
: "")
);
},
},
});
</script>
<style lang="scss" scoped>
+141 -99
View File
@@ -1,16 +1,29 @@
<template>
<div class="train-stats">
<div class="stats_button">
<action-button @click.native="toggleStatsOpen">
<img :src="statsIcon" :alt="$t('trains.stats')" />
{{ $t("trains.stats") }}
<action-button @click="toggleStatsOpen">
<img
:src="statsIcon"
:alt="$t('trains.stats')"
/>
<p class="xd">{{ $t("trains.stats") }}</p>
</action-button>
</div>
<transition name="stats-anim" class="stats_wrapper" tag="div">
<div class="stats-body" v-if="trainStatsOpen">
<transition
name="stats-anim"
class="stats_wrapper"
tag="div"
>
<div
class="stats-body"
v-if="trainStatsOpen"
>
<h2 class="stats-header">
<img :src="statsIcon" :alt="$t('trains.stats')" />
<img
:src="statsIcon"
:alt="$t('trains.stats')"
/>
{{ $t("trains.stats") }}
</h2>
@@ -70,7 +83,11 @@
<div class="title stats-title">{{ $t("trains.stats-locos") }}</div>
<div class="loco-list stats-content">
<div class="loco-item" v-for="(loco, i) in locoList" :key="i">
<div
class="loco-item"
v-for="(loco, i) in locoList"
:key="i"
>
{{ loco[0] }} | {{ loco[1] }}
</div>
</div>
@@ -81,120 +98,145 @@
</template>
<script lang="ts">
import { Component, Vue, Prop } from "vue-property-decorator";
import ActionButton from "@/components/Global/ActionButton.vue";
import Train from "@/scripts/interfaces/Train";
import { computed, defineComponent } from "@vue/runtime-core";
@Component({ components: { ActionButton } })
export default class TrainStats extends Vue {
@Prop() readonly trains!: Train[];
trainStatsOpen = false;
export default defineComponent({
components: { ActionButton },
props: {
trains: {
type: Array as () => Train[],
required: true,
},
},
toggleStatsOpen() {
this.trainStatsOpen = !this.trainStatsOpen;
}
data: () => ({
trainStatsOpen: false,
statsIcon: require("@/assets/icon-stats.svg"),
}),
statsIcon = require("@/assets/icon-stats.svg");
methods: {
toggleStatsOpen() {
this.trainStatsOpen = !this.trainStatsOpen;
},
},
get speedStats(): { avg: string; min: string; max: string } {
if (this.trains.length == 0) return { avg: "0", min: "0", max: "0" };
setup(props) {
const speedStats = computed(() => {
if (props.trains.length == 0) return { avg: "0", min: "0", max: "0" };
const avg = (
this.trains.reduce((acc, train) => acc + train.speed, 0) /
this.trains.length
).toFixed(2);
const avg = (
props.trains.reduce((acc, train) => acc + train.speed, 0) /
props.trains.length
).toFixed(2);
const minMax = this.trains.reduce((acc, train) => {
if (!train.timetableData) return acc;
const minMaxSpeed = props.trains.reduce((acc, train) => {
if (!train.timetableData) return acc;
acc[0] =
acc[0] === undefined || train.speed < acc[0] ? train.speed : acc[0];
acc[0] = !acc[0] || train.speed < acc[0] ? train.speed : acc[0];
acc[1] =
acc[1] === undefined || train.speed > acc[1] ? train.speed : acc[1];
return acc;
}, [] as any);
acc[1] = !acc[1] || train.speed > acc[1] ? train.speed : acc[1];
return acc;
}, [] as any);
return { avg, min: minMax[0].toString(), max: minMax[1].toString() };
}
return {
avg,
min: minMaxSpeed[0].toString(),
max: minMaxSpeed[1].toString(),
};
});
get timetableStats(): { avg: string; min: string; max: string } {
if (this.trains.length == 0) return { avg: "0", min: "0", max: "0" };
const timetableStats = computed(() => {
if (props.trains.length == 0) return { avg: "0", min: "0", max: "0" };
const avg = (
this.trains.reduce(
(acc, train) =>
train.timetableData ? acc + train.timetableData.routeDistance : acc,
0
) / this.trains.length
).toFixed(2);
const avg = (
props.trains.reduce(
(acc, train) =>
train.timetableData ? acc + train.timetableData.routeDistance : acc,
0
) / props.trains.length
).toFixed(2);
const minMax = this.trains.reduce((acc, train) => {
if (!train.timetableData) return acc;
const minMaxDistance = props.trains.reduce((acc, train) => {
if (!train.timetableData) return acc;
acc[0] =
acc[0] === undefined || train.timetableData.routeDistance < acc[0]
? train.timetableData.routeDistance
: acc[0];
acc[0] =
!acc[0] || train.timetableData.routeDistance < acc[0]
? train.timetableData.routeDistance
: acc[0];
acc[1] =
acc[1] === undefined || train.timetableData.routeDistance > acc[1]
? train.timetableData.routeDistance
: acc[1];
return acc;
}, [] as any);
acc[1] =
!acc[1] || train.timetableData.routeDistance > acc[1]
? train.timetableData.routeDistance
: acc[1];
return acc;
}, [] as any);
return { avg, min: minMax[0].toString(), max: minMax[1].toString() };
}
return {
avg,
min: minMaxDistance[0].toString(),
max: minMaxDistance[1].toString(),
};
});
get categoryList(): Map<string, number> {
const map = this.trains.reduce((acc, train) => {
if (!train.timetableData || !train.timetableData.category) return acc;
const categoryList = computed(() => {
const map = props.trains.reduce((acc, train) => {
if (!train.timetableData || !train.timetableData.category) return acc;
acc.set(
train.timetableData.category,
acc.get(train.timetableData.category)
? acc.get(train.timetableData.category) + 1
: 1
acc.set(
train.timetableData.category,
acc.get(train.timetableData.category)
? acc.get(train.timetableData.category) + 1
: 1
);
return acc;
}, new Map());
return new Map([...map.entries()].sort((a, b) => b[1] - a[1]));
});
const locoList = computed(() => {
const map: Map<string, number> = props.trains.reduce((acc, train) => {
if (!train.timetableData || !train.locoType) return acc;
acc.set(
train.locoType,
acc.get(train.locoType) ? acc.get(train.locoType) + 1 : 1
);
return acc;
}, new Map());
const sorted = [...map.entries()]
.sort((a, b) => b[1] - a[1])
.filter((v, i) => i < 3);
return sorted;
});
const specialTrainCount = computed(() => {
const twrList = props.trains.filter(
(train) => train.timetableData && train.timetableData.TWR
);
const skrList = props.trains.filter(
(train) => train.timetableData && train.timetableData.SKR
);
return acc;
}, new Map());
return [twrList.length, skrList.length];
});
return new Map([...map.entries()].sort((a, b) => b[1] - a[1]));
}
get locoList(): any[] {
const map = this.trains.reduce((acc, train) => {
if (!train.timetableData || !train.locoType) return acc;
acc.set(
train.locoType,
acc.get(train.locoType) ? acc.get(train.locoType) + 1 : 1
);
return acc;
}, new Map());
const sorted = [...map.entries()]
.sort((a, b) => b[1] - a[1])
.filter((v, i) => i < 3);
return sorted;
}
get specialTrainCount(): [number, number] {
const twrList = this.trains.filter(
(train) => train.timetableData && train.timetableData.TWR
);
const skrList = this.trains.filter(
(train) => train.timetableData && train.timetableData.SKR
);
return [twrList.length, skrList.length];
}
}
return {
speedStats,
timetableStats,
categoryList,
locoList,
specialTrainCount,
};
},
});
</script>
<style lang="scss" scoped>
@@ -206,7 +248,7 @@ export default class TrainStats extends Vue {
transition: all 150ms ease-out;
}
&-enter,
&-enter-from,
&-leave-to {
opacity: 0;
transform: translateY(30px);
+134 -88
View File
@@ -26,7 +26,7 @@
class="train-row"
v-for="(train, i) in computedTrains"
:key="i"
:ref="train.timetableData ? train.timetableData.timetableId : -1"
:ref="train.timetableData && (el => { elList[train.timetableData.timetableId] = el })"
>
<div
class="wrapper no-timetable"
@@ -319,117 +319,163 @@
</template>
<script lang="ts">
import { Vue, Component, Prop, Watch } from "vue-property-decorator";
import Train from "@/scripts/interfaces/Train";
import TrainStop from "@/scripts/interfaces/TrainStop";
import TrainSchedule from "@/components/TrainsView/TrainSchedule.vue";
import { DataStatus } from "@/scripts/enums/DataStatus";
import {
computed,
ComputedRef,
defineComponent,
onBeforeUpdate,
Ref,
ref,
} from "@vue/runtime-core";
import { useStore } from "@/store";
import { GETTERS } from "@/constants/storeConstants";
@Component({
components: { TrainSchedule },
})
export default class TrainTable extends Vue {
@Prop() computedTrains!: Train[];
@Prop() timetableDataStatus!: DataStatus;
@Prop() queryTrain!: string;
export default defineComponent({
components: {
TrainSchedule,
},
showedSchedule = 0;
props: {
computedTrains: {
type: Array as () => Train[],
required: true,
},
defaultLocoImage = require("@/assets/unknown.png");
queryTrain: {
type: String,
required: false,
},
},
ascSVG = require("@/assets/icon-arrow-asc.svg");
descSVG = require("@/assets/icon-arrow-desc.svg");
data: () => ({
showedSchedule: 0,
defaultLocoImage: require("@/assets/unknown.png"),
speedIcon: string = require("@/assets/icon-speed.svg");
massIcon: string = require("@/assets/icon-mass.svg");
lengthIcon: string = require("@/assets/icon-length.svg");
ascSVG: require("@/assets/icon-arrow-asc.svg"),
descSVG: require("@/assets/icon-arrow-desc.svg"),
distanceIcon: string = require("@/assets/icon-distance.svg");
sceneryIcon: string = require("@/assets/icon-scenery.svg");
signalIcon: string = require("@/assets/icon-signal.svg");
routeIcon: string = require("@/assets/icon-route.svg");
speedIcon: require("@/assets/icon-speed.svg"),
massIcon: require("@/assets/icon-mass.svg"),
lengthIcon: require("@/assets/icon-length.svg"),
get timetableLoaded() {
return this.timetableDataStatus == DataStatus.Loaded;
}
distanceIcon: require("@/assets/icon-distance.svg"),
sceneryIcon: require("@/assets/icon-scenery.svg"),
signalIcon: require("@/assets/icon-signal.svg"),
routeIcon: require("@/assets/icon-route.svg"),
}),
get timetableError() {
return this.timetableDataStatus == DataStatus.Error;
}
setup(props) {
const store = useStore();
const elList: Ref<HTMLElement[]> = ref([]);
get distanceLimitExceeded() {
return (
this.computedTrains.findIndex(
(train) =>
train.timetableData && train.timetableData.routeDistance > 200
) != -1
);
}
changeScheduleShowState(elementId: number) {
if (elementId < 0) return;
this.showedSchedule = this.showedSchedule == elementId ? 0 : elementId;
this.$nextTick(() => {
const currentEl: HTMLElement = this.$refs[elementId][0];
currentEl.scrollIntoView({
behavior: "smooth",
block: "nearest",
});
onBeforeUpdate(() => {
elList.value.length = 0;
});
}
@Watch("queryTrain")
onSearchedTrainChange(trainNo: string) {
const timetableId = this.computedTrains.find(
(train) => train.trainNo == parseInt(trainNo)
)?.timetableData?.timetableId;
const timetableDataStatus: ComputedRef<DataStatus> = computed(
() => store.getters[GETTERS.timetableDataStatus]
);
if (!timetableId) return;
const timetableLoaded = computed(
() => timetableDataStatus.value === DataStatus.Loaded
);
const timetableError = computed(
() => timetableDataStatus.value === DataStatus.Error
);
this.changeScheduleShowState(timetableId);
}
const distanceLimitExceeded = computed(
() =>
props.computedTrains.findIndex(
({ timetableData }) =>
timetableData && timetableData.routeDistance > 200
) != -1
);
onImageError(e: Event) {
const imageEl = e.target as HTMLImageElement;
return {
timetableLoaded,
timetableError,
distanceLimitExceeded,
elList,
};
},
imageEl.src = this.defaultLocoImage;
}
methods: {
changeScheduleShowState(timetableId: number) {
if (timetableId < 0) return;
generateStopList(stops: any): string | undefined {
if (!stops) return "";
this.showedSchedule =
this.showedSchedule == timetableId ? 0 : timetableId;
return stops
.reduce((acc, stop: TrainStop, i) => {
if (stop.stopType.includes("ph"))
acc.push(
`<strong style='color:${
stop.confirmed ? "springgreen" : "white"
}'>${stop.stopName}</strong>`
);
else if (
i > 0 &&
i < stops.length - 1 &&
!stop.stopNameRAW.includes("po.") &&
!stop.stopNameRAW.includes("SBL")
)
acc.push(`<span style='color:${ stop.confirmed ? "springgreen" : "lightgray" }'>${stop.stopName}</span>`);
return acc;
}, [])
.join(" > ");
}
this.$nextTick(() => {
const currentEl: HTMLElement = this.elList[timetableId];
calculateCars(locoType: string, cars: string[]) {
if (cars.length == 0 && locoType.includes("EN")) return "EZT";
else if (cars.length == 0) return "LOK";
currentEl.scrollIntoView({
behavior: "smooth",
block: "nearest",
});
});
},
return `${this.$t("trains.cars")}: <span style='color:gold'> ${cars.length}</span>`;
}
}
onImageError(e: Event) {
const imageEl = e.target as HTMLImageElement;
imageEl.src = this.defaultLocoImage;
},
generateStopList(stops: TrainStop[]): string | undefined {
if (!stops) return "";
return stops
.reduce((acc: string[], stop: TrainStop, i: number) => {
if (stop.stopType.includes("ph"))
acc.push(
`<strong style='color:${
stop.confirmed ? "springgreen" : "white"
}'>${stop.stopName}</strong>`
);
else if (
i > 0 &&
i < stops.length - 1 &&
!stop.stopNameRAW.includes("po.") &&
!stop.stopNameRAW.includes("SBL")
)
acc.push(
`<span style='color:${
stop.confirmed ? "springgreen" : "lightgray"
}'>${stop.stopName}</span>`
);
return acc;
}, [])
.join(" > ");
},
calculateCars(locoType: string, cars: string[]) {
if (cars.length == 0 && locoType.includes("EN")) return "EZT";
else if (cars.length == 0) return "LOK";
return `${this.$t("trains.cars")}: <span style='color:gold'> ${
cars.length
}</span>`;
},
},
watch: {
queryTrain(trainNo: string) {
const timetableId = this.computedTrains.find(
(train) => train.trainNo == parseInt(trainNo)
)?.timetableData?.timetableId;
if (!timetableId) return;
this.changeScheduleShowState(timetableId);
},
},
});
</script>
<style lang="scss" scoped>
+23
View File
@@ -0,0 +1,23 @@
export const ACTIONS = {
synchronizeData: "synchronizeData",
fetchOnlineData: "fetchOnlineData",
fetchTimetableData: "fetchTimetableData"
}
export const MUTATIONS = {
SET_SCENERY_DATA: "SET_SCENERY_DATA",
SET_SCENERY_DATA_STATUS: "SET_SCENERY_DATA_STATUS",
SET_DATA_CONNECTION_STATUS: "SET_DATA_CONNECTION_STATUS",
UPDATE_STATIONS: "UPDATE_STATIONS",
UPDATE_TRAINS: "UPDATE_TRAINS",
UPDATE_TIMETABLES: "UPDATE_TIMETABLES"
}
export const GETTERS = {
stationList: "stationList",
trainList: "trainList",
allData: "allData",
timetableDataStatus: "timetableDataStatus",
sceneryDataStatus: "sceneryDataStatus",
dataStatus: "dataStatus"
}
-1
View File
@@ -1 +0,0 @@
{"success":true,"respCode":10,"message":[{"dispatcherId":13983,"dispatcherName":"Don350","dispatcherIsSupporter":true,"stationName":"Hetmanice","stationHash":"f7389af5","region":"eu","maxUsers":8,"currentUsers":0,"spawn":1,"lastSeen":1606776450625,"dispatcherExp":14,"nameFromHeader":"Hetmanice","spawnString":"He_Tm1,1,550,True,False,False,ALL","networkConnectionString":"2020.1.1Stable004","isOnline":1,"dispatcherRate":22},{"dispatcherId":22620,"dispatcherName":"Calleman","dispatcherIsSupporter":true,"stationName":"Wielichowo","stationHash":"e4f61c89","region":"ru","maxUsers":29,"currentUsers":1,"spawn":0,"lastSeen":1606776448323,"dispatcherExp":11,"nameFromHeader":"Wielichowo","spawnString":null,"networkConnectionString":"2020.1.1Stable004","isOnline":1,"dispatcherRate":0},{"dispatcherId":10048,"dispatcherName":"Sprytny_Zbys","dispatcherIsSupporter":false,"stationName":"Piaskowo","stationHash":"74a6c5ba","region":"eu","maxUsers":29,"currentUsers":0,"spawn":1,"lastSeen":1606776447873,"dispatcherExp":11,"nameFromHeader":"Piaskowo","spawnString":"Ps_G3,1,650,True,False,False,ALL;Ps_G4,1,650,True,False,False,ALL;Ps_N,-1,185,True,False,False,PAS","networkConnectionString":"2020.1.1Stable004","isOnline":1,"dispatcherRate":23},{"dispatcherId":4310,"dispatcherName":"jedrek386","dispatcherIsSupporter":true,"stationName":"Niedoradz","stationHash":"2a7a048e","region":"eu","maxUsers":7,"currentUsers":0,"spawn":1,"lastSeen":1606776454351,"dispatcherExp":1,"nameFromHeader":"Niedoradz","spawnString":"Ne_H,1,200,True,False,False,H","networkConnectionString":"2020.1.1Stable004","isOnline":1,"dispatcherRate":2},{"dispatcherId":10048,"dispatcherName":"Sprytny_Zbys","dispatcherIsSupporter":false,"stationName":"LCS Skrzynki","stationHash":"c9d5dc18","region":"eu","maxUsers":10,"currentUsers":1,"spawn":0,"lastSeen":1606776460199,"dispatcherExp":11,"nameFromHeader":"LCS Skrzynki","spawnString":null,"networkConnectionString":"2020.1.1Stable004","isOnline":1,"dispatcherRate":0},{"dispatcherId":6975,"dispatcherName":"ziolek","dispatcherIsSupporter":false,"stationName":"Kcynia","stationHash":"64f1a3ba","region":"ru","maxUsers":9,"currentUsers":0,"spawn":0,"lastSeen":1606775846330,"dispatcherExp":4,"nameFromHeader":"Kcynia","spawnString":null,"networkConnectionString":"2020.1.1Stable004","isOnline":0,"dispatcherRate":0},{"dispatcherId":7240,"dispatcherName":"ASkier","dispatcherIsSupporter":false,"stationName":"Głębce","stationHash":"3f7e4639","region":"ru","maxUsers":4,"currentUsers":0,"spawn":0,"lastSeen":1606775877861,"dispatcherExp":1,"nameFromHeader":"Głębce","spawnString":null,"networkConnectionString":"2020.1.1Stable004","isOnline":0,"dispatcherRate":0},{"dispatcherId":16658,"dispatcherName":"NIEMIEC141","dispatcherIsSupporter":false,"stationName":"Buk 2018","stationHash":"4c831fc3","region":"eu","maxUsers":4,"currentUsers":0,"spawn":0,"lastSeen":1606775836686,"dispatcherExp":7,"nameFromHeader":"Buk 2018","spawnString":"NO_SPAWN","networkConnectionString":"2020.1.1Stable004","isOnline":0,"dispatcherRate":0},{"dispatcherId":6975,"dispatcherName":"ziolek","dispatcherIsSupporter":false,"stationName":"Kolsko","stationHash":"687dcf5b","region":"ru","maxUsers":6,"currentUsers":0,"spawn":0,"lastSeen":1606776448954,"dispatcherExp":4,"nameFromHeader":"Kolsko","spawnString":null,"networkConnectionString":"2020.1.1Stable004","isOnline":1,"dispatcherRate":0},{"dispatcherId":7240,"dispatcherName":"ASkier","dispatcherIsSupporter":false,"stationName":"Karszynek","stationHash":"c0e19184","region":"ru","maxUsers":5,"currentUsers":0,"spawn":0,"lastSeen":1606776456733,"dispatcherExp":1,"nameFromHeader":"Karszynek","spawnString":null,"networkConnectionString":"2020.1.1Stable004","isOnline":1,"dispatcherRate":0},{"dispatcherId":3619,"dispatcherName":"SzybkiWiewiór","dispatcherIsSupporter":false,"stationName":"Knot","stationHash":"cbaad885","region":"ru","maxUsers":14,"currentUsers":1,"spawn":0,"lastSeen":1606776460872,"dispatcherExp":7,"nameFromHeader":"Knot","spawnString":null,"networkConnectionString":"2020.1.1Stable004","isOnline":1,"dispatcherRate":0},{"dispatcherId":16658,"dispatcherName":"NIEMIEC141","dispatcherIsSupporter":false,"stationName":"Parzęczewo","stationHash":"325b1a74","region":"eu","maxUsers":29,"currentUsers":0,"spawn":1,"lastSeen":1606776392453,"dispatcherExp":7,"nameFromHeader":"Parzęczewo","spawnString":"LUZ1,-1,50,True,False,True,;LUZ2,-1,50,True,False,True,;POSP1,-1,200,True,False,False,;POSP2,-1,300,True,False,False,;POSP3,-1,200,True,False,False,;Pr_P20,1,200,True,True,False,;Pr_T28,1,750,True,False,False,;Pr_T34,1,750,True,False,False,;Pr_U38,-1,750,True,False,False,","networkConnectionString":"2020.1.1Stable004","isOnline":1,"dispatcherRate":0}]}
+27
View File
@@ -0,0 +1,27 @@
import { createI18n, LocaleMessages, VueMessageType } from 'vue-i18n'
/**
* Load locale messages
*
* The loaded `JSON` locale messages is pre-compiled by `@intlify/vue-i18n-loader`, which is integrated into `vue-cli-plugin-i18n`.
* See: https://github.com/intlify/vue-i18n-loader#rocket-i18n-resource-pre-compilation
*/
function loadLocaleMessages(): LocaleMessages<VueMessageType> {
const locales = require.context('./locales', true, /[A-Za-z0-9-_,\s]+\.json$/i)
const messages: LocaleMessages<VueMessageType> = {}
locales.keys().forEach(key => {
const matched = key.match(/([A-Za-z0-9-_]+)\./i)
if (matched && matched.length > 1) {
const locale = matched[1]
messages[locale] = locales(key)
}
})
return messages
}
export default createI18n({
legacy: false,
locale: process.env.VUE_APP_I18N_LOCALE || 'en',
fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || 'en',
messages: loadLocaleMessages()
})
+142 -142
View File
@@ -1,142 +1,142 @@
{
"app": {
"sceneries": "SCENERIES",
"trains": "TRAINS",
"journal": "JOURNAL",
"loading": "Loading data...",
"support": "Support the project"
},
"desc": {
"control-type": "Control type: ",
"signals-type": "Signals type: ",
"SBL": "This scenery has automatic line blockade system on following routes: ",
"default": "This scenery is available by default",
"non-public": "This scenery is not public",
"unavailable": "This scenery is unavailable",
"real": "This scenery is real"
},
"signals": {
"współczesna": "modern",
"mieszana": "mixed",
"kształtowa": "mechanical",
"historyczna": "historyczna"
},
"controls": {
"SPK": "SPK",
"SCS": "SCS",
"SCS-SPK": "SCS/SPK",
"SPE": "SPE",
"ręczne": "manual",
"ręczne+SPK": "manual + SPK",
"ręczne+SCS": "manual + SCS",
"mechaniczne": "levers (mechanical)",
"mechaniczne+SPK": "levers + SPK",
"mechaniczne+SCS": "levers + SCS"
},
"status": {
"online": "UNTIL ",
"free": "FREE",
"ending": "ENDS SOON",
"not-signed": "NOT SIGNED IN",
"no-limit": "NO LIMIT",
"unavailable": "UNAVAILABLE",
"brb": "AFK",
"no-space": "NO SPACE"
},
"options": {
"filters": "FILTERS",
"donate": "DONATE"
},
"filters": {
"title": "STATION FILTER",
"default": "DEFAULT",
"not-default": "OTHER",
"real": "REAL",
"fictional": "FICTIONAL",
"SPK": "SPK",
"SCS": "SCS",
"SPE": "SPE",
"manual": "MANUAL",
"mechanical": "MECHANICAL",
"modern": "MODERN",
"semaphores": "SEMAPHORES",
"mixed": "MIXED",
"historical": "HISTORICAL",
"free": "FREE",
"occupied": "OCCUPIED",
"sliders": {
"min-lvl": "MIN. REQUIRED DISPATCHER LEVEL",
"max-lvl": "MAX. REQUIRED DISPATCHER LEVEL",
"routes-1t-cat": "MIN. CATENARY SINGLE TRACK ROUTES",
"routes-1t-other": "MIN. OTHER SINGLE TRACK ROUTES",
"routes-2t-cat": "MIN. CATENARY DOUBLE TRACK ROUTES",
"routes-2t-other": "MIN. OTHER DOUBLE TRACK ROUTES"
},
"save": "SAVE FILTERS",
"reset": "RESET FILTERS",
"close": "CLOSE FILTERS"
},
"sceneries": {
"station": "Station",
"min-lvl": "Min. dispatcher <br> level",
"status": "Status",
"dispatcher": "Dispatcher",
"dispatcher-lvl": "Dispatcher <br> level",
"routes": "Routes <br> double | single",
"general": "General info",
"users": "Drivers online",
"spawns": "Spawns online",
"timetables": "Active timetables",
"no-stations": "No stations to show here!"
},
"trains": {
"no-trains": "Oops! No trains online!",
"loading": "Loading train data...",
"stats": "TRAFFIC STATISTICS",
"stats-speed": "TRAINS SPEED (MIN | AVG | MAX) [km/h]",
"stats-length": "TIMETABLES LENGTH (MIN | AVG | MAX) [km]",
"stats-categories": "TIMETABLE CATEGORIES",
"stats-special-twr": "HIGH RISK",
"stats-special-skr": "EXCEEDED STRUCT. GAUGE",
"stats-locos": "MOST COMMON UNITS",
"option-mass": "mass",
"option-speed": "speed",
"option-length": "length",
"option-distance": "distance",
"option-timetable": "train no.",
"search-no": "Search for train no...",
"search-driver": "Search for driver...",
"detailed-timetable": "Detailed timetable for train no. ",
"via-title": "Via: ",
"no-timetable": "no current timetable",
"distance-exceeded": "Attention! Due to an internal error, timetables with route distance greater than 200km might be incorrect!",
"cars": "Cars"
},
"journal": {
"title": "SCENERY ACTIVITY JOURNAL",
"subtitle": "Shows all recent dispatchers on a selected scenery",
"disclaimer": "<b>This functionality is unfinished!</b> <br> Information shown here could be false or incorrect!",
"select": "Select a scenery"
},
"scenery": {
"users": "PLAYERS ONLINE",
"spawns": "OPEN SPAWNS",
"timetables": "ACTIVE TIMETABLES",
"no-timetables": "No active timetables!",
"no-users": "NO ACTIVE PLAYERS",
"no-spawns": "NO OPEN SPAWNS",
"no-scenery": "Oops! This scenery doesn't exist!",
"return-btn": "Return to main site"
},
"timetables": {
"timetable-only": "Switch to timetable-only view",
"online": "At station",
"departed": "Dispatched",
"departed-away": "Departed",
"arriving": "En route",
"stopped": "Stopped",
"terminated": "Terminated",
"begins": "BEGINS HERE",
"terminates": "TERMINATES <br /> HERE"
}
}
{
"app": {
"sceneries": "SCENERIES",
"trains": "TRAINS",
"journal": "JOURNAL",
"loading": "Loading data...",
"support": "Support the project"
},
"desc": {
"control-type": "Control type: ",
"signals-type": "Signals type: ",
"SBL": "This scenery has automatic line blockade system on following routes: ",
"default": "This scenery is available by default",
"non-public": "This scenery is not public",
"unavailable": "This scenery is unavailable",
"real": "This scenery is real"
},
"signals": {
"współczesna": "modern",
"mieszana": "mixed",
"kształtowa": "mechanical",
"historyczna": "historyczna"
},
"controls": {
"SPK": "SPK",
"SCS": "SCS",
"SCS-SPK": "SCS/SPK",
"SPE": "SPE",
"ręczne": "manual",
"ręczne+SPK": "manual + SPK",
"ręczne+SCS": "manual + SCS",
"mechaniczne": "levers (mechanical)",
"mechaniczne+SPK": "levers + SPK",
"mechaniczne+SCS": "levers + SCS"
},
"status": {
"online": "UNTIL ",
"free": "FREE",
"ending": "ENDS SOON",
"not-signed": "NOT SIGNED IN",
"no-limit": "NO LIMIT",
"unavailable": "UNAVAILABLE",
"brb": "AFK",
"no-space": "NO SPACE"
},
"options": {
"filters": "FILTERS",
"donate": "DONATE"
},
"filters": {
"title": "STATION FILTER",
"default": "DEFAULT",
"not-default": "OTHER",
"real": "REAL",
"fictional": "FICTIONAL",
"SPK": "SPK",
"SCS": "SCS",
"SPE": "SPE",
"manual": "MANUAL",
"mechanical": "MECHANICAL",
"modern": "MODERN",
"semaphores": "SEMAPHORES",
"mixed": "MIXED",
"historical": "HISTORICAL",
"free": "FREE",
"occupied": "OCCUPIED",
"sliders": {
"min-lvl": "MIN. REQUIRED DISPATCHER LEVEL",
"max-lvl": "MAX. REQUIRED DISPATCHER LEVEL",
"routes-1t-cat": "MIN. CATENARY SINGLE TRACK ROUTES",
"routes-1t-other": "MIN. OTHER SINGLE TRACK ROUTES",
"routes-2t-cat": "MIN. CATENARY DOUBLE TRACK ROUTES",
"routes-2t-other": "MIN. OTHER DOUBLE TRACK ROUTES"
},
"save": "SAVE FILTERS",
"reset": "RESET FILTERS",
"close": "CLOSE FILTERS"
},
"sceneries": {
"station": "Station",
"min-lvl": "Min. dispatcher\nlevel",
"status": "Status",
"dispatcher": "Dispatcher",
"dispatcher-lvl": "Dispatcher\nlevel",
"routes": "Routes\ndouble / single",
"general": "General info",
"users": "Drivers online",
"spawns": "Spawns online",
"timetables": "Active timetables",
"no-stations": "No stations to show here!"
},
"trains": {
"no-trains": "Oops! No trains online!",
"loading": "Loading train data...",
"stats": "TRAFFIC STATISTICS",
"stats-speed": "TRAINS SPEED (MIN, AVG, MAX) [km/h]",
"stats-length": "TIMETABLES LENGTH (MIN, AVG, MAX) [km]",
"stats-categories": "TIMETABLE CATEGORIES",
"stats-special-twr": "HIGH RISK",
"stats-special-skr": "EXCEEDED STRUCT. GAUGE",
"stats-locos": "MOST COMMON UNITS",
"option-mass": "mass",
"option-speed": "speed",
"option-length": "length",
"option-distance": "distance",
"option-timetable": "train no.",
"search-no": "Search for train no...",
"search-driver": "Search for driver...",
"detailed-timetable": "Detailed timetable for train no. ",
"via-title": "Via: ",
"no-timetable": "no current timetable",
"distance-exceeded": "Attention! Due to an internal error, timetables with route distance greater than 200km might be incorrect!",
"cars": "Cars"
},
"journal": {
"title": "SCENERY ACTIVITY JOURNAL",
"subtitle": "Shows all recent dispatchers on a selected scenery",
"disclaimer": "<b>This functionality is unfinished!</b> \n Information shown here could be false or incorrect!",
"select": "Select a scenery"
},
"scenery": {
"users": "PLAYERS ONLINE",
"spawns": "OPEN SPAWNS",
"timetables": "ACTIVE TIMETABLES",
"no-timetables": "No active timetables!",
"no-users": "NO ACTIVE PLAYERS",
"no-spawns": "NO OPEN SPAWNS",
"no-scenery": "Oops! This scenery doesn't exist!",
"return-btn": "Return to main site"
},
"timetables": {
"timetable-only": "Switch to timetable-only view",
"online": "At station",
"departed": "Dispatched",
"departed-away": "Departed",
"arriving": "En route",
"stopped": "Stopped",
"terminated": "Terminated",
"begins": "BEGINS HERE",
"terminates": "TERMINATES\nHERE"
}
}
+142 -142
View File
@@ -1,142 +1,142 @@
{
"app": {
"sceneries": "SCENERIE",
"trains": "POCIĄGI",
"journal": "DZIENNIK",
"loading": "Pobieranie danych...",
"support": "Wspomóż projekt"
},
"desc": {
"control-type": "Sterowanie: ",
"signals-type": "Sygnalizacja: ",
"SBL": "Sceneria posiada SBL na szlakach: ",
"default": "Sceneria dostępna domyślnie w paczce z grą",
"non-public": "Sceneria niepubliczna",
"unavailable": "Sceneria niedostępna",
"real": "Sceneria realna"
},
"signals": {
"współczesna": "współczesna",
"mieszana": "mieszana",
"kształtowa": "kształtowa",
"historyczna": "historyczna"
},
"controls": {
"SPK": "SPK",
"SCS": "SCS",
"SCS-SPK": "SCS/SPK",
"SPE": "SPE",
"ręczne": "ręczne",
"ręczne+SPK": "ręczne + SPK",
"ręczne+SCS": "ręczne + SCS",
"mechaniczne": "mechaniczne",
"mechaniczne+SPK": "mechaniczne + SPK",
"mechaniczne+SCS": "mechaniczne + SCS"
},
"status": {
"online": "DO ",
"free": "WOLNA",
"ending": "KOŃCZY",
"not-signed": "NIEZALOGOWANY",
"no-limit": "BEZ LIMITU",
"unavailable": "NIEDOSTĘPNY",
"brb": "Z/W",
"no-space": "BRAK MIEJSCA"
},
"options": {
"filters": "FILTRY",
"donate": "WESPRZYJ"
},
"filters": {
"title": "FILTRUJ STACJE",
"default": "DOMYŚLNA",
"not-default": "POZA PACZKĄ",
"real": "REALNA",
"fictional": "FIKCYJNA",
"SPK": "SPK",
"SCS": "SCS",
"SPE": "SPE",
"manual": "RĘCZNE",
"mechanical": "MECHANICZNE",
"modern": "WSPÓŁCZESNA",
"semaphores": "KSZTAŁTOWA",
"mixed": "MIESZANA",
"historical": "HISTORYCZNA",
"free": "WOLNA",
"occupied": "ZAJĘTA",
"sliders": {
"min-lvl": "MIN. WYMAGANY POZIOM DYŻURNEGO",
"max-lvl": "MAKS. WYMAGANY POZIOM DYŻURNEGO",
"routes-1t-cat": "SZLAKI JEDNOTOROWE ZELEKTR. (MINIMUM)",
"routes-1t-other": "SZLAKI JEDNOTOROWE NIEZELEKTR. (MINIMUM)",
"routes-2t-cat": "SZLAKI DWUTOROWE ZELEKTR. (MINIMUM)",
"routes-2t-other": "SZLAKI DWUTOROWE NIEZELEKTR. (MINIMUM)"
},
"save": "ZAPISZ FILTRY",
"reset": "RESETUJ FILTRY",
"close": "ZAMKNIJ FILTRY"
},
"sceneries": {
"station": "Stacja",
"min-lvl": "Min. poziom <br/> dyżurnego",
"status": "Status",
"dispatcher": "Dyżurny",
"dispatcher-lvl": "Poziom <br> dyżurnego",
"routes": "Szlaki <br> 2tor | 1tor",
"general": "Informacje <br> ogólne",
"users": "Maszyniści online",
"spawns": "Otwarte spawny",
"timetables": "Aktywne rozkłady jazdy",
"no-stations": "Brak stacji do wyświetlenia!"
},
"trains": {
"no-trains": "Brak pociągów online!",
"loading": "Pobieranie danych o pociągach...",
"stats": "STATYSTYKI RUCHU",
"stats-speed": "PRĘDKOŚCI POCIĄGÓW (MIN | ŚR | MAX) [km/h]",
"stats-length": "DŁUGOŚCI ROZKŁADÓW (MIN | ŚR | MAX) [km]",
"stats-categories": "KATEGORIE RJ",
"stats-special-twr": "WYSOKIEGO RYZYKA",
"stats-special-skr": "PRZEKROCZONA SKRAJNIA",
"stats-locos": "NAJCZĘSTSZE JEDNOSTKI",
"option-mass": "masa",
"option-speed": "prędkość",
"option-length": "długość",
"option-distance": "kilometraż",
"option-timetable": "numer pociągu",
"search-no": "Szukaj nr pociągu...",
"search-driver": "Szukaj maszynisty...",
"detailed-timetable": "Szczegółowy rozkład jazdy pociągu ",
"via-title": "Przez: ",
"no-timetable": "brak rozkładu jazdy",
"distance-exceeded": "Uwaga! Z powodu wewnętrznego błędu serwera TD2, rozkłady jazdy o kilometrażu powyżej 200km mogą być niepoprawne!",
"cars": "Wagony"
},
"journal": {
"title": "DZIENNIK AKTYWNOŚCI SCENERII",
"subtitle": "Pokazuje dyżurnych, którzy ostatnio byli aktywni na wybranej scenerii",
"disclaimer": "<b>Ta funkcjonalność jest w testach beta!</b> <br> Informacje pokazywane na ekranie mogą znikać, a ich zawartość może być fałszywa!",
"select": "Wybierz scenerię"
},
"scenery": {
"users": "GRACZE ONLINE",
"spawns": "OTWARTE SPAWNY",
"timetables": "AKTYWNE ROZKŁADY JAZDY",
"no-timetables": "Brak aktywnych rozkładów!",
"no-users": "BRAK AKTYWNYCH GRACZY",
"no-spawns": "BRAK OTWARTYCH SPAWNÓW",
"no-scenery": "Ups! Ta sceneria nie istnieje!",
"return-btn": "Wróć na stronę główną"
},
"timetables": {
"timetable-only": "Wyodrębnij rozkłady jazdy",
"online": "Na stacji",
"departed": "Odprawiony",
"departed-away": "Odjechał",
"arriving": "W drodze",
"stopped": "Postój",
"terminated": "Skończył bieg",
"begins": "ROZPOCZYNA <br /> BIEG",
"terminates": "KOŃCZY BIEG"
}
}
{
"app": {
"sceneries": "SCENERIE",
"trains": "POCIĄGI",
"journal": "DZIENNIK",
"loading": "Pobieranie danych...",
"support": "Wspomóż projekt"
},
"desc": {
"control-type": "Sterowanie: ",
"signals-type": "Sygnalizacja: ",
"SBL": "Sceneria posiada SBL na szlakach: ",
"default": "Sceneria dostępna domyślnie w paczce z grą",
"non-public": "Sceneria niepubliczna",
"unavailable": "Sceneria niedostępna",
"real": "Sceneria realna"
},
"signals": {
"współczesna": "współczesna",
"mieszana": "mieszana",
"kształtowa": "kształtowa",
"historyczna": "historyczna"
},
"controls": {
"SPK": "SPK",
"SCS": "SCS",
"SCS-SPK": "SCS/SPK",
"SPE": "SPE",
"ręczne": "ręczne",
"ręczne+SPK": "ręczne + SPK",
"ręczne+SCS": "ręczne + SCS",
"mechaniczne": "mechaniczne",
"mechaniczne+SPK": "mechaniczne + SPK",
"mechaniczne+SCS": "mechaniczne + SCS"
},
"status": {
"online": "DO ",
"free": "WOLNA",
"ending": "KOŃCZY",
"not-signed": "NIEZALOGOWANY",
"no-limit": "BEZ LIMITU",
"unavailable": "NIEDOSTĘPNY",
"brb": "Z/W",
"no-space": "BRAK MIEJSCA"
},
"options": {
"filters": "FILTRY",
"donate": "WESPRZYJ"
},
"filters": {
"title": "FILTRUJ STACJE",
"default": "DOMYŚLNA",
"not-default": "POZA PACZKĄ",
"real": "REALNA",
"fictional": "FIKCYJNA",
"SPK": "SPK",
"SCS": "SCS",
"SPE": "SPE",
"manual": "RĘCZNE",
"mechanical": "MECHANICZNE",
"modern": "WSPÓŁCZESNA",
"semaphores": "KSZTAŁTOWA",
"mixed": "MIESZANA",
"historical": "HISTORYCZNA",
"free": "WOLNA",
"occupied": "ZAJĘTA",
"sliders": {
"min-lvl": "MIN. WYMAGANY POZIOM DYŻURNEGO",
"max-lvl": "MAKS. WYMAGANY POZIOM DYŻURNEGO",
"routes-1t-cat": "SZLAKI JEDNOTOROWE ZELEKTR. (MINIMUM)",
"routes-1t-other": "SZLAKI JEDNOTOROWE NIEZELEKTR. (MINIMUM)",
"routes-2t-cat": "SZLAKI DWUTOROWE ZELEKTR. (MINIMUM)",
"routes-2t-other": "SZLAKI DWUTOROWE NIEZELEKTR. (MINIMUM)"
},
"save": "ZAPISZ FILTRY",
"reset": "RESETUJ FILTRY",
"close": "ZAMKNIJ FILTRY"
},
"sceneries": {
"station": "Stacja",
"min-lvl": "Min. poziom\ndyżurnego",
"status": "Status",
"dispatcher": "Dyżurny",
"dispatcher-lvl": "Poziom\ndyżurnego",
"routes": "Szlaki\n2tor / 1tor",
"general": "Informacje\nogólne",
"users": "Maszyniści online",
"spawns": "Otwarte spawny",
"timetables": "Aktywne rozkłady jazdy",
"no-stations": "Brak stacji do wyświetlenia!"
},
"trains": {
"no-trains": "Brak pociągów online!",
"loading": "Pobieranie danych o pociągach...",
"stats": "STATYSTYKI RUCHU",
"stats-speed": "PRĘDKOŚCI POCIĄGÓW (MIN, ŚR, MAX) [km/h]",
"stats-length": "DŁUGOŚCI ROZKŁADÓW (MIN, ŚR, MAX) [km]",
"stats-categories": "KATEGORIE RJ",
"stats-special-twr": "WYSOKIEGO RYZYKA",
"stats-special-skr": "PRZEKROCZONA SKRAJNIA",
"stats-locos": "NAJCZĘSTSZE JEDNOSTKI",
"option-mass": "masa",
"option-speed": "prędkość",
"option-length": "długość",
"option-distance": "kilometraż",
"option-timetable": "numer pociągu",
"search-no": "Szukaj nr pociągu...",
"search-driver": "Szukaj maszynisty...",
"detailed-timetable": "Szczegółowy rozkład jazdy pociągu ",
"via-title": "Przez: ",
"no-timetable": "brak rozkładu jazdy",
"distance-exceeded": "Uwaga! Z powodu wewnętrznego błędu serwera TD2, rozkłady jazdy o kilometrażu powyżej 200km mogą być niepoprawne!",
"cars": "Wagony"
},
"journal": {
"title": "DZIENNIK AKTYWNOŚCI SCENERII",
"subtitle": "Pokazuje dyżurnych, którzy ostatnio byli aktywni na wybranej scenerii",
"disclaimer": "<b>Ta funkcjonalność jest w testach beta!</b> \n Informacje pokazywane na ekranie mogą znikać, a ich zawartość może być fałszywa!",
"select": "Wybierz scenerię"
},
"scenery": {
"users": "GRACZE ONLINE",
"spawns": "OTWARTE SPAWNY",
"timetables": "AKTYWNE ROZKŁADY JAZDY",
"no-timetables": "Brak aktywnych rozkładów!",
"no-users": "BRAK AKTYWNYCH GRACZY",
"no-spawns": "BRAK OTWARTYCH SPAWNÓW",
"no-scenery": "Ups! Ta sceneria nie istnieje!",
"return-btn": "Wróć na stronę główną"
},
"timetables": {
"timetable-only": "Wyodrębnij rozkłady jazdy",
"online": "Na stacji",
"departed": "Odprawiony",
"departed-away": "Odjechał",
"arriving": "W drodze",
"stopped": "Postój",
"terminated": "Skończył bieg",
"begins": "ROZPOCZYNA\nBIEG",
"terminates": "KOŃCZY BIEG"
}
}
+15 -18
View File
@@ -1,28 +1,25 @@
import Vue from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
import VueI18n from 'vue-i18n';
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import { store, key } from './store'
import enLang from '@/lang/en.json';
import plLang from '@/lang/pl.json';
import enLang from '@/locales/en.json';
import plLang from '@/locales/pl.json';
Vue.use(VueI18n);
import { createI18n } from 'vue-i18n'
const i18n = new VueI18n({
const i18n = createI18n({
locale: 'pl',
fallbackLocale: 'pl',
messages: {
en: enLang,
pl: plLang,
},
});
enableLegacy: false
})
Vue.config.productionTip = false;
new Vue({
router,
store,
i18n,
render: h => h(App),
}).$mount('#app');
createApp(App)
.use(store, key)
.use(router)
.use(i18n)
.mount('#app')
View File
+40 -41
View File
@@ -1,47 +1,46 @@
import Vue from 'vue';
import Component from 'vue-class-component';
import { defineComponent } from 'vue';
// You can declare mixins as the same style as components.
@Component
export default class styleMixin extends Vue {
calculateExpStyle(exp: string | number, isSupporter: boolean = false): string {
const bgColor = exp > -1 ? (exp < 2 ? '#26B0D9' : `hsl(${-exp * 5 + 100}, 85%, 50%)`) : '#666';
export default defineComponent({
methods: {
calculateExpStyle(exp: string | number, isSupporter = false): string {
const bgColor = exp > -1 ? (exp < 2 ? '#26B0D9' : `hsl(${-exp * 5 + 100}, 85%, 50%)`) : '#666';
const fontColor = exp > 15 || exp == -1 ? 'white' : 'black';
const boxShadow = isSupporter ? `box-shadow: 0 0 10px 2px ${bgColor};` : '';
return `background-color: ${bgColor}; color: ${fontColor}; ${boxShadow}`;
},
const fontColor = exp > 15 || exp == -1 ? 'white' : 'black';
const boxShadow = isSupporter ? `0 0 10px 2px ${bgColor}` : '';
statusClasses(occupiedTo: string) {
let className = '';
return `backgroundColor: ${bgColor}; color: ${fontColor}; box-shadow: ${boxShadow};`;
}
switch (occupiedTo) {
case 'WOLNA':
className = 'free';
break;
case 'KOŃCZY':
className = 'ending';
break;
case 'NIEZALOGOWANY':
className = 'not-signed';
break;
case 'BEZ LIMITU':
className = 'no-limit';
break;
case 'NIEDOSTĘPNY':
className = 'unavailable';
break;
case 'Z/W':
className = 'brb';
break;
case 'BRAK MIEJSCA':
className = 'no-space';
break;
default:
break;
}
statusClasses(occupiedTo: string) {
let className = '';
switch (occupiedTo) {
case 'WOLNA':
className = 'free';
break;
case 'KOŃCZY':
className = 'ending';
break;
case 'NIEZALOGOWANY':
className = 'not-signed';
break;
case 'BEZ LIMITU':
className = 'no-limit';
break;
case 'NIEDOSTĘPNY':
className = 'unavailable';
break;
case 'Z/W':
className = 'brb';
break;
case 'BRAK MIEJSCA':
className = 'no-space';
break;
default:
break;
return className;
}
return className;
}
}
})
+14 -23
View File
@@ -1,22 +1,19 @@
import Vue from "vue";
import VueRouter, { RouteConfig } from "vue-router";
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
import StationsView from "../views/StationsView.vue";
import TrainsView from "../views/TrainsView.vue";
import StationsView from "@/views/StationsView.vue";
Vue.use(VueRouter);
const routes: Array<RouteConfig> = [
const routes: Array<RouteRecordRaw> = [
{
path: "/",
name: "StationsView",
path: '/',
name: 'StationsView',
component: StationsView
},
{
path: "/trains",
name: "TrainsView",
component: TrainsView,
props: true
component: () => import("@/views/TrainsView.vue"),
props: true,
},
{
path: "/scenery",
@@ -24,17 +21,11 @@ const routes: Array<RouteConfig> = [
component: () => import("@/views/SceneryView.vue"),
props: true
},
{
path: "/history",
name: "HistoryView",
component: () => import("@/views/HistoryView.vue")
}
];
]
const router = new VueRouter({
mode: "history",
base: process.env.BASE_URL,
routes
});
const router = createRouter({
history: createWebHashHistory(),
routes,
})
export default router;
export default router
+5 -3
View File
@@ -1,11 +1,12 @@
export default interface Filter {
[key: string]: (boolean | number),
default: boolean;
notDefault: boolean;
real: boolean;
fictional: boolean;
SPK: boolean;
SCS: boolean;
SPE: boolean;
"SPK": boolean;
"SCS": boolean;
"SPE": boolean;
ręczne: boolean;
mechaniczne: boolean;
współczesna: boolean;
@@ -23,4 +24,5 @@ export default interface Filter {
free: boolean;
occupied: boolean;
ending: boolean;
nonPublic: boolean;
}
+1
View File
@@ -54,6 +54,7 @@ export default interface Station {
stationTrains: {
driverName: number;
trainNo: number;
stopStatus?: string;
}[];
scheduledTrains: ScheduledTrain[];
+17
View File
@@ -0,0 +1,17 @@
import TrainStop from "./TrainStop";
export default interface Timetable {
trainNo: number;
driverName: string;
driverId: number;
currentStationName: string;
currentStationHash: string;
timetableId: number;
category: string;
route: string;
TWR: boolean;
SKR: boolean;
routeDistance: number;
followingStops: TrainStop[];
followingSceneries: string[];
}
@@ -0,0 +1,18 @@
export default interface StationAPIData {
dispatcherId: number;
dispatcherName: string;
dispatcherIsSupporter: boolean;
stationName: string;
stationHash: string;
region: string;
maxUsers: number;
currentUsers: number;
spawn: number;
lastSeen: number;
dispatcherExp: number;
nameFromHeader: string;
spawnString: string;
networkConnectionString: string;
isOnline: number;
dispatcherRate: number;
}
@@ -0,0 +1,34 @@
export default interface TimetableAPIData {
trainInfo: {
timetableId: number;
trainNo: number;
trainCategoryCode: string;
driverId: number;
driverName: string;
route: string;
twr: boolean;
skr: boolean;
sceneries: string[];
};
stopPoints: {
arrivalLine: string | null;
arrivalTime: string | null;
arrivalDelay: number;
arrivalRealTime: string | null;
pointDistance: number;
pointName: string;
pointNameRAW: string;
entryId: number;
pointId: number;
comments: string | null;
confirmed: boolean;
isStopped: boolean;
pointStopTime: number | null;
pointStopType: string;
departureLine: string | null;
departureTime: string | null;
departureDelay: number;
departureRealTime: string | null;
}[];
}
@@ -0,0 +1,19 @@
import StationAPIData from "./StationAPIData";
export default interface TrainAPIData {
trainNo: number;
driverId: number;
driverName: string;
driverIsSupporter: boolean;
station: StationAPIData;
dataSignal: string;
dataSceneryConnection: string;
dataDistance: number;
dataCon: string;
dataSpeed: number;
dataMass: number;
dataLength: number;
region: string;
isOnline: boolean;
lastSeen: number;
}
@@ -122,6 +122,7 @@ export default class StationFilterManager {
free: true,
occupied: false,
ending: false,
nonPublic: false
};
private filters: Filter = { ...this.filterInitStates };
+6
View File
@@ -0,0 +1,6 @@
export const URLs = {
stations: "https://api.td2.info.pl:9640/?method=getStationsOnline",
trains: "https://api.td2.info.pl:9640/?method=getTrainsOnline",
dispatchers: "https://api.td2.info.pl:9640/?method=readFromSWDR&value=getDispatcherStatusList%3B1",
getTimetableURL: (trainNo: string | number) => `https://api.td2.info.pl:9640/?method=readFromSWDR&value=getTimetable%3B${trainNo}%3Beu`
};
+11 -23
View File
@@ -1,10 +1,9 @@
import Station from "../interfaces/Station";
import TrainStop from "../interfaces/TrainStop";
const timetableURL = (trainNo: number) => `https://api.td2.info.pl:9640/?method=readFromSWDR&value=getTimetable%3B${trainNo}%3Beu`;
const getLocoURL = (locoType: string) => `https://rj.td2.info.pl/dist/img/thumbnails/${locoType.includes("EN") ? locoType + "rb" : locoType}.png`;
export const getLocoURL = (locoType: string): string => (`https://rj.td2.info.pl/dist/img/thumbnails/${locoType.includes("EN") ? locoType + "rb" : locoType}.png`)
const getStatusID = (stationStatus: any) => {
export const getStatusID = (stationStatus: any): string => {
if (!stationStatus) return "not-signed";
const statusCode = stationStatus[2];
@@ -33,7 +32,7 @@ const getStatusID = (stationStatus: any) => {
return "unavailable";
};
const getStatusTimestamp = (stationStatus: any) => {
export const getStatusTimestamp = (stationStatus: any): number => {
if (!stationStatus) return -2;
const statusCode = stationStatus[2];
@@ -56,7 +55,7 @@ const getStatusTimestamp = (stationStatus: any) => {
return -1;
};
const parseSpawns = (spawnString: string) => {
export const parseSpawns = (spawnString: string) => {
if (!spawnString) return [];
if (spawnString === "NO_SPAWN") return [];
@@ -69,9 +68,9 @@ const parseSpawns = (spawnString: string) => {
});
};
const getTimestamp = (date: string | null) => (date ? new Date(date).getTime() : 0);
export const getTimestamp = (date: string | null): number => (date ? new Date(date).getTime() : 0);
const timestampToString = (timestamp: number | null) =>
export const timestampToString = (timestamp: number | null): string =>
timestamp
? new Date(timestamp).toLocaleTimeString("pl-PL", {
hour: "2-digit",
@@ -79,10 +78,10 @@ const timestampToString = (timestamp: number | null) =>
})
: "";
const getTrainStopStatus = (stopInfo: TrainStop, timetableData: { currentStationName: string }, station: Station) => {
let stopStatus: string = "",
stopLabel: string = "",
stopStatusID: number = -1;
export const getTrainStopStatus = (stopInfo: TrainStop, timetableData: { currentStationName: string }, station: Station) => {
let stopStatus = "",
stopLabel = "",
stopStatusID = -1;
if (stopInfo.terminatesHere && stopInfo.confirmed) {
stopStatus = "terminated";
@@ -111,15 +110,4 @@ const getTrainStopStatus = (stopInfo: TrainStop, timetableData: { currentStation
}
return { stopStatus, stopLabel, stopStatusID };
};
export default {
timetableURL,
getLocoURL,
getStatusID,
parseSpawns,
getTimestamp,
timestampToString,
getStatusTimestamp,
getTrainStopStatus
};
};
-13
View File
@@ -1,13 +0,0 @@
import Vue, { VNode } from 'vue'
declare global {
namespace JSX {
// tslint:disable no-empty-interface
interface Element extends VNode {}
// tslint:disable no-empty-interface
interface ElementClass extends Vue {}
interface IntrinsicElements {
[elem: string]: any
}
}
}
+4 -2
View File
@@ -1,4 +1,6 @@
/* eslint-disable */
declare module '*.vue' {
import Vue from 'vue'
export default Vue
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}
+455 -9
View File
@@ -1,12 +1,458 @@
import Vue from 'vue';
import Vuex from 'vuex';
/* eslint-disable */
import Store from '@/store/store';
import { InjectionKey } from 'vue'
import { createStore, useStore as baseUseStore, Store } from 'vuex'
Vue.use(Vuex);
const store = new Vuex.Store({
modules: {
Store,
import axios from "axios";
import JSONStationData from "@/data/stationData.json";
import Station from "@/scripts/interfaces/Station";
import Train from "@/scripts/interfaces/Train";
import TrainStop from "@/scripts/interfaces/TrainStop";
import { StoreData } from "@/scripts/interfaces/StoreData";
import TimetableAPIData from '@/scripts/interfaces/api/TimetableAPIData';
import StationAPIData from '@/scripts/interfaces/api/StationAPIData';
import TrainAPIData from '@/scripts/interfaces/api/TrainAPIData';
import Timetable from '@/scripts/interfaces/Timetable';
import { ACTIONS, MUTATIONS } from "@/constants/storeConstants";
import { DataStatus } from "@/scripts/enums/DataStatus";
import { getLocoURL, getStatusID, getStatusTimestamp, getTimestamp, getTrainStopStatus, parseSpawns, timestampToString } from "@/scripts/utils/storeUtils";
import { URLs } from '@/scripts/utils/apiURLs';
export interface State {
stationList: Station[],
trainList: Train[],
trainCount: number;
stationCount: number;
dataConnectionStatus: DataStatus;
sceneryDataStatus: DataStatus;
timetableDataStatus: DataStatus;
listenerLaunched: boolean;
}
export const key: InjectionKey<Store<State>> = Symbol()
export const store = createStore<State>({
state: () => ({
stationList: [],
trainList: [],
trainCount: 0,
stationCount: 0,
dataConnectionStatus: DataStatus.Loading,
sceneryDataStatus: DataStatus.Loading,
timetableDataStatus: DataStatus.Loading,
listenerLaunched: false
}),
getters: {
stationList: (state) => state.stationList,
trainList: (state) => state.trainList,
allData: (state): StoreData => ({
stationList: state.stationList,
trainList: state.trainList,
activeTrainCount: state.trainCount,
activeStationCount: state.stationCount,
dataConnectionStatus: state.dataConnectionStatus,
timetableDataStatus: state.timetableDataStatus
}),
timetableDataStatus: (state): DataStatus => state.timetableDataStatus,
sceneryDataStatus: (state): DataStatus => state.sceneryDataStatus,
dataStatus: (state): DataStatus => state.dataConnectionStatus
},
});
export default store;
actions: {
async synchronizeData({ commit, dispatch, state }) {
if(state.listenerLaunched) return;
commit(MUTATIONS.SET_SCENERY_DATA);
commit(MUTATIONS.SET_SCENERY_DATA_STATUS, DataStatus.Loaded);
dispatch(ACTIONS.fetchOnlineData);
setInterval(() => dispatch(ACTIONS.fetchOnlineData), 30000);
},
async fetchOnlineData({ commit, dispatch }) {
Promise.all([axios.get(URLs.stations), axios.get(URLs.trains), axios.get(URLs.dispatchers)])
.then(async response => {
const onlineStationsData: StationAPIData[] = response[0].data.message;
const onlineTrainsData: TrainAPIData[] = await response[1].data.message;
const onlineDispatchersData: string[][] = await response[2].data.message;
const updatedStationList = onlineStationsData.reduce((acc, station) => {
if (station.region !== "eu" || !station.isOnline) return acc;
const stationStatus = onlineDispatchersData.find((status: string[]) => status[0] == station.stationHash && status[1] == "eu");
const statusTimestamp = getStatusTimestamp(stationStatus);
const statusID = getStatusID(stationStatus);
const stationTrains = onlineTrainsData
.filter(train => train.region === "eu" && train.isOnline && train.station.stationName === station.stationName)
.map(train => ({ driverName: train.driverName, trainNo: train.trainNo }));
acc.push({
stationName: station.stationName,
stationHash: station.stationHash,
maxUsers: station.maxUsers,
currentUsers: station.currentUsers,
spawns: parseSpawns(station.spawnString),
dispatcherName: station.dispatcherName,
dispatcherRate: station.dispatcherRate,
dispatcherId: station.dispatcherId,
dispatcherExp: station.dispatcherExp,
dispatcherIsSupporter: station.dispatcherIsSupporter,
stationTrains,
statusTimestamp,
statusID,
statusTimeString: timestampToString(statusTimestamp)
});
return acc;
}, [] as any);
const updatedTrainList = await Promise.all(
onlineTrainsData
.filter(train => train.region === "eu")
.map(async train => {
const locoType = train.dataCon.split(";") ? train.dataCon.split(";")[0] : train.dataCon;
return {
trainNo: train.trainNo,
mass: train.dataMass,
length: train.dataLength,
speed: train.dataSpeed,
distance: train.dataDistance,
signal: train.dataSignal,
online: train.isOnline,
driverId: train.driverId,
driverName: train.driverName,
currentStationName: train.station.stationName,
currentStationHash: train.station.stationHash,
connectedTrack: train.dataSceneryConnection,
locoType,
locoURL: getLocoURL(locoType),
cars: train.dataCon.split(";").filter((train, i) => i > 0) || []
};
})
);
// Pass reduced lists to mutations
commit(MUTATIONS.UPDATE_STATIONS, updatedStationList);
commit(MUTATIONS.UPDATE_TRAINS, updatedTrainList);
dispatch(ACTIONS.fetchTimetableData);
})
.catch(() => {
commit(MUTATIONS.SET_DATA_CONNECTION_STATUS, DataStatus.Error);
});
},
async fetchTimetableData({ commit }) {
const reducedList = this.state.trainList.reduce(async (acc: Promise<Timetable[]>, train: Train) => {
const timetable: TimetableAPIData = await (await axios.get(URLs.getTimetableURL(train.trainNo))).data.message;
const trainInfo = timetable.trainInfo;
if (!timetable || !trainInfo) return acc;
const followingStops: TrainStop[] = timetable.stopPoints.reduce((stopsAcc: TrainStop[], point) => {
if (point.pointNameRAW.toLowerCase().includes("sbl")) return stopsAcc;
const arrivalTimestamp = getTimestamp(point.arrivalTime);
const arrivalRealTimestamp = getTimestamp(point.arrivalRealTime);
const departureTimestamp = getTimestamp(point.departureTime);
const departureRealTimestamp = getTimestamp(point.departureRealTime);
stopsAcc.push({
stopName: point.pointName,
stopNameRAW: point.pointNameRAW,
stopType: point.pointStopType,
stopDistance: point.pointDistance,
mainStop: point.pointName.includes("strong"),
arrivalLine: point.arrivalLine,
arrivalTimeString: timestampToString(arrivalTimestamp),
arrivalTimestamp: arrivalTimestamp,
arrivalRealTimeString: timestampToString(arrivalRealTimestamp),
arrivalRealTimestamp: arrivalRealTimestamp,
arrivalDelay: point.arrivalDelay,
departureLine: point.departureLine,
departureTimeString: timestampToString(departureTimestamp),
departureTimestamp: departureTimestamp,
departureRealTimeString: timestampToString(departureRealTimestamp),
departureRealTimestamp: departureRealTimestamp,
departureDelay: point.departureDelay,
beginsHere: arrivalTimestamp == 0,
terminatesHere: departureTimestamp == 0,
confirmed: point.confirmed,
stopped: point.isStopped,
stopTime: point.pointStopTime
});
return stopsAcc;
}, []);
(await acc).push({
trainNo: train.trainNo,
driverName: train.driverName,
driverId: train.driverId,
currentStationName: train.currentStationName,
currentStationHash: train.currentStationHash,
timetableId: trainInfo.timetableId,
category: trainInfo.trainCategoryCode,
route: trainInfo.route,
TWR: trainInfo.twr,
SKR: trainInfo.skr,
routeDistance: timetable.stopPoints[timetable.stopPoints.length - 1].pointDistance,
followingStops,
followingSceneries: trainInfo.sceneries
});
return acc;
}, Promise.resolve([]));
commit(MUTATIONS.UPDATE_TIMETABLES, (await reducedList));
}
},
mutations: {
SET_SCENERY_DATA(state) {
state.stationList = JSONStationData.map(station => ({
stationName: station[0] as string,
stationURL: station[1] as string,
stationLines: station[2] as string,
stationProject: station[3] as string,
reqLevel: station[4] as string,
supportersOnly: station[5] == "TAK",
signalType: station[6] as string,
controlType: station[7] as string,
SBL: station[8] as string,
TWB: station[9] as string,
routes: {
oneWay: {
catenary: station[10] as number,
noCatenary: station[11] as number
},
twoWay: {
catenary: station[12] as number,
noCatenary: station[13] as number
}
},
checkpoints: station[14] ? (station[14] as string[]).map(sub => ({ checkpointName: sub, scheduledTrains: [] })) : null,
stops: station[15] as string[],
default: station[16] as boolean,
nonPublic: station[17] as boolean,
unavailable: station[18] as boolean,
stationHash: "",
maxUsers: 0,
currentUsers: 0,
dispatcherName: "",
dispatcherRate: 0,
dispatcherExp: -1,
dispatcherId: 0,
dispatcherIsSupporter: false,
online: false,
statusTimestamp: -3,
statusID: "free",
statusTimeString: "",
stationTrains: [],
scheduledTrains: [],
spawns: []
}));
},
SET_SCENERY_DATA_STATUS(state, status: DataStatus) {
state.sceneryDataStatus = status;
},
SET_DATA_CONNECTION_STATUS(state, status: DataStatus) {
state.dataConnectionStatus = status;
},
UPDATE_STATIONS(state, updatedStationList: any[]) {
state.stationList = state.stationList.reduce((acc: Station[], station) => {
const onlineStationData = updatedStationList.find(updatedStation => updatedStation.stationName === station.stationName);
const listedStationData = JSONStationData.find(data => data[0] === station.stationName);
if (onlineStationData)
acc.push({
...station,
...onlineStationData,
online: true
});
else if (listedStationData)
acc.push({
...station,
stationProject: "",
stationHash: "",
maxUsers: 0,
currentUsers: 0,
dispatcherName: "",
dispatcherRate: 0,
dispatcherExp: -1,
dispatcherId: 0,
dispatcherIsSupporter: false,
online: false,
statusID: "free",
statusTimestamp: -3,
statusTimeString: "",
stationTrains: [],
scheduledTrains: [],
checkpoints: null
});
return acc;
}, [] as Station[]);
updatedStationList
.filter(uStation => !state.stationList.some(station => uStation.stationName === station.stationName))
.forEach(uStation => {
state.stationList.push({
...uStation,
scheduledTrains: [],
stationTrains: uStation.stationTrains || [],
subStations: [],
online: true,
reqLevel: "-1",
nonPublic: true
});
});
state.stationCount = state.stationList.filter(station => station.online).length;
state.dataConnectionStatus = DataStatus.Loaded;
},
UPDATE_TRAINS(state, updatedTrainList: any[]) {
state.trainList = updatedTrainList.reduce((acc, updatedTrain) => {
const trainData = state.trainList.find(train => train.trainNo === updatedTrain.trainNo);
if (trainData) acc.push({ ...trainData, ...updatedTrain });
else acc.push({ ...updatedTrain });
return acc;
}, [] as Train[]);
state.trainCount = state.trainList.filter(train => train.online).length;
state.dataConnectionStatus = DataStatus.Loaded;
},
UPDATE_TIMETABLES(state, timetableList: Timetable[]) {
state.stationList = state.stationList.map(station => {
const stationName = station.stationName.toLowerCase();
const scheduledTrains: Station["scheduledTrains"] = timetableList.reduce((acc: Station["scheduledTrains"], timetable: Timetable) => {
if (!timetable.followingSceneries.includes(station.stationHash)) return acc;
const stopInfoIndex = timetable.followingStops.findIndex(stop => {
const stopName = stop.stopNameRAW.toLowerCase();
if (stationName === stopName) return true;
if (stopName.includes(stationName) && !stop.stopName.includes("po.") && !stop.stopName.includes("podg.")) return true;
if (stationName.includes(stopName) && !stop.stopName.includes("po.") && !stop.stopName.includes("podg.")) return true;
if (stopName.includes("podg.") && stopName.split(", podg.")[0] && stationName.includes(stopName.split(", podg.")[0])) return true;
if (station.stops && station.stops.includes(stop.stopNameRAW)) return true;
return false;
});
if (stopInfoIndex == -1) return acc;
const trainStop = timetable.followingStops[stopInfoIndex];
const trainStopStatus = getTrainStopStatus(trainStop, timetable, station);
acc.push({
trainNo: timetable.trainNo,
driverName: timetable.driverName,
driverId: timetable.driverId,
currentStationName: timetable.currentStationName,
currentStationHash: timetable.currentStationHash,
category: timetable.category,
beginsAt: timetable.followingStops[0].stopNameRAW,
terminatesAt: timetable.followingStops[timetable.followingStops.length - 1].stopNameRAW,
nearestStop: "",
stopInfo: trainStop,
stopLabel: trainStopStatus.stopLabel,
stopStatus: trainStopStatus.stopStatus,
stopStatusID: trainStopStatus.stopStatusID
});
return acc;
}, []);
if (station.checkpoints) {
station.checkpoints.forEach(cp => (cp.scheduledTrains.length = 0));
for (const checkpoint of station.checkpoints) {
timetableList.forEach(timetable => {
timetable.followingStops
.filter(trainStop => trainStop.stopNameRAW.toLowerCase() === checkpoint.checkpointName.toLowerCase())
.forEach(trainStop => {
const trainStopStatus = getTrainStopStatus(trainStop, timetable, station);
checkpoint.scheduledTrains.push({
trainNo: timetable.trainNo,
driverName: timetable.driverName,
driverId: timetable.driverId,
currentStationName: timetable.currentStationName,
currentStationHash: timetable.currentStationHash,
category: timetable.category,
beginsAt: timetable.followingStops[0].stopNameRAW,
terminatesAt: timetable.followingStops[timetable.followingStops.length - 1].stopNameRAW,
nearestStop: "",
stopInfo: trainStop,
stopLabel: trainStopStatus.stopLabel,
stopStatus: trainStopStatus.stopStatus,
stopStatusID: trainStopStatus.stopStatusID
});
});
});
}
}
return { ...station, scheduledTrains };
});
state.trainList = state.trainList.reduce((acc, train) => {
const timetableData = timetableList.find(data => data && data.trainNo === train.trainNo);
const trainStopData = state.stationList
.find(station => station.stationName === train.currentStationName)
?.scheduledTrains.find(stationTrain => stationTrain.trainNo === train.trainNo);
acc.push({ ...train, timetableData, stopStatus: trainStopData?.stopStatus || "", stopLabel: trainStopData?.stopLabel || "" });
return acc;
}, [] as Train[]);
state.timetableDataStatus = DataStatus.Loaded;
}
}
})
export function useStore(): Store<State> {
return baseUseStore(key)
}
-591
View File
@@ -1,591 +0,0 @@
import { Module, VuexModule, Mutation, Action } from "vuex-module-decorators";
import axios from "axios";
import JSONStationData from "@/data/stationData.json";
import Station from "@/scripts/interfaces/Station";
import Train from "@/scripts/interfaces/Train";
import TrainStop from "@/scripts/interfaces/TrainStop";
import utils from "@/scripts/utils/storeUtils";
import { DataStatus } from "@/scripts/enums/DataStatus";
import { StoreData } from "@/scripts/interfaces/StoreData";
interface TimetableAPIData {
trainInfo: {
timetableId: number;
trainNo: number;
trainCategoryCode: string;
driverId: number;
driverName: string;
route: string;
twr: boolean;
skr: boolean;
sceneries: string[];
};
stopPoints: {
arrivalLine: string | null;
arrivalTime: string | null;
arrivalDelay: number;
arrivalRealTime: string | null;
pointDistance: number;
pointName: string;
pointNameRAW: string;
entryId: number;
pointId: number;
comments: string | null;
confirmed: boolean;
isStopped: boolean;
pointStopTime: number | null;
pointStopType: string;
departureLine: string | null;
departureTime: string | null;
departureDelay: number;
departureRealTime: string | null;
}[];
}
interface StationAPIData {
dispatcherId: number;
dispatcherName: string;
dispatcherIsSupporter: boolean;
stationName: string;
stationHash: string;
region: string;
maxUsers: number;
currentUsers: number;
spawn: number;
lastSeen: number;
dispatcherExp: number;
nameFromHeader: string;
spawnString: string;
networkConnectionString: string;
isOnline: number;
dispatcherRate: number;
}
interface TrainAPIData {
trainNo: number;
driverId: number;
driverName: string;
driverIsSupporter: boolean;
station: StationAPIData;
dataSignal: string;
dataSceneryConnection: string;
dataDistance: number;
dataCon: string;
dataSpeed: number;
dataMass: number;
dataLength: number;
region: string;
isOnline: boolean;
lastSeen: number;
}
interface Timetable {
trainNo: number;
driverName: string;
driverId: number;
currentStationName: string;
currentStationHash: string;
timetableId: number;
category: string;
route: string;
TWR: boolean;
SKR: boolean;
routeDistance: number;
followingStops: TrainStop[];
followingSceneries: string[];
}
// interface OnlineStationData {
// dispatcherId: number;
// dispatcherName: string;
// dispatcherIsSupporter: boolean;
// stationName: string;
// stationHash: string;
// region: string;
// maxUsers: number;
// currentUsers: number;
// spawn: number;
// lastSeen: number;
// dispatcherExp: number;
// nameFromHeader: string;
// spawnString: string;
// networkConnectionString: string;
// isOnline: number;
// dispatcherRate: number;
// }
// interface TrainData {
// trainNo: number;
// driverId: number;
// driverName: string;
// driverIsSupporter: boolean;
// dataSignal: string;
// dataSceneryConnection: string;
// dataDistance: number;
// dataCon: string;
// dataSpeed: number;
// dataMass: number;
// dataLength: number;
// station: OnlineStationData;
// region: string;
// isOnline: number;
// lastSeen: number;
// }
const URLs = {
stations: "https://api.td2.info.pl:9640/?method=getStationsOnline",
trains: "https://api.td2.info.pl:9640/?method=getTrainsOnline",
dispatchers: "https://api.td2.info.pl:9640/?method=readFromSWDR&value=getDispatcherStatusList%3B1"
};
@Module
export default class Store extends VuexModule {
private trainCount: number = 0;
private stationCount: number = 0;
private dataConnectionStatus: DataStatus = DataStatus.Loading;
private sceneryDataStatus: DataStatus = DataStatus.Loading;
private timetableDataStatus: DataStatus = DataStatus.Loading;
private stationList: Station[] = [];
private trainList: Train[] = [];
//GETTERS
get getAllData(): StoreData {
return {
stationList: this.stationList,
trainList: this.trainList,
activeTrainCount: this.trainCount,
activeStationCount: this.stationCount,
dataConnectionStatus: this.dataConnectionStatus,
timetableDataStatus: this.timetableDataStatus
};
}
get getStationList() {
return this.stationList;
}
get getTrainList() {
return this.trainList;
}
get getTimetableDataStatus() {
return this.timetableDataStatus;
}
get getDataStatus() {
return this.dataConnectionStatus;
}
get getSceneryDataStatus() {
return this.sceneryDataStatus;
}
//ACTIONS
@Action
async synchronizeData() {
this.context.commit("setSceneryData");
this.context.commit("setSceneryDataStatus", DataStatus.Loaded);
this.context.dispatch("fetchOnlineData");
setInterval(() => this.context.dispatch("fetchOnlineData"), 20000);
}
// Fetching all station and train data from API
@Action
async fetchOnlineData() {
Promise.all([axios.get(URLs.stations), axios.get(URLs.trains), axios.get(URLs.dispatchers)])
.then(async response => {
const onlineStationsData: StationAPIData[] = response[0].data.message;
const onlineTrainsData: TrainAPIData[] = await response[1].data.message;
const onlineDispatchersData: string[][] = await response[2].data.message;
const updatedStationList = onlineStationsData.reduce((acc, station) => {
if (station.region !== "eu" || !station.isOnline) return acc;
const stationStatus = onlineDispatchersData.find((status: string[]) => status[0] == station.stationHash && status[1] == "eu");
const statusTimestamp = utils.getStatusTimestamp(stationStatus);
const statusID = utils.getStatusID(stationStatus);
const stationTrains = onlineTrainsData
.filter(train => train.region === "eu" && train.isOnline && train.station.stationName === station.stationName)
.map(train => ({ driverName: train.driverName, trainNo: train.trainNo }));
acc.push({
stationName: station.stationName,
stationHash: station.stationHash,
maxUsers: station.maxUsers,
currentUsers: station.currentUsers,
spawns: utils.parseSpawns(station.spawnString),
dispatcherName: station.dispatcherName,
dispatcherRate: station.dispatcherRate,
dispatcherId: station.dispatcherId,
dispatcherExp: station.dispatcherExp,
dispatcherIsSupporter: station.dispatcherIsSupporter,
stationTrains,
statusTimestamp,
statusID,
statusTimeString: utils.timestampToString(statusTimestamp)
});
return acc;
}, [] as any);
const updatedTrainList = await Promise.all(
onlineTrainsData
.filter(train => train.region === "eu")
.map(async train => {
const locoType = train.dataCon.split(";") ? train.dataCon.split(";")[0] : train.dataCon;
return {
trainNo: train.trainNo,
mass: train.dataMass,
length: train.dataLength,
speed: train.dataSpeed,
distance: train.dataDistance,
signal: train.dataSignal,
online: train.isOnline,
driverId: train.driverId,
driverName: train.driverName,
currentStationName: train.station.stationName,
currentStationHash: train.station.stationHash,
connectedTrack: train.dataSceneryConnection,
locoType,
locoURL: utils.getLocoURL(locoType),
cars: train.dataCon.split(";").filter((train, i) => i > 0) || []
};
})
);
this.context.commit("updateOnlineStations", updatedStationList);
this.context.commit("updateOnlineTrains", updatedTrainList);
this.context.dispatch("fetchTimetableData");
})
.catch(() => {
this.context.commit("setDataConnectionStatus", DataStatus.Error);
});
}
// Fetching timetable data from API basing on online trains
@Action({ commit: "updateTimetableData" })
async fetchTimetableData() {
return this.trainList.reduce(async (acc: Promise<Timetable[]>, train: Train) => {
const timetable: TimetableAPIData = await (await axios.get(utils.timetableURL(train.trainNo))).data.message;
const trainInfo = timetable.trainInfo;
if (!timetable || !trainInfo) return acc;
const followingStops: TrainStop[] = timetable.stopPoints.reduce((stopsAcc: TrainStop[], point) => {
if (point.pointNameRAW.toLowerCase().includes("sbl")) return stopsAcc;
const arrivalTimestamp = utils.getTimestamp(point.arrivalTime);
const arrivalRealTimestamp = utils.getTimestamp(point.arrivalRealTime);
const departureTimestamp = utils.getTimestamp(point.departureTime);
const departureRealTimestamp = utils.getTimestamp(point.departureRealTime);
stopsAcc.push({
stopName: point.pointName,
stopNameRAW: point.pointNameRAW,
stopType: point.pointStopType,
stopDistance: point.pointDistance,
mainStop: point.pointName.includes("strong"),
arrivalLine: point.arrivalLine,
arrivalTimeString: utils.timestampToString(arrivalTimestamp),
arrivalTimestamp: arrivalTimestamp,
arrivalRealTimeString: utils.timestampToString(arrivalRealTimestamp),
arrivalRealTimestamp: arrivalRealTimestamp,
arrivalDelay: point.arrivalDelay,
departureLine: point.departureLine,
departureTimeString: utils.timestampToString(departureTimestamp),
departureTimestamp: departureTimestamp,
departureRealTimeString: utils.timestampToString(departureRealTimestamp),
departureRealTimestamp: departureRealTimestamp,
departureDelay: point.departureDelay,
beginsHere: arrivalTimestamp == 0,
terminatesHere: departureTimestamp == 0,
confirmed: point.confirmed,
stopped: point.isStopped,
stopTime: point.pointStopTime
});
return stopsAcc;
}, []);
(await acc).push({
trainNo: train.trainNo,
driverName: train.driverName,
driverId: train.driverId,
currentStationName: train.currentStationName,
currentStationHash: train.currentStationHash,
timetableId: trainInfo.timetableId,
category: trainInfo.trainCategoryCode,
route: trainInfo.route,
TWR: trainInfo.twr,
SKR: trainInfo.skr,
routeDistance: timetable.stopPoints[timetable.stopPoints.length - 1].pointDistance,
followingStops,
followingSceneries: trainInfo.sceneries
});
return acc;
}, Promise.resolve([]));
}
//MUTATIONS
@Mutation
private setDataConnectionStatus(status: DataStatus) {
this.dataConnectionStatus = status;
}
@Mutation
private setSceneryDataStatus(status: DataStatus) {
this.sceneryDataStatus = status;
}
@Mutation
private setSceneryData() {
/*
0: stationName,
1: stationURL,
2: stationlines,
3: stationProject?,
4: reqLevel,
5: supportersOnly,
6: signalType,
7: controlType,
8: SBL,
9: two-way block,
10: routes, one-way, catenary,
11: routes, one-way, no catenary,
12: routes, two-way, catenary,
13: routes, two-way, no catenary,
14: subStations?,
15: stops?,
16: default,
17: nonPublic,
18: unavailable
*/
this.stationList = JSONStationData.map(station => ({
stationName: station[0] as string,
stationURL: station[1] as string,
stationLines: station[2] as string,
stationProject: station[3] as string,
reqLevel: station[4] as string,
supportersOnly: station[5] == "TAK",
signalType: station[6] as string,
controlType: station[7] as string,
SBL: station[8] as string,
TWB: station[9] as string,
routes: {
oneWay: {
catenary: station[10] as number,
noCatenary: station[11] as number
},
twoWay: {
catenary: station[12] as number,
noCatenary: station[13] as number
}
},
checkpoints: station[14] ? (station[14] as string[]).map(sub => ({ checkpointName: sub, scheduledTrains: [] })) : null,
stops: station[15] as string[],
default: station[16] as boolean,
nonPublic: station[17] as boolean,
unavailable: station[18] as boolean,
stationHash: "",
maxUsers: 0,
currentUsers: 0,
dispatcherName: "",
dispatcherRate: 0,
dispatcherExp: -1,
dispatcherId: 0,
dispatcherIsSupporter: false,
online: false,
statusTimestamp: -3,
statusID: "free",
statusTimeString: "",
stationTrains: [],
scheduledTrains: [],
spawns: []
}));
}
@Mutation
private updateOnlineStations(updatedStationList: any[]) {
this.stationList = this.stationList.reduce((acc: Station[], station) => {
const onlineStationData = updatedStationList.find(updatedStation => updatedStation.stationName === station.stationName);
const listedStationData = JSONStationData.find(data => data[0] === station.stationName);
if (onlineStationData)
acc.push({
...station,
...onlineStationData,
online: true
});
else if (listedStationData)
acc.push({
...station,
stationProject: "",
stationHash: "",
maxUsers: 0,
currentUsers: 0,
dispatcherName: "",
dispatcherRate: 0,
dispatcherExp: -1,
dispatcherId: 0,
dispatcherIsSupporter: false,
online: false,
statusID: "free",
statusTimestamp: -3,
statusTimeString: "",
stationTrains: [],
scheduledTrains: [],
checkpoints: null
});
return acc;
}, [] as Station[]);
updatedStationList
.filter(uStation => !this.stationList.some(station => uStation.stationName === station.stationName))
.forEach(uStation => {
this.stationList.push({
...uStation,
scheduledTrains: [],
stationTrains: uStation.stationTrains || [],
subStations: [],
online: true,
reqLevel: "-1",
nonPublic: true
});
});
this.stationCount = this.stationList.filter(station => station.online).length;
this.dataConnectionStatus = DataStatus.Loaded;
}
@Mutation
private updateOnlineTrains(updatedTrainList) {
this.trainList = updatedTrainList.reduce((acc, updatedTrain) => {
const trainData = this.trainList.find(train => train.trainNo === updatedTrain.trainNo);
if (trainData) acc.push({ ...trainData, ...updatedTrain });
else acc.push({ ...updatedTrain });
return acc;
}, [] as Train[]);
this.trainCount = this.trainList.filter(train => train.online).length;
this.dataConnectionStatus = DataStatus.Loaded;
}
@Mutation
private updateTimetableData(timetableList: Timetable[]) {
this.stationList = this.stationList.map(station => {
const stationName = station.stationName.toLowerCase();
const scheduledTrains: Station["scheduledTrains"] = timetableList.reduce((acc: Station["scheduledTrains"], timetable: Timetable, index) => {
if (!timetable.followingSceneries.includes(station.stationHash)) return acc;
const stopInfoIndex = timetable.followingStops.findIndex(stop => {
const stopName = stop.stopNameRAW.toLowerCase();
if (stationName === stopName) return true;
if (stopName.includes(stationName) && !stop.stopName.includes("po.") && !stop.stopName.includes("podg.")) return true;
if (stationName.includes(stopName) && !stop.stopName.includes("po.") && !stop.stopName.includes("podg.")) return true;
if (stopName.includes("podg.") && stopName.split(", podg.")[0] && stationName.includes(stopName.split(", podg.")[0])) return true;
if (station.stops && station.stops.includes(stop.stopNameRAW)) return true;
return false;
});
if (stopInfoIndex == -1) return acc;
const trainStop = timetable.followingStops[stopInfoIndex];
const trainStopStatus = utils.getTrainStopStatus(trainStop, timetable, station);
acc.push({
trainNo: timetable.trainNo,
driverName: timetable.driverName,
driverId: timetable.driverId,
currentStationName: timetable.currentStationName,
currentStationHash: timetable.currentStationHash,
category: timetable.category,
beginsAt: timetable.followingStops[0].stopNameRAW,
terminatesAt: timetable.followingStops[timetable.followingStops.length - 1].stopNameRAW,
nearestStop: "",
stopInfo: trainStop,
stopLabel: trainStopStatus.stopLabel,
stopStatus: trainStopStatus.stopStatus,
stopStatusID: trainStopStatus.stopStatusID
});
return acc;
}, []);
if (station.checkpoints) {
station.checkpoints.forEach(cp => (cp.scheduledTrains.length = 0));
for (let checkpoint of station.checkpoints) {
timetableList.forEach(timetable => {
timetable.followingStops
.filter(trainStop => trainStop.stopNameRAW.toLowerCase() === checkpoint.checkpointName.toLowerCase())
.forEach(trainStop => {
const trainStopStatus = utils.getTrainStopStatus(trainStop, timetable, station);
checkpoint.scheduledTrains.push({
trainNo: timetable.trainNo,
driverName: timetable.driverName,
driverId: timetable.driverId,
currentStationName: timetable.currentStationName,
currentStationHash: timetable.currentStationHash,
category: timetable.category,
beginsAt: timetable.followingStops[0].stopNameRAW,
terminatesAt: timetable.followingStops[timetable.followingStops.length - 1].stopNameRAW,
nearestStop: "",
stopInfo: trainStop,
stopLabel: trainStopStatus.stopLabel,
stopStatus: trainStopStatus.stopStatus,
stopStatusID: trainStopStatus.stopStatusID
});
});
});
}
}
return { ...station, scheduledTrains };
});
this.trainList = this.trainList.reduce((acc, train) => {
const timetableData = timetableList.find(data => data && data.trainNo === train.trainNo);
const trainStopData = this.stationList
.find(station => station.stationName === train.currentStationName)
?.scheduledTrains.find(stationTrain => stationTrain.trainNo === train.trainNo);
acc.push({ ...train, timetableData, stopStatus: trainStopData?.stopStatus || "", stopLabel: trainStopData?.stopLabel || "" });
return acc;
}, [] as Train[]);
this.timetableDataStatus = DataStatus.Loaded;
}
}
-333
View File
@@ -1,333 +0,0 @@
<template>
<div class="history_view">
<div class="history_wrapper">
<div class="header">
<h2>{{ $t("journal.title") }}</h2>
<p style="color: #ccc">
{{ $t("journal.subtitle") }}
</p>
<!-- <select-box
:itemList="filteredStationList"
:title="$t('journal.select')"
@itemSelected="itemSelected"
/> -->
<div class="disclaimer" v-html="$t('journal.disclaimer')"></div>
</div>
<div class="list">
<div class="list_wrapper">
<!-- <div class="list_loading" v-if="dataLoading">POBIERANIE DANYCH...</div> -->
<transition name="list-anim" mode="out-in">
<ul
class="list_content"
v-if="
!dataLoading &&
!historyLoading &&
computedHistoryList.length != 0
"
:key="inputStationName"
>
<li v-if="currentDispatcherFrom != -1" class="current">
<div class="dispatcher-name">
<a
:href="`https://td2.info.pl/profile/?u=${currentDispatcherId}`"
>{{ currentDispatcher }}</a
>
</div>
<div class="dispatcher-date">
<span style="color: #bbb">{{
new Date(currentDispatcherFrom).toLocaleDateString("pl-PL")
}}</span>
{{
new Date(currentDispatcherFrom).toLocaleTimeString(
"pl-PL",
{ hour: "2-digit", minute: "2-digit" }
)
}}
</div>
</li>
<li v-for="(history, i) in computedHistoryList" :key="i">
<div class="dispatcher-name">
<a
:href="`https://td2.info.pl/profile/?u=${history.dispatcherId}`"
>{{ history.dispatcherName }}</a
>
</div>
<div class="dispatcher-date">
<div>
<span style="color: #888">{{
history.dispatcherFromDate
}}</span>
{{ history.dispatcherFromTime }}
</div>
<div>
<span style="color: #888">{{
history.dispatcherToDate
}}</span>
{{ history.dispatcherToTime }}
</div>
</div>
</li>
</ul>
</transition>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import axios from "axios";
import { Component, Vue, Watch } from "vue-property-decorator";
import { Getter } from "vuex-class";
import Station from "@/scripts/interfaces/Station";
import Scenery from "@/scripts/interfaces/Scenery";
import SelectBox from "@/components/Global/SelectBox.vue";
@Component({ components: { SelectBox } })
export default class HistoryView extends Vue {
@Getter("getStationList") stationList!: Station[];
sceneryHistoryList: Scenery[] = [];
currentSceneryHistory: Scenery["dispatcherHistory"] = [];
currentDispatcher: string = "";
currentDispatcherId: number = 0;
currentDispatcherFrom: number = -1;
inputStationName = "";
dataLoading = true; /* Initial data */
historyLoading = false; /* History loaded after input is checked */
async mounted() {
try {
const responseData: Scenery[] = await (
await axios.get(
"https://stacjownik.herokuapp.com/api/getSceneryInfo?items=-1"
)
).data;
this.sceneryHistoryList = responseData;
} catch (error) {
console.error(error);
}
this.dataLoading = false;
}
get filteredStationList() {
return this.sceneryHistoryList
.map((station) => station.stationName)
.sort((a, b) => (a.toLowerCase() >= b.toLowerCase() ? 1 : -1));
}
get computedHistoryList() {
return this.currentSceneryHistory
.map(
({ dispatcherName, dispatcherFrom, dispatcherTo, dispatcherId }) => ({
dispatcherName,
dispatcherFrom,
dispatcherTo,
dispatcherId,
dispatcherFromDate: new Date(dispatcherFrom).toLocaleDateString(
"pl-PL"
),
dispatcherFromTime: new Date(dispatcherFrom).toLocaleTimeString(
"pl-PL",
{ hour: "2-digit", minute: "2-digit" }
),
dispatcherToDate: new Date(dispatcherTo).toLocaleDateString("pl-PL"),
dispatcherToTime: new Date(dispatcherTo).toLocaleTimeString("pl-PL", {
hour: "2-digit",
minute: "2-digit",
}),
})
)
.reverse();
}
async itemSelected(itemName: string) {
try {
this.historyLoading = true;
const selectedScenery: Scenery = await (
await axios.get(
`https://stacjownik.herokuapp.com/api/getSceneryInfo?name=${itemName}&items=10`
)
).data;
this.currentSceneryHistory = selectedScenery.dispatcherHistory;
this.currentDispatcher = selectedScenery.currentDispatcher;
this.currentDispatcherId = selectedScenery.currentDispatcherId;
this.currentDispatcherFrom = selectedScenery.currentDispatcherFrom;
} catch (error) {
console.error(error);
}
this.historyLoading = false;
}
}
</script>
<style scoped lang="scss">
@import "../styles/responsive.scss";
.history {
&_view {
font-size: 1.2em;
}
&_wrapper {
width: 100%;
height: 100%;
text-align: center;
margin-top: 0.5em;
}
}
.list-anim {
&-enter-active,
&-leave-active {
transition: all 150ms ease-out;
}
&-enter,
&-leave-to {
opacity: 0.1;
transform: scale(0.95);
}
&-move {
transition: transform 100ms;
}
}
.disclaimer {
color: #aaa;
}
.search-box {
display: flex;
justify-content: center;
align-items: center;
&_content {
position: relative;
margin: 1em 0;
font-size: 1em;
}
select {
border: none;
font-size: 1em;
background-color: rgb(87, 87, 87);
padding: 0.3em;
padding-right: 50px;
outline: none;
color: white;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
cursor: pointer;
}
label {
position: relative;
&.disabled::after {
color: gray;
}
&::after {
content: "<>";
position: absolute;
top: -5%;
right: 0.3em;
font-weight: bold;
}
}
}
.list {
text-align: center;
margin: 1em 0;
display: flex;
justify-content: center;
&_loading,
&_no-info {
margin: 0.3em 0;
padding: 0.5em 2em;
color: white;
}
&_loading {
background-color: #b96b11;
}
&_no-info {
background-color: firebrick;
}
&_wrapper {
@include smallScreen() {
width: 95%;
font-size: 0.9em;
}
}
&_content {
max-height: 75vh;
overflow: auto;
padding: 0.2em 0.5em;
}
&_content > li {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
background: #222;
padding: 0.3em 0.8em;
margin: 0.3em 0;
gap: 10em;
@include smallScreen() {
gap: 1em;
}
& > div {
margin: 0 1em;
}
&.current {
background: #007200;
}
& > .dispatcher-name {
display: flex;
justify-content: center;
align-items: center;
font-size: 1.1em;
font-weight: 500;
}
}
}
</style>
+51 -36
View File
@@ -11,8 +11,14 @@
</action-button>
</div>
<div class="scenery-wrapper" v-if="stationInfo">
<SceneryInfo :stationInfo="stationInfo" :timetableOnly="timetableOnly" />
<div
class="scenery-wrapper"
v-if="stationInfo"
>
<SceneryInfo
:stationInfo="stationInfo"
:timetableOnly="timetableOnly"
/>
<SceneryTimetable
:stationInfo="stationInfo"
@@ -24,49 +30,58 @@
</template>
<script lang="ts">
import { Vue, Component } from "vue-property-decorator";
import { Getter } from "vuex-class";
import Station from "@/scripts/interfaces/Station";
import { StoreData } from "@/scripts/interfaces/StoreData";
import { DataStatus } from "@/scripts/enums/DataStatus";
import SceneryInfo from "@/components/SceneryView/SceneryInfo.vue";
import SceneryTimetable from "@/components/SceneryView/SceneryTimetable.vue";
import { StoreData } from "@/scripts/interfaces/StoreData";
import { DataStatus } from "@/scripts/enums/DataStatus";
import ActionButton from "@/components/Global/ActionButton.vue";
@Component({
import { computed, ComputedRef, defineComponent } from "@vue/runtime-core";
import { useStore } from "@/store";
import { GETTERS } from "@/constants/storeConstants";
import { useRoute } from "vue-router";
export default defineComponent({
components: { SceneryInfo, SceneryTimetable, ActionButton },
})
export default class SceneryView extends Vue {
@Getter("getAllData") data!: StoreData;
timetableOnly: boolean = false;
setup() {
const route = useRoute();
const store = useStore();
activated() {
this.timetableOnly =
this.$route.query["timetable_only"] == "1" ? true : false;
}
get isComponentVisible() {
return this.$route.path === "/scenery";
}
get isDataLoaded() {
return this.data.dataConnectionStatus == DataStatus.Loaded;
}
get stationInfo(): Station | undefined {
if (!this.$route.query.station) return;
return this.data.stationList.find(
(station) =>
station.stationName ===
this.$route.query.station.toString().replaceAll("_", " ")
const data: ComputedRef<StoreData> = computed(
() => store.getters[GETTERS.allData]
);
}
}
const timetableOnly = computed(() =>
route.query["timetable_only"] == "1" ? true : false
);
const isComponentVisible = computed(() => route.path === "/scenery");
const isDataLoaded = computed(
() => data.value.dataConnectionStatus === DataStatus.Loaded
);
const stationInfo = computed(() => {
if (!route.query.station) return;
return data.value.stationList.find(
(station) =>
station.stationName ===
route.query.station?.toString().replaceAll("_", " ")
);
});
return {
data,
timetableOnly,
isComponentVisible,
isDataLoaded,
stationInfo,
};
},
});
</script>
<style lang="scss" scoped>
+82 -113
View File
@@ -1,14 +1,9 @@
<template>
<div class="stations-view">
<DonationModal
:modalHidden="modalHidden"
@toggleModal="toggleModal"
/>
<div class="wrapper">
<div class="body">
<div class="options-bar">
<action-button @click.native="() => toggleCardsState('filter')">
<action-button @click="() => toggleCardsState('filter')">
<img
class="button_icon"
:src="require('@/assets/icon-filter2.svg')"
@@ -42,7 +37,7 @@
<transition name="card-anim">
<FilterCard
v-if="filterCardOpen"
:showCard="filterCardOpen"
:exit="() => toggleCardsState('filter')"
@changeFilterValue="changeFilterValue"
@resetFilters="resetFilters"
@@ -52,10 +47,6 @@
</template>
<script lang="ts">
import { Vue, Component } from "vue-property-decorator";
import { Getter } from "vuex-class";
import Station from "@/scripts/interfaces/Station";
import StorageManager from "@/scripts/managers/storageManager";
@@ -68,128 +59,106 @@ import FilterCard from "@/components/StationsView/FilterCard.vue";
import ActionButton from "@/components/Global/ActionButton.vue";
import { StoreData } from "@/scripts/interfaces/StoreData";
import { DataStatus } from "@/scripts/enums/DataStatus";
import { computed, ComputedRef, defineComponent, reactive } from "vue";
import { useStore } from "@/store";
import { GETTERS } from "@/constants/storeConstants";
@Component({
export default defineComponent({
components: {
StationTable,
FilterCard,
ActionButton,
},
})
export default class StationsView extends Vue {
STORAGE_KEY: string = "options_saved";
STORAGE_MODAL: string = "modal";
data: () => ({
trainIcon: require("@/assets/icon-train.svg"),
timetableIcon: require("@/assets/icon-timetable.svg"),
dolarIcon: require("@/assets/icon-dolar.svg"),
filterCardOpen: false,
modalHidden: true,
STORAGE_KEY: "options_saved",
inputs: inputData,
}),
setup() {
const store = useStore();
const filterManager = reactive(new StationFilterManager());
const focusedStationName = "";
trainIcon: string = require("@/assets/icon-train.svg");
timetableIcon: string = require("@/assets/icon-timetable.svg");
dolarIcon: string = require("@/assets/icon-dolar.svg");
const data: ComputedRef<StoreData> = computed(
() => store.getters[GETTERS.allData]
);
filterManager: StationFilterManager = new StationFilterManager();
focusedStationName: string = "";
filterCardOpen: boolean = false;
modalHidden: boolean = true;
inputs = inputData;
@Getter("getStationList") stationList!: Station[];
@Getter("getAllData") data!: StoreData;
get dataStatusClass() {
if (this.data.dataConnectionStatus == DataStatus.Loading) return "loading";
if (this.data.dataConnectionStatus == DataStatus.Error) return "error";
return "success";
}
get timetableDataStatusClass() {
if (this.data.timetableDataStatus == DataStatus.Loading) return "loading";
if (this.data.timetableDataStatus == DataStatus.Error) return "error";
return "success";
}
mounted() {
this.initializeOptionsStorage();
// this.initializeModalStorage();
window.addEventListener("keydown", (e: KeyboardEvent) => {
if (e.keyCode == 27 && this.focusedStationName != "") {
this.focusedStationName = "";
}
const computedStations: ComputedRef<Station[]> = computed(() => {
return filterManager.getFilteredStationList(
store.getters[GETTERS.stationList]
);
});
}
initializeOptionsStorage() {
const getStatusClass = computed(() => {
if (data.value.dataConnectionStatus == DataStatus.Loading)
return "loading";
if (data.value.dataConnectionStatus == DataStatus.Error) return "error";
return "success";
});
const timetableDataStatusClass = computed(() => {
if (data.value.timetableDataStatus == DataStatus.Loading)
return "loading";
if (data.value.timetableDataStatus == DataStatus.Error) return "error";
return "success";
});
const focusedStationInfo = computed(() =>
computedStations.value.find(
(station) => station.stationName === focusedStationName
)
);
return {
data,
computedStations,
filterManager,
getStatusClass,
timetableDataStatusClass,
focusedStationName,
focusedStationInfo,
};
},
mounted() {
if (!StorageManager.isRegistered(this.STORAGE_KEY)) return;
this.inputs.options.forEach((option) => {
const value = StorageManager.getBooleanValue(option.name);
this.changeFilterValue({ name: option.name, value: value ? 0 : 1 });
option.value = value;
});
this.inputs.sliders.forEach((slider) => {
const value = StorageManager.getNumericValue(slider.name);
this.changeFilterValue({ name: slider.name, value });
slider.value = value;
});
}
initializeModalStorage() {
if (StorageManager.isRegistered(`${this.STORAGE_MODAL}_hidden`))
this.modalHidden = StorageManager.getBooleanValue(
`${this.STORAGE_MODAL}_hidden`
);
}
toggleModal() {
this.modalHidden = !this.modalHidden;
StorageManager.setBooleanValue(
`${this.STORAGE_MODAL}_hidden`,
this.modalHidden
);
}
toggleCardsState(name: string): void {
if (name == "filter") {
this.filterCardOpen = !this.filterCardOpen;
}
}
changeSorter(index: number) {
this.filterManager.changeSorter(index);
}
changeFilterValue(filter: { name: string; value: number }) {
this.filterManager.changeFilterValue(filter);
}
resetFilters() {
this.filterManager.resetFilters();
}
get computedStations() {
return this.filterManager.getFilteredStationList(this.stationList);
}
closeCard() {
this.focusedStationName = "";
}
setFocusedStation(name: string) {
this.focusedStationName = this.focusedStationName == name ? "" : name;
}
get focusedStationInfo() {
return this.computedStations.find(
(station) => station.stationName === this.focusedStationName
);
}
}
},
methods: {
toggleCardsState(name: string): void {
if (name == "filter") {
this.filterCardOpen = !this.filterCardOpen;
}
},
changeSorter(index: number) {
this.filterManager.changeSorter(index);
},
changeFilterValue(filter: { name: string; value: number }) {
this.filterManager.changeFilterValue(filter);
},
resetFilters() {
this.filterManager.resetFilters();
},
closeCard() {
this.focusedStationName = "";
},
setFocusedStation(name: string) {
this.focusedStationName = this.focusedStationName == name ? "" : name;
},
},
});
</script>
<style lang="scss" scoped>
@@ -202,7 +171,7 @@ export default class StationsView extends Vue {
transition: all $animDuration $animType;
}
&-enter,
&-enter-from,
&-leave-to {
transform: translate(-50%, -50%) scale(0.85);
opacity: 0;
+110 -78
View File
@@ -2,11 +2,14 @@
<section class="trains-view">
<div class="wrapper">
<div class="options-bar">
<TrainStats :trains="trains" :trainStatsOpen="trainStatsOpen" />
<TrainStats
:trains="trainList"
:trainStatsOpen="trainStatsOpen"
/>
<TrainOptions
:queryTrain="queryTrain"
@change-sorter="changeSorter"
@changeSorter="changeSorter"
@changeSearchedTrain="changeSearchedTrain"
@changeSearchedDriver="changeSearchedDriver"
/>
@@ -14,7 +17,6 @@
<TrainTable
:computedTrains="computedTrains"
:timetableDataStatus="timetableDataStatus"
:queryTrain="queryTrain"
/>
</div>
@@ -22,105 +24,135 @@
</template>
<script lang="ts">
import Vue from "vue";
import { Component, Prop } from "vue-property-decorator";
import { Getter } from "vuex-class";
import { computed, ComputedRef, defineComponent, ref } from "vue";
import { DataStatus } from "@/scripts/enums/DataStatus";
import Train from "@/scripts/interfaces/Train";
import TrainTable from "@/components/TrainsView/TrainTable.vue";
import TrainStats from "@/components/TrainsView/TrainStats.vue";
import TrainOptions from "@/components/TrainsView/TrainOptions.vue";
import ActionButton from "@/components/Global/ActionButton.vue";
import { DataStatus } from "@/scripts/enums/DataStatus";
@Component({
import { useStore } from "@/store";
import { GETTERS } from "@/constants/storeConstants";
const filteredTrainList = (
trainList: Train[],
searchedTrain: string,
searchedDriver: string,
sorterActive: { id: string; dir: number }
) => {
return trainList
.filter(
(train) =>
train.online &&
(searchedTrain.length > 0
? train.trainNo.toString().includes(searchedTrain)
: true) &&
(searchedDriver.length > 0
? train.driverName
.toLowerCase()
.includes(searchedDriver.toLowerCase())
: true)
)
.sort((a: Train, b: Train) => {
switch (sorterActive.id) {
case "mass":
if (a.mass > b.mass) return sorterActive.dir;
return -sorterActive.dir;
case "distance":
if (
(a.timetableData?.routeDistance || -1) >
(b.timetableData?.routeDistance || -1)
)
return sorterActive.dir;
return -sorterActive.dir;
case "speed":
if (a.speed > b.speed) return sorterActive.dir;
return -sorterActive.dir;
case "timetable":
if (a.trainNo > b.trainNo) return sorterActive.dir;
return -sorterActive.dir;
case "length":
if (a.length > b.length) return sorterActive.dir;
return -sorterActive.dir;
default:
break;
}
return 0;
});
};
export default defineComponent({
components: {
TrainTable,
TrainStats,
TrainOptions,
ActionButton,
},
})
export default class TrainsView extends Vue {
@Getter("getTrainList") trains!: Train[];
@Getter("getTimetableDataStatus") timetableDataStatus!: DataStatus;
// Passed in route as query parameters
@Prop() readonly queryTrain!: string;
props: ["queryTrain"],
statsIcon = require("@/assets/icon-stats.svg");
data: () => ({
statsIcon: require("@/assets/icon-stats.svg"),
trainStatsOpen: false,
}),
sorterActive: { id: string; dir: number } = { id: "distance", dir: -1 };
setup() {
const store = useStore();
trainStatsOpen: boolean = false;
const trainList: ComputedRef<Train[]> = computed(
() => store.getters[GETTERS.trainList]
);
searchedTrain: string = "";
searchedDriver: string = "";
const timetableDataStatus: ComputedRef<DataStatus> = computed(
() => store.getters[GETTERS.timetableDataStatus]
);
changeSearchedTrain(trainNo: string) {
this.searchedTrain = trainNo;
}
const sorterActive = ref({ id: "distance", dir: -1 });
const searchedDriver = ref("");
const searchedTrain = ref("");
changeSearchedDriver(name: string) {
this.searchedDriver = name;
}
const computedTrains: ComputedRef<Train[]> = computed(() => {
if (timetableDataStatus.value != DataStatus.Loaded) return [];
changeSorter(sorter: { id: string; dir: number }) {
this.sorterActive = sorter;
}
return filteredTrainList(
trainList.value,
searchedTrain.value,
searchedDriver.value,
sorterActive.value
);
});
get computedTrains() {
return this.timetableDataStatus != DataStatus.Loaded
? []
: this.trains
.filter(
(train) =>
train.online &&
(this.searchedTrain.length > 0
? train.trainNo.toString().includes(this.searchedTrain)
: true) &&
(this.searchedDriver.length > 0
? train.driverName
.toLowerCase()
.includes(this.searchedDriver.toLowerCase())
: true)
)
.sort((a, b) => {
switch (this.sorterActive.id) {
case "mass":
if (a.mass > b.mass) return this.sorterActive.dir;
return -this.sorterActive.dir;
return {
trainList,
computedTrains,
searchedTrain,
searchedDriver,
sorterActive,
};
},
case "distance":
if (
(a.timetableData?.routeDistance || -1) >
(b.timetableData?.routeDistance || -1)
)
return this.sorterActive.dir;
methods: {
changeSearchedTrain(trainNo: string) {
this.searchedTrain = trainNo;
},
return -this.sorterActive.dir;
changeSearchedDriver(name: string) {
this.searchedDriver = name;
},
case "speed":
if (a.speed > b.speed) return this.sorterActive.dir;
return -this.sorterActive.dir;
case "timetable":
if (a.trainNo > b.trainNo) return this.sorterActive.dir;
return -this.sorterActive.dir;
case "length":
if (a.length > b.length) return this.sorterActive.dir;
return -this.sorterActive.dir;
default:
break;
}
return 0;
});
}
}
changeSorter(sorter: { id: string; dir: number }) {
this.sorterActive = sorter;
},
},
});
</script>
<style lang="scss" scoped>
+14
View File
@@ -0,0 +1,14 @@
import { ComponentCustomProperties } from 'vue'
import { Store } from 'vuex'
declare module '@vue/runtime-core' {
// declare your own store states
interface State {
count: number
}
// provide typings for `this.$store`
interface ComponentCustomProperties {
$store: Store<State>
}
}
+1 -21
View File
@@ -1,23 +1,3 @@
// module.exports = {
// publicPath: process.env.NODE_ENV === "production" ? "/dist" : "/",
// };
module.exports = {
chainWebpack: config => {
config.resolve.alias.set('vue', '@vue/compat')
config.module
.rule('vue')
.use('vue-loader')
.tap(options => {
return {
...options,
compilerOptions: {
compatConfig: {
MODE: 2
}
}
}
})
}
}
// };