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" "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": { "@mrmlnc/readdir-enhanced": {
"version": "2.2.1", "version": "2.2.1",
"resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz",
@@ -1135,6 +1203,12 @@
"@types/node": "*" "@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": { "@types/express": {
"version": "4.17.11", "version": "4.17.11",
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.11.tgz", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.11.tgz",
@@ -1597,6 +1671,52 @@
"integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
"dev": true "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": { "semver": {
"version": "6.3.0", "version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
@@ -1611,6 +1731,41 @@
"requires": { "requires": {
"minipass": "^3.1.1" "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": { "@vue/component-compiler-utils": {
"version": "3.2.0", "version": "3.2.0",
"resolved": "https://registry.npmjs.org/@vue/component-compiler-utils/-/component-compiler-utils-3.2.0.tgz", "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": { "@vue/preload-webpack-plugin": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/@vue/preload-webpack-plugin/-/preload-webpack-plugin-1.1.2.tgz", "resolved": "https://registry.npmjs.org/@vue/preload-webpack-plugin/-/preload-webpack-plugin-1.1.2.tgz",
"integrity": "sha512-LIZMuJk38pk9U9Ur4YzHjlIyMuxPlACdBIHH9/nGYVTsaGKOSnSuELiE8vS9wa+dJpIYspYUOqk+L1Q4pgHQHQ==", "integrity": "sha512-LIZMuJk38pk9U9Ur4YzHjlIyMuxPlACdBIHH9/nGYVTsaGKOSnSuELiE8vS9wa+dJpIYspYUOqk+L1Q4pgHQHQ==",
"dev": true "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": { "@vue/web-component-wrapper": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/@vue/web-component-wrapper/-/web-component-wrapper-1.3.0.tgz", "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": { "cyclist": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz",
@@ -3891,12 +4225,6 @@
"assert-plus": "^1.0.0" "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": { "debug": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
@@ -4583,6 +4911,11 @@
"integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
"dev": true "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": { "esutils": {
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
@@ -5033,14 +5366,16 @@
"integrity": "sha1-jHyF2gcTtAYFVrTXHAF3XuEmnrk=", "integrity": "sha1-jHyF2gcTtAYFVrTXHAF3XuEmnrk=",
"requires": { "requires": {
"websocket-extensions": ">=0.1.1" "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==", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"dev": true "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": { "gensync": {
"version": "1.0.0-beta.1", "version": "1.0.0-beta.1",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz",
@@ -5787,6 +6131,12 @@
"safer-buffer": ">= 2.1.2 < 3" "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": { "icss-utils": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-4.1.1.tgz", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-4.1.1.tgz",
@@ -6496,6 +6846,12 @@
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true "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": { "lodash.defaultsdeep": {
"version": "4.6.1", "version": "4.6.1",
"resolved": "https://registry.npmjs.org/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.1.tgz", "resolved": "https://registry.npmjs.org/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.1.tgz",
@@ -6571,6 +6927,15 @@
"yallist": "^3.0.2" "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": { "make-dir": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
@@ -6918,6 +7283,12 @@
"thenify-all": "^1.0.0" "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": { "nanomatch": {
"version": "1.2.13", "version": "1.2.13",
"resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", "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": { "postcss-modules-extract-imports": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz", "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz",
@@ -9209,6 +9639,12 @@
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
"dev": true "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": { "source-map-resolve": {
"version": "0.5.3", "version": "0.5.3",
"resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz",
@@ -9246,6 +9682,12 @@
"integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=",
"dev": true "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": { "spdx-correct": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz",
@@ -9443,6 +9885,12 @@
"integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=",
"dev": true "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": { "string-width": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
@@ -9724,8 +10172,7 @@
"to-fast-properties": { "to-fast-properties": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
"integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4="
"dev": true
}, },
"to-object-path": { "to-object-path": {
"version": "0.3.0", "version": "0.3.0",
@@ -9947,9 +10394,9 @@
"dev": true "dev": true
}, },
"typescript": { "typescript": {
"version": "3.9.9", "version": "4.1.6",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.9.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.6.tgz",
"integrity": "sha512-kdMjTiekY+z/ubJCATUPlRDl39vXYiMV9iyeMuEuXZh2we6zz80uovNN2WlAxmmdE/Z/YQe+EbOEXB5RHEED3w==", "integrity": "sha512-pxnwLxeb/Z5SP80JDRzVjh58KsM6jZHRAOtTpS7sXLS4ogXNKC9ANxHHZqLLeVHZN35jCtI4JdmLLbLiC1kBow==",
"dev": true "dev": true
}, },
"uglify-js": { "uglify-js": {
@@ -10289,9 +10736,14 @@
"dev": true "dev": true
}, },
"vue": { "vue": {
"version": "2.6.12", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/vue/-/vue-2.6.12.tgz", "resolved": "https://registry.npmjs.org/vue/-/vue-3.1.2.tgz",
"integrity": "sha512-uhmLFETqPPNyuLLbsKz6ioJ4q7AZHzD8ZVFNATNyICSZouqP2Sz0rotWQC8UNBF6VGSCs5abnKJoStA6JbCbfg==" "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": { "vue-class-component": {
"version": "7.2.6", "version": "7.2.6",
@@ -10305,9 +10757,15 @@
"dev": true "dev": true
}, },
"vue-i18n": { "vue-i18n": {
"version": "8.24.4", "version": "9.1.6",
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-8.24.4.tgz", "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.1.6.tgz",
"integrity": "sha512-RZE94WUAGxEiBAANxQ0pptbRwDkNKNSXl3fnJslpFOxVMF6UkUtMDSuYGuW2blDrVgweIXVpethOVkYoNNT9xw==" "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": { "vue-loader": {
"version": "15.9.7", "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": { "vue-router": {
"version": "3.5.1", "version": "4.0.10",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.5.1.tgz", "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.0.10.tgz",
"integrity": "sha512-RRQNLT8Mzr8z7eL4p7BtKvRaTSGdCbTy2+Mm5HTJvLGYSSeG9gDzNasJPP/yOYKLy+/cLG/ftrqq5fvkFwBJEw==" "integrity": "sha512-YbPf6QnZpyyWfnk7CUt2Bme+vo7TLfg1nGZNkvYqKYh4vLaFw6Gn8bPGdmt5m4qrGnKoXLqc4htAsd3dIukICA==",
"requires": {
"@vue/devtools-api": "^6.0.0-beta.14"
}
}, },
"vue-style-loader": { "vue-style-loader": {
"version": "4.1.3", "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": { "vue-template-es2015-compiler": {
"version": "1.9.1", "version": "1.9.1",
"resolved": "https://registry.npmjs.org/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz", "resolved": "https://registry.npmjs.org/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz",
@@ -10459,9 +10821,12 @@
"dev": true "dev": true
}, },
"vuex": { "vuex": {
"version": "3.6.2", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/vuex/-/vuex-3.6.2.tgz", "resolved": "https://registry.npmjs.org/vuex/-/vuex-4.0.2.tgz",
"integrity": "sha512-ETW44IqCgBpVomy520DT5jf8n0zoCac+sxWnn+hMe/CzaSejb/eVw2YToiXYX+Ex/AuHHia28vWTq4goAexFbw==" "integrity": "sha512-M6r8uxELjZIK8kTKDGgZTYX/ahzblnzC4isU1tpmEuOIIKmV+TRdc+H4s8ds2NuZ7wpUTdGRzJRtoj+lI+pc0Q==",
"requires": {
"@vue/devtools-api": "^6.0.0-beta.11"
}
}, },
"vuex-class": { "vuex-class": {
"version": "0.3.2", "version": "0.3.2",
+2 -4
View File
@@ -12,10 +12,8 @@
"dotenv": "^8.6.0", "dotenv": "^8.6.0",
"firestore": "^1.1.6", "firestore": "^1.1.6",
"howler": "^2.2.1", "howler": "^2.2.1",
"vue": "^2.6.12", "vue": "^3.1.2",
"vue-class-component": "^7.2.6", "vue-class-component": "^7.2.6",
"vue-property-decorator": "^8.5.1",
"@vue/compat": "^3.1.0",
"vue-i18n": "^9.1.6", "vue-i18n": "^9.1.6",
"vue-router": "^4.0.0-0", "vue-router": "^4.0.0-0",
"vuex": "^4.0.0-0" "vuex": "^4.0.0-0"
@@ -29,7 +27,7 @@
"axios": "^0.21.1", "axios": "^0.21.1",
"sass": "^1.32.13", "sass": "^1.32.13",
"sass-loader": "^8.0.2", "sass-loader": "^8.0.2",
"typescript": "^3.9.9", "typescript": "~4.1.5",
"@vue/compiler-sfc": "^3.1.0", "@vue/compiler-sfc": "^3.1.0",
"vuex-class": "^0.3.2", "vuex-class": "^0.3.2",
"vuex-module-decorators": "^0.17.0" "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;
}
+78 -244
View File
@@ -1,10 +1,10 @@
git <template> <template>
<div class="app"> <div class="app">
<UpdateModal <!-- <UpdateModal
:currentVersion="VERSION" :currentVersion="VERSION"
@toggleUpdateModal="toggleUpdateModal" @toggleUpdateModal="toggleUpdateModal"
v-if="updateModalVisible" v-if="updateModalVisible"
/> /> -->
<div class="app_container"> <div class="app_container">
<header class="app_header"> <header class="app_header">
@@ -83,14 +83,18 @@ git <template>
</header> </header>
<main class="app_main"> <main class="app_main">
<transition
name="view-anim" <router-view v-slot="{ Component }">
mode="out-in" <transition
> name="view-anim"
<keep-alive> mode="out-in"
<router-view /> >
</keep-alive> <keep-alive>
</transition> <component :is="Component" />
</keep-alive>
</transition>
</router-view>
</main> </main>
<footer class="app_footer"> <footer class="app_footer">
@@ -109,81 +113,49 @@ git <template>
</template> </template>
<script lang="ts"> <script lang="ts">
import { Vue, Component } from "vue-property-decorator"; // import UpdateModal from "@/components/Global/UpdateModal.vue";
import { Action, Getter } from "vuex-class";
import UpdateModal from "@/components/Global/UpdateModal.vue";
import Clock from "@/components/App/Clock.vue"; import Clock from "@/components/App/Clock.vue";
import StorageManager from "@/scripts/managers/storageManager"; import StorageManager from "@/scripts/managers/storageManager";
import { computed, ComputedRef, defineComponent } from "vue";
import { GETTERS } from "./constants/storeConstants";
import { StoreData } from "./scripts/interfaces/StoreData"; import { StoreData } from "./scripts/interfaces/StoreData";
import { useStore } from "./store";
// import { StoreData } from "./scripts/interfaces/StoreData";
@Component({ export default defineComponent({
components: { Clock, UpdateModal }, components: {
}) Clock,
export default class App extends Vue { },
@Action("synchronizeData") synchronizeData;
@Getter("getAllData") data!: StoreData;
private VERSION = "1.4.7"; setup() {
const store = useStore();
store.dispatch("synchronizeData");
hasReleaseNotes = false; const data: ComputedRef<StoreData> = computed(
updateModalVisible = false; () => store.getters[GETTERS.allData]
);
currentLang = "pl"; return {
data,
};
},
iconEN = require("@/assets/icon-en.jpg"); data: () => ({
iconPL = require("@/assets/icon-pl.svg"); VERSION: "1.4.7",
updateModalVisible: false,
hasReleaseNotes: false,
currentLang: "pl",
toggleUpdateModal() { iconEN: require("@/assets/icon-en.jpg"),
this.updateModalVisible = !this.updateModalVisible; iconPL: require("@/assets/icon-pl.svg"),
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() { created() {
this.loadLang(); this.loadLang();
this.synchronizeData(); },
}
mounted() { 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) { if (StorageManager.getStringValue("version") != this.VERSION) {
StorageManager.setStringValue("version", this.VERSION); StorageManager.setStringValue("version", this.VERSION);
@@ -194,186 +166,48 @@ export default class App extends Vue {
this.updateModalVisible = this.updateModalVisible =
this.hasReleaseNotes && this.hasReleaseNotes &&
!StorageManager.getBooleanValue("version_notes_read"); !StorageManager.getBooleanValue("version_notes_read");
} },
detectIEVersion() { methods: {
var rv = -1; toggleUpdateModal() {
if (navigator.appName == "Microsoft Internet Explorer") { this.updateModalVisible = !this.updateModalVisible;
var ua = navigator.userAgent; StorageManager.setBooleanValue("version_notes_read", true);
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; changeLang(lang: string) {
} this.$i18n.locale = lang;
} this.currentLang = lang;
</script>
<style lang="scss"> StorageManager.setStringValue("lang", lang);
@import "./styles/responsive.scss"; },
@import "./styles/variables.scss";
@import "./styles/global.scss";
@import "./styles/scenery_status.scss";
:root { loadLang() {
--clr-primary: #ffc014; const storageLang = StorageManager.getStringValue("lang");
--clr-secondary: #2f2f2f;
--clr-bg: #333; if (storageLang) {
this.changeLang(storageLang);
--clr-accent: #1085b3; return;
--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; if (!window.navigator.language) {
} this.changeLang("pl");
} return;
}
&_info { switch (window.navigator.language) {
display: flex; case "pl-PL":
justify-content: space-between; this.changeLang("pl");
break;
case "en-EN":
default:
this.changeLang("en");
break;
}
font-size: 1.25em; return;
},
},
});
</script>
margin: 0 0.3em; <style lang="scss" src="./App.scss"></style>
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>
+13 -12
View File
@@ -1,27 +1,28 @@
<template> <template>
<div class="clock">{{ formattedDate }}</div> <div class="clock">{{ computedDate }}</div>
</template> </template>
<script lang="ts"> <script lang="ts">
import Vue from "vue"; import { computed, defineComponent, ref } from "vue";
export default Vue.extend({ export default defineComponent({
name: "clock", name: "clock",
data: () => ({ data: () => ({
timestamp: Date.now(), timestamp: Date.now(),
}), }),
computed: { setup() {
formattedDate() { let timestamp = ref(Date.now());
return new Date(this.timestamp).toLocaleString("pl-PL", {
const computedDate = computed(() => new Date(timestamp.value).toLocaleString("pl-PL", {
hour: "2-digit", hour: "2-digit",
minute: "2-digit", minute: "2-digit",
second: "2-digit", second: "2-digit",
}); }));
},
}, setInterval(() => (timestamp.value = Date.now()), 1000);
mounted() {
setInterval(() => (this.timestamp = Date.now()), 1000); return { computedDate }
}, }
}); });
</script> </script>
+4 -5
View File
@@ -3,12 +3,11 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Vue, Component, Prop } from "vue-property-decorator"; import { defineComponent } from "@vue/runtime-core";
@Component export default defineComponent({
export default class Loading extends Vue { props: ["message"],
@Prop() readonly message!: string; });
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
+16 -5
View File
@@ -1,14 +1,23 @@
<template> <template>
<section class="updates card" v-if="cardOpen"> <section
class="updates card"
v-if="cardOpen"
>
<h2>Ostatnie aktualizacje w Stacjowniku</h2> <h2>Ostatnie aktualizacje w Stacjowniku</h2>
<p>Tutaj będą pojawiać się informacje o kolejnych nowościach na stronie :)</p> <p>Tutaj będą pojawiać się informacje o kolejnych nowościach na stronie :)</p>
<ul> <ul>
<li v-for="(update, i) in updates" :key="i"> <li
v-for="(update, i) in updates"
:key="i"
>
<div>{{update.date}}</div> <div>{{update.date}}</div>
<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> </div>
</li> </li>
</ul> </ul>
@@ -16,7 +25,9 @@
</template> </template>
<script> <script>
export default { import { defineComponent } from "@vue/runtime-core";
export default defineComponent({
data() { data() {
return { return {
updates: { updates: {
@@ -29,7 +40,7 @@ export default {
}, },
}; };
}, },
}; });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
+3 -7
View File
@@ -5,13 +5,12 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Vue } from "vue-property-decorator"; import { defineComponent } from "vue";
@Component export default defineComponent({});
export default class ActionButton extends Vue {}
</script> </script>
<style lang="scss" scoped> <style lang="scss">
@import "../../styles/variables"; @import "../../styles/variables";
@import "../../styles/responsive"; @import "../../styles/responsive";
@@ -40,15 +39,12 @@ export default class ActionButton extends Vue {}
img { img {
width: 1.25em; width: 1.25em;
vertical-align: middle; vertical-align: middle;
margin-right: 0.35em; margin-right: 0.35em;
} }
p { p {
font-size: 1em; font-size: 1em;
overflow: hidden; overflow: hidden;
transition: max-width 0.35s ease-in-out;
} }
&.open { &.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> <template>
<div class="select-box"> <div class="select-box">
<div class="select-box_content"> <div class="select-box_content">
<button class="selected" @click="toggleBox"> <button
{{ selectedItemComp ? selectedItemComp.value : "" }} class="selected"
@click="toggleBox"
>
{{ computedSelectedItem.value }}
</button> </button>
<div class="options" v-if="boxVisible"> <div
<div class="option" v-for="item in itemList" :key="item.id"> class="options"
v-if="boxVisible"
>
<div
class="option"
v-for="item in itemList"
:key="item.id"
>
<label :for="item.id"> <label :for="item.id">
<input <input
type="button" type="button"
@@ -15,7 +25,7 @@
@click="selectOption(item)" @click="selectOption(item)"
/> />
<span :style="selectedItemComp.id == item.id ? 'color: gold;' : ''"> <span :style="computedSelectedItem.id == item.id ? 'color: gold;' : ''">
{{ item.value }} {{ item.value }}
</span> </span>
</label> </label>
@@ -24,51 +34,79 @@
</div> </div>
<div class="arrow"> <div class="arrow">
<img :src="boxVisible ? ascIcon : descIcon" alt="arrow-icon" /> <img
:src="boxVisible ? ascIcon : descIcon"
alt="arrow-icon"
/>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Vue, Prop, Emit } from "vue-property-decorator"; import {
computed,
defineComponent,
defineEmit,
Ref,
ref,
} from "@vue/runtime-core";
@Component interface Item {
export default class SelectBox extends Vue { id: string | number;
@Prop({ required: true }) itemList!: { id: string | number; value: string }[]; 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();
}
} }
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> </script>
<style lang="scss" scoped> <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" :href="stationInfo.stationURL"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
>{{ stationInfo.stationName }}</a >{{ stationInfo.stationName }}</a>
>
<span v-else>{{ stationInfo.stationName }}</span> <span v-else>{{ stationInfo.stationName }}</span>
</div> </div>
<div class="scenery-hash" v-if="stationInfo.stationHash"> <div
class="scenery-hash"
v-if="stationInfo.stationHash"
>
#{{ stationInfo.stationHash }} #{{ stationInfo.stationHash }}
</div> </div>
</div> </div>
@@ -23,24 +25,36 @@
:class="!stationInfo.stationHash ? 'no-stats' : ''" :class="!stationInfo.stationHash ? 'no-stats' : ''"
> >
<span class="likes"> <span class="likes">
<img :src="likeIcon" alt="icon-like" /> <img
:src="likeIcon"
alt="icon-like"
/>
<span>{{ stationInfo.dispatcherRate }}</span> <span>{{ stationInfo.dispatcherRate }}</span>
</span> </span>
<span class="users"> <span class="users">
<img :src="userIcon" alt="icon-user" /> <img
:src="userIcon"
alt="icon-user"
/>
<span>{{ stationInfo.currentUsers }}</span> <span>{{ stationInfo.currentUsers }}</span>
/ /
<span>{{ stationInfo.maxUsers }}</span> <span>{{ stationInfo.maxUsers }}</span>
</span> </span>
<span class="spawns"> <span class="spawns">
<img :src="spawnIcon" alt="icon-spawn" /> <img
:src="spawnIcon"
alt="icon-spawn"
/>
<span>{{ stationInfo.spawns.length }}</span> <span>{{ stationInfo.spawns.length }}</span>
</span> </span>
<span class="schedules"> <span class="schedules">
<img :src="timetableIcon" alt="icon-timetable" /> <img
:src="timetableIcon"
alt="icon-timetable"
/>
<span v-if="stationInfo.scheduledTrains"> <span v-if="stationInfo.scheduledTrains">
<span style="color: #eee">{{ <span style="color: #eee">{{
stationInfo.scheduledTrains.length stationInfo.scheduledTrains.length
@@ -111,7 +125,10 @@
</div> </div>
<div class="info-dispatcher"> <div class="info-dispatcher">
<div class="dispatcher" v-if="stationInfo.stationHash"> <div
class="dispatcher"
v-if="stationInfo.stationHash"
>
<span <span
class="dispatcher_level" class="dispatcher_level"
:style=" :style="
@@ -129,7 +146,10 @@
<span class="dispatcher_name">{{ stationInfo.dispatcherName }}</span> <span class="dispatcher_name">{{ stationInfo.dispatcherName }}</span>
</div> </div>
<span class="status-badge" :class="stationInfo.statusID"> <span
class="status-badge"
:class="stationInfo.statusID"
>
{{ $t(`status.${stationInfo.statusID}`) }} {{ $t(`status.${stationInfo.statusID}`) }}
{{ {{
stationInfo.statusID == "online" ? stationInfo.statusTimeString : "" stationInfo.statusID == "online" ? stationInfo.statusTimeString : ""
@@ -141,7 +161,10 @@
<div class="user-list"> <div class="user-list">
<h3 class="user-header"> <h3 class="user-header">
{{ $t("scenery.users") }} {{ $t("scenery.users") }}
<img :src="userIcon" alt="icon-user" /> <img
:src="userIcon"
alt="icon-user"
/>
</h3> </h3>
<div <div
@@ -166,7 +189,10 @@
<div class="spawn-list"> <div class="spawn-list">
<h3 class="spawn-header"> <h3 class="spawn-header">
{{ $t("scenery.spawns") }} {{ $t("scenery.spawns") }}
<img :src="spawnIcon" alt="icon-spawn" /> <img
:src="spawnIcon"
alt="icon-spawn"
/>
</h3> </h3>
<span <span
@@ -181,7 +207,7 @@
<span <span
class="spawn none" class="spawn none"
v-if="!stationInfo.spawns || stationInfo.spawns.length == 0" v-if="!stationInfo.spawns || stationInfo.spawns.length == 0"
>{{ $t("scenery.no-spawns") }} >{{ $t("scenery.no-spawns") }}
</span> </span>
</div> </div>
</div> </div>
@@ -190,44 +216,56 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Prop } from "vue-property-decorator";
import Station from "@/scripts/interfaces/Station"; import Station from "@/scripts/interfaces/Station";
import styleMixin from "@/mixins/styleMixin"; import styleMixin from "@/mixins/styleMixin";
import { computed, defineComponent } from "@vue/runtime-core";
@Component export default defineComponent({
export default class SceneryInfo extends styleMixin { props: {
@Prop() readonly stationInfo!: Station; stationInfo: {
@Prop() readonly timetableOnly!: boolean; type: Object as () => Station,
},
likeIcon: string = require("@/assets/icon-like.svg"); timetableOnly: Boolean,
spawnIcon: string = require("@/assets/icon-spawn.svg"); },
timetableIcon: string = require("@/assets/icon-timetable.svg");
userIcon: string = require("@/assets/icon-user.svg");
get computedStationTrains() { mixins: [styleMixin],
if (!this.stationInfo) return null;
return this.stationInfo.stationTrains.map((stationTrain) => { data: () => ({
const scheduledData = this.stationInfo?.scheduledTrains.find( likeIcon: require("@/assets/icon-like.svg"),
(scheduledTrain) => scheduledTrain.trainNo === stationTrain.trainNo spawnIcon: require("@/assets/icon-spawn.svg"),
); timetableIcon: require("@/assets/icon-timetable.svg"),
userIcon: require("@/assets/icon-user.svg"),
}),
return { setup(props) {
...stationTrain, const computedStationTrains = computed(() => {
stopStatus: scheduledData?.stopStatus || "no-timetable", 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) { return { computedStationTrains };
this.$router.push({ },
name: "TrainsView",
params: { queryTrain: trainNo.toString() }, methods: {
}); navigateToTrain(trainNo: number) {
} this.$router.push({
} name: "TrainsView",
params: { queryTrain: trainNo.toString() },
});
},
},
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
+100 -72
View File
@@ -25,11 +25,14 @@
value: cp.checkpointName, value: cp.checkpointName,
})) }))
" "
@selected="chooseOption" @selected="selectCheckpoint"
></select-box> ></select-box>
</div> </div>
<span class="timetable-item loading" v-if="dataStatus == 0">{{ <span
class="timetable-item loading"
v-if="dataStatus == 0"
>{{
$t("app.loading") $t("app.loading")
}}</span> }}</span>
@@ -48,14 +51,12 @@
> >
<span class="timetable-general"> <span class="timetable-general">
<span class="general-info"> <span class="general-info">
<router-link <router-link :to="{
:to="{
name: 'TrainsView', name: 'TrainsView',
params: { params: {
queryTrain: scheduledTrain.trainNo.toString(), queryTrain: scheduledTrain.trainNo.toString(),
}, },
}" }">
>
<span> <span>
<strong>{{ scheduledTrain.category }}</strong> <strong>{{ scheduledTrain.category }}</strong>
{{ scheduledTrain.trainNo }} {{ scheduledTrain.trainNo }}
@@ -68,21 +69,17 @@
'https://td2.info.pl/profile/?u=' + scheduledTrain.driverId 'https://td2.info.pl/profile/?u=' + scheduledTrain.driverId
" "
target="_blank" target="_blank"
>{{ scheduledTrain.driverName }}</a >{{ scheduledTrain.driverName }}</a>
>
</span> </span>
<div class="info-route"> <div class="info-route">
<strong <strong>{{ scheduledTrain.beginsAt }} -
>{{ scheduledTrain.beginsAt }} - {{ scheduledTrain.terminatesAt }}</strong>
{{ scheduledTrain.terminatesAt }}</strong
>
</div> </div>
</span> </span>
<span class="general-status"> <span class="general-status">
<span :class="scheduledTrain.stopStatus" <span :class="scheduledTrain.stopStatus">{{ $t(`timetables.${scheduledTrain.stopStatus}`) }}
>{{ $t(`timetables.${scheduledTrain.stopStatus}`) }}
</span> </span>
</span> </span>
</span> </span>
@@ -95,7 +92,10 @@
v-html="$t('timetables.begins')" v-html="$t('timetables.begins')"
> >
</span> </span>
<span class="arrival-time" v-else> <span
class="arrival-time"
v-else
>
{{ scheduledTrain.stopInfo.arrivalTimeString }} ({{ {{ scheduledTrain.stopInfo.arrivalTimeString }} ({{
scheduledTrain.stopInfo.arrivalDelay scheduledTrain.stopInfo.arrivalDelay
}}) }})
@@ -103,7 +103,10 @@
</span> </span>
<span class="schedule-stop"> <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.stopTime }}
{{ scheduledTrain.stopInfo.stopType }} {{ scheduledTrain.stopInfo.stopType }}
</span> </span>
@@ -116,7 +119,10 @@
v-html="$t('timetables.terminates')" v-html="$t('timetables.terminates')"
> >
</span> </span>
<span class="departure-time" v-else> <span
class="departure-time"
v-else
>
{{ scheduledTrain.stopInfo.departureTimeString }} ({{ {{ scheduledTrain.stopInfo.departureTimeString }} ({{
scheduledTrain.stopInfo.departureDelay scheduledTrain.stopInfo.departureDelay
}}) }})
@@ -129,74 +135,96 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Vue, Prop } from "vue-property-decorator";
import Station from "@/scripts/interfaces/Station"; import Station from "@/scripts/interfaces/Station";
import ScheduledTrain from "@/scripts/interfaces/ScheduledTrain";
import SelectBox from "../Global/SelectBox.vue"; import SelectBox from "../Global/SelectBox.vue";
import { computed, defineComponent, ref } from "@vue/runtime-core";
import { useRoute } from "vue-router";
@Component({ components: { SelectBox } }) export default defineComponent({
export default class SceneryTimetable extends Vue { components: { SelectBox },
@Prop() readonly stationInfo!: Station;
@Prop() readonly timetableOnly!: boolean;
@Prop() readonly dataStatus!: number;
viewIcon: string = require("@/assets/icon-view.svg"); props: {
stationInfo: {
type: Object as () => Station,
},
timetableOnly: {
type: Boolean,
},
dataStatus: {
type: Number,
},
},
listOpen: boolean = false; data: () => ({
selectedOption: string = ""; viewIcon: require("@/assets/icon-view.svg"),
listOpen: false,
}),
loadSelectedOption() { setup(props) {
if (!this.stationInfo) return; const route = useRoute();
if (!this.stationInfo.checkpoints) return; const currentURL = computed(() => `${location.origin}${route.fullPath}`);
if (this.selectedOption != "") return;
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() { mounted() {
this.loadSelectedOption(); this.loadSelectedOption();
} },
activated() { activated() {
this.loadSelectedOption(); 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> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
+64 -59
View File
@@ -1,5 +1,8 @@
<template> <template>
<section class="card"> <section
class="card"
v-if="showCard"
>
<div <div
class="card-exit" class="card-exit"
@click="exit" @click="exit"
@@ -79,13 +82,13 @@
<div class="card-actions flex"> <div class="card-actions flex">
<action-button <action-button
class="outlined" class="outlined"
@click.native="resetFilters" @click="resetFilters"
> >
{{ $t("filters.reset") }} {{ $t("filters.reset") }}
</action-button> </action-button>
<action-button <action-button
class="outlined" class="outlined"
@click.native="exit" @click="exit"
>{{ >{{
$t("filters.close") $t("filters.close")
}}</action-button> }}</action-button>
@@ -94,83 +97,85 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Vue, Component, Prop } from "vue-property-decorator";
import inputData from "@/data/options.json"; import inputData from "@/data/options.json";
import StorageManager from "@/scripts/managers/storageManager"; import StorageManager from "@/scripts/managers/storageManager";
import { defineComponent } from "@vue/runtime-core";
import ActionButton from "../Global/ActionButton.vue"; import ActionButton from "../Global/ActionButton.vue";
@Component({ components: { ActionButton } }) export default defineComponent({
export default class FilterCard extends Vue { components: { ActionButton },
inputs = { ...inputData }; props: ["showCard", "exit"],
saveOptions: boolean = false;
STORAGE_KEY: string = "options_saved";
@Prop() exit!: () => void; data: () => ({
inputs: { ...inputData },
saveOptions: false,
STORAGE_KEY: "options_saved",
}),
mounted() { mounted() {
this.saveOptions = StorageManager.isRegistered(this.STORAGE_KEY); this.saveOptions = StorageManager.isRegistered(this.STORAGE_KEY);
} },
handleChange(e: Event): void { methods: {
const target = <HTMLInputElement>e.target; handleChange(e: Event) {
const target = e.target as HTMLInputElement;
this.$emit("changeFilterValue", { this.$emit("changeFilterValue", {
name: target.name, name: target.name,
value: !target.checked, value: !target.checked,
}); });
if (this.saveOptions)
StorageManager.setBooleanValue(target.name, target.checked);
},
if (this.saveOptions) handleInput(e: Event) {
StorageManager.setBooleanValue(target.name, target.checked); const target = e.target as HTMLInputElement;
}
handleInput(e: Event): void { this.$emit("changeFilterValue", {
const target = <HTMLInputElement>e.target; name: target.name,
this.$emit("changeFilterValue", { value: target.value,
name: target.name, });
value: target.value, if (this.saveOptions)
}); StorageManager.setStringValue(target.name, target.value);
},
if (this.saveOptions) saveFilters() {
StorageManager.setStringValue(target.name, target.value); if (!this.saveOptions) {
} StorageManager.unregisterStorage(this.STORAGE_KEY);
return;
}
saveFilters(): void { StorageManager.registerStorage(this.STORAGE_KEY);
if (!this.saveOptions) {
StorageManager.unregisterStorage(this.STORAGE_KEY);
return;
}
StorageManager.registerStorage(this.STORAGE_KEY); this.inputs.options.forEach((option) =>
StorageManager.setBooleanValue(option.name, option.value)
);
this.inputs.options.forEach((option) => this.inputs.sliders.forEach((slider) =>
StorageManager.setBooleanValue(option.name, option.value) StorageManager.setNumericValue(slider.name, slider.value)
); );
},
this.inputs.sliders.forEach((slider) => resetFilters() {
StorageManager.setNumericValue(slider.name, slider.value) this.inputs.options.forEach((option) => {
); option.value = option.defaultValue;
} StorageManager.setBooleanValue(option.name, option.value);
});
resetFilters(): void { this.inputs.sliders.forEach((slider) => {
this.inputs.options.forEach((option) => { slider.value = slider.defaultValue;
option.value = option.defaultValue; StorageManager.setNumericValue(slider.name, slider.value);
StorageManager.setBooleanValue(option.name, option.value); });
});
this.inputs.sliders.forEach((slider) => { this.$emit("resetFilters");
slider.value = slider.defaultValue; },
StorageManager.setNumericValue(slider.name, slider.value);
});
this.$emit("resetFilters"); closeCard() {
} this.exit();
},
closeCard(): void { },
this.exit(); });
}
}
</script> </script>
<style lang="scss" scoped> <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}`) $t('desc.signals-type') + $t(`signals.${station.signalType}`)
" "
/> />
<img <img
v-if="station.SBL && station.SBL !== ''" v-if="station.SBL && station.SBL !== ''"
:src="require(`@/assets/icon-SBL.svg`)" :src="require(`@/assets/icon-SBL.svg`)"
@@ -236,80 +237,87 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Prop } from "vue-property-decorator";
import Station from "@/scripts/interfaces/Station";
import styleMixin from "@/mixins/styleMixin"; 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 { 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({ export default defineComponent({
components: { Options }, props: {
}) stations: {
export default class StationTable extends styleMixin { type: Array as () => Station[],
@Prop() readonly stations!: Station[]; required: true,
@Prop() readonly sorterActive!: number; },
@Prop() readonly setFocusedStation!: () => void; sorterActive: {
@Prop() readonly changeSorter!: () => void; type: Object as () => { id: string; dir: number },
required: true,
},
@Getter("getAllData") storeAPIData!: StoreData; setFocusedStation: Function,
changeSorter: Function,
},
likeIcon: string = require("@/assets/icon-like.svg"); mixins: [styleMixin],
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");
ascIcon: string = require("@/assets/icon-arrow-asc.svg"); data: () => ({
descIcon: string = require("@/assets/icon-arrow-desc.svg"); 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 = [ ascIcon: require("@/assets/icon-arrow-asc.svg"),
"station", descIcon: require("@/assets/icon-arrow-desc.svg"),
"min-lvl",
"status",
"dispatcher",
"dispatcher-lvl",
"routes",
"general",
];
headIconsIds = ["user", "spawn", "timetable"]; headIds: [
"station",
"min-lvl",
"status",
"dispatcher",
"dispatcher-lvl",
"routes",
"general",
],
headTitles: string[][] = [ headIconsIds: ["user", "spawn", "timetable"],
["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"],
];
setScenery(name: string) { setup() {
const station = this.stations.find( const store = useStore();
(station) => station.stationName === name
const dataConnectionStatus: ComputedRef<DataStatus> = computed(
() => store.getters[GETTERS.dataStatus]
); );
if (!station) return; const isDataLoaded = () =>
computed(() => {
dataConnectionStatus.value == DataStatus.Loaded;
});
this.$router.push({ return {
name: "SceneryView", isDataLoaded,
query: { station: station.stationName.replaceAll(" ", "_") }, };
}); },
}
get isDataLoaded() { methods: {
return this.storeAPIData.dataConnectionStatus == DataStatus.Loaded; 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> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@@ -358,6 +366,7 @@ table {
padding: 0.5em; padding: 0.5em;
background-color: $primaryCol; background-color: $primaryCol;
white-space: pre-wrap;
cursor: pointer; cursor: pointer;
user-select: none; user-select: none;
@@ -442,8 +451,8 @@ td.station {
} }
.track { .track {
margin: 0 0.3em; margin: 0 0.35em;
padding: 0.35em 0.1em; padding: 0.35em;
font-size: 1.05em; font-size: 1.05em;
white-space: pre-wrap; 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> </template>
<script lang="ts"> <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"; import SelectBox from "../Global/SelectBox.vue";
@Component({ components: { SelectBox } }) export default defineComponent({
export default class TrainOptions extends Vue { components: { SelectBox },
// Passed as component parameters props: ["queryTrain"],
@Prop() readonly queryTrain!: string; emits: ["changeSearchedTrain", "changeSearchedDriver", "changeSorter"],
exitIcon = require("@/assets/icon-exit.svg"); data: () => ({
exitIcon: require("@/assets/icon-exit.svg"),
searchedTrain: "",
searchedDriver: "",
}),
searchedTrain = ""; setup() {
searchedDriver = ""; const { t } = useI18n();
sorterOptions: { id: string; value: string }[] = [ const sorterOptions = [
{ {
id: "mass", id: "mass",
value: "masa", value: "masa",
}, },
{ {
id: "speed", id: "speed",
value: "prędkość", value: "prędkość",
}, },
{ {
id: "length", id: "length",
value: "długość", value: "długość",
}, },
{ {
id: "distance", id: "distance",
value: "kilometraż", value: "kilometraż",
}, },
{ {
id: "timetable", id: "timetable",
value: "numer pociągu", value: "numer pociągu",
}, },
]; ];
get translatedSorterOptions() { const translatedSorterOptions = computed(() =>
return this.sorterOptions.map((option) => ({ sorterOptions.map(({ id }) => ({
id: option.id, id,
value: this.$t(`trains.option-${option.id}`), value: t(`trains.option-${id}`),
})); }))
} );
return {
translatedSorterOptions,
};
},
mounted() { mounted() {
if (this.queryTrain) { if (this.queryTrain) {
this.searchedTrain = this.queryTrain; this.searchedTrain = this.queryTrain;
this.searchedDriver = ""; this.searchedDriver = "";
} }
} },
/* Emitters to TrainsView managing variables */ methods: {
chooseTrain(train: string) {
this.$emit("changeSearchedTrain", train);
},
@Emit("changeSearchedTrain") chooseDriver(driverName: string) {
chooseTrain(train: string) { this.$emit("changeSearchedDriver", driverName);
return train; },
}
@Emit("changeSearchedDriver") changeSorter(item: { id: string | number; value: string }) {
chooseDriver(driverName: string) { this.$emit("changeSorter", { id: item.id, dir: -1 });
return driverName; },
} },
@Emit() watch: {
changeSorter(item: { id: string | number; value: string }) { searchedTrain(value: string) {
return { id: item.id, dir: -1 }; this.chooseTrain(value);
} },
/* Watchers for search boxes */ searchedDriver(value: string) {
this.chooseDriver(value);
},
@Watch("searchedTrain") queryTrain(train: string) {
watchSearchedTrain(train: string) { if (!train) return;
this.chooseTrain(train); if (train == "") return;
}
@Watch("searchedDriver") this.searchedTrain = train;
watchSearchedDriver(driver: string) { this.searchedDriver = "";
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 = "";
}
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
+28 -30
View File
@@ -1,5 +1,8 @@
<template> <template>
<div class="train-schedule" @click="click"> <div
class="train-schedule"
@click="this.$emit('click')"
>
<div class="schedule-wrapper"> <div class="schedule-wrapper">
<ul class="stop_list"> <ul class="stop_list">
<li <li
@@ -19,11 +22,17 @@
<div class="stop-bar"></div> <div class="stop-bar"></div>
<span class="distance" v-if="stop.stopDistance"> <span
class="distance"
v-if="stop.stopDistance"
>
{{ Math.floor(stop.stopDistance) }} {{ Math.floor(stop.stopDistance) }}
</span> </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="stop-date">
<span <span
class="date arrival" class="date arrival"
@@ -79,9 +88,7 @@
<div class="progress-bar"></div> <div class="progress-bar"></div>
<span v-if="i < followingStops.length - 1"> <span v-if="i < followingStops.length - 1">
<span <span v-if="stop.departureLine == followingStops[i + 1].arrivalLine">
v-if="stop.departureLine == followingStops[i + 1].arrivalLine"
>
{{ stop.departureLine }} {{ stop.departureLine }}
</span> </span>
@@ -98,32 +105,23 @@
</template> </template>
<script lang="ts"> <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 methods: {
export default class TrainSchedule extends Vue { stylizeTime(timeString: string, delay: number, confirmed: boolean) {
@Prop() readonly followingStops!: TrainStop[]; return (
@Prop() readonly currentStationName!: string; timeString +
(delay != 0 && confirmed
stylizeTime(timeString: string, delay: number, confirmed: boolean) { ? " (" + (delay > 0 ? "+" : "") + delay.toString() + ")"
return ( : "")
timeString + );
(delay != 0 && confirmed },
? " (" + (delay > 0 ? "+" : "") + delay.toString() + ")" },
: "") });
);
}
click() {
this.$emit("click");
}
mounted() {
console.log("mounted");
}
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
+141 -99
View File
@@ -1,16 +1,29 @@
<template> <template>
<div class="train-stats"> <div class="train-stats">
<div class="stats_button"> <div class="stats_button">
<action-button @click.native="toggleStatsOpen"> <action-button @click="toggleStatsOpen">
<img :src="statsIcon" :alt="$t('trains.stats')" /> <img
{{ $t("trains.stats") }} :src="statsIcon"
:alt="$t('trains.stats')"
/>
<p class="xd">{{ $t("trains.stats") }}</p>
</action-button> </action-button>
</div> </div>
<transition name="stats-anim" class="stats_wrapper" tag="div"> <transition
<div class="stats-body" v-if="trainStatsOpen"> name="stats-anim"
class="stats_wrapper"
tag="div"
>
<div
class="stats-body"
v-if="trainStatsOpen"
>
<h2 class="stats-header"> <h2 class="stats-header">
<img :src="statsIcon" :alt="$t('trains.stats')" /> <img
:src="statsIcon"
:alt="$t('trains.stats')"
/>
{{ $t("trains.stats") }} {{ $t("trains.stats") }}
</h2> </h2>
@@ -70,7 +83,11 @@
<div class="title stats-title">{{ $t("trains.stats-locos") }}</div> <div class="title stats-title">{{ $t("trains.stats-locos") }}</div>
<div class="loco-list stats-content"> <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] }} {{ loco[0] }} | {{ loco[1] }}
</div> </div>
</div> </div>
@@ -81,120 +98,145 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Vue, Prop } from "vue-property-decorator";
import ActionButton from "@/components/Global/ActionButton.vue"; import ActionButton from "@/components/Global/ActionButton.vue";
import Train from "@/scripts/interfaces/Train"; import Train from "@/scripts/interfaces/Train";
import { computed, defineComponent } from "@vue/runtime-core";
@Component({ components: { ActionButton } }) export default defineComponent({
export default class TrainStats extends Vue { components: { ActionButton },
@Prop() readonly trains!: Train[]; props: {
trainStatsOpen = false; trains: {
type: Array as () => Train[],
required: true,
},
},
toggleStatsOpen() { data: () => ({
this.trainStatsOpen = !this.trainStatsOpen; 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 } { setup(props) {
if (this.trains.length == 0) return { avg: "0", min: "0", max: "0" }; const speedStats = computed(() => {
if (props.trains.length == 0) return { avg: "0", min: "0", max: "0" };
const avg = ( const avg = (
this.trains.reduce((acc, train) => acc + train.speed, 0) / props.trains.reduce((acc, train) => acc + train.speed, 0) /
this.trains.length props.trains.length
).toFixed(2); ).toFixed(2);
const minMax = this.trains.reduce((acc, train) => { const minMaxSpeed = props.trains.reduce((acc, train) => {
if (!train.timetableData) return acc; if (!train.timetableData) return acc;
acc[0] = acc[0] = !acc[0] || train.speed < acc[0] ? train.speed : acc[0];
acc[0] === undefined || train.speed < acc[0] ? train.speed : acc[0];
acc[1] = acc[1] = !acc[1] || train.speed > acc[1] ? train.speed : acc[1];
acc[1] === undefined || train.speed > acc[1] ? train.speed : acc[1]; return acc;
return acc; }, [] as any);
}, [] 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 } { const timetableStats = computed(() => {
if (this.trains.length == 0) return { avg: "0", min: "0", max: "0" }; if (props.trains.length == 0) return { avg: "0", min: "0", max: "0" };
const avg = ( const avg = (
this.trains.reduce( props.trains.reduce(
(acc, train) => (acc, train) =>
train.timetableData ? acc + train.timetableData.routeDistance : acc, train.timetableData ? acc + train.timetableData.routeDistance : acc,
0 0
) / this.trains.length ) / props.trains.length
).toFixed(2); ).toFixed(2);
const minMax = this.trains.reduce((acc, train) => { const minMaxDistance = props.trains.reduce((acc, train) => {
if (!train.timetableData) return acc; if (!train.timetableData) return acc;
acc[0] = acc[0] =
acc[0] === undefined || train.timetableData.routeDistance < acc[0] !acc[0] || train.timetableData.routeDistance < acc[0]
? train.timetableData.routeDistance ? train.timetableData.routeDistance
: acc[0]; : acc[0];
acc[1] = acc[1] =
acc[1] === undefined || train.timetableData.routeDistance > acc[1] !acc[1] || train.timetableData.routeDistance > acc[1]
? train.timetableData.routeDistance ? train.timetableData.routeDistance
: acc[1]; : acc[1];
return acc; return acc;
}, [] as any); }, [] 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 categoryList = computed(() => {
const map = this.trains.reduce((acc, train) => { const map = props.trains.reduce((acc, train) => {
if (!train.timetableData || !train.timetableData.category) return acc; if (!train.timetableData || !train.timetableData.category) return acc;
acc.set( acc.set(
train.timetableData.category, train.timetableData.category,
acc.get(train.timetableData.category) acc.get(train.timetableData.category)
? acc.get(train.timetableData.category) + 1 ? acc.get(train.timetableData.category) + 1
: 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; return [twrList.length, skrList.length];
}, new Map()); });
return new Map([...map.entries()].sort((a, b) => b[1] - a[1])); return {
} speedStats,
timetableStats,
get locoList(): any[] { categoryList,
const map = this.trains.reduce((acc, train) => { locoList,
if (!train.timetableData || !train.locoType) return acc; specialTrainCount,
};
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];
}
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@@ -206,7 +248,7 @@ export default class TrainStats extends Vue {
transition: all 150ms ease-out; transition: all 150ms ease-out;
} }
&-enter, &-enter-from,
&-leave-to { &-leave-to {
opacity: 0; opacity: 0;
transform: translateY(30px); transform: translateY(30px);
+134 -88
View File
@@ -26,7 +26,7 @@
class="train-row" class="train-row"
v-for="(train, i) in computedTrains" v-for="(train, i) in computedTrains"
:key="i" :key="i"
:ref="train.timetableData ? train.timetableData.timetableId : -1" :ref="train.timetableData && (el => { elList[train.timetableData.timetableId] = el })"
> >
<div <div
class="wrapper no-timetable" class="wrapper no-timetable"
@@ -319,117 +319,163 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Vue, Component, Prop, Watch } from "vue-property-decorator";
import Train from "@/scripts/interfaces/Train"; import Train from "@/scripts/interfaces/Train";
import TrainStop from "@/scripts/interfaces/TrainStop"; import TrainStop from "@/scripts/interfaces/TrainStop";
import TrainSchedule from "@/components/TrainsView/TrainSchedule.vue"; import TrainSchedule from "@/components/TrainsView/TrainSchedule.vue";
import { DataStatus } from "@/scripts/enums/DataStatus"; 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({ export default defineComponent({
components: { TrainSchedule }, components: {
}) TrainSchedule,
export default class TrainTable extends Vue { },
@Prop() computedTrains!: Train[];
@Prop() timetableDataStatus!: DataStatus;
@Prop() queryTrain!: string;
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"); data: () => ({
descSVG = require("@/assets/icon-arrow-desc.svg"); showedSchedule: 0,
defaultLocoImage: require("@/assets/unknown.png"),
speedIcon: string = require("@/assets/icon-speed.svg"); ascSVG: require("@/assets/icon-arrow-asc.svg"),
massIcon: string = require("@/assets/icon-mass.svg"); descSVG: require("@/assets/icon-arrow-desc.svg"),
lengthIcon: string = require("@/assets/icon-length.svg");
distanceIcon: string = require("@/assets/icon-distance.svg"); speedIcon: require("@/assets/icon-speed.svg"),
sceneryIcon: string = require("@/assets/icon-scenery.svg"); massIcon: require("@/assets/icon-mass.svg"),
signalIcon: string = require("@/assets/icon-signal.svg"); lengthIcon: require("@/assets/icon-length.svg"),
routeIcon: string = require("@/assets/icon-route.svg");
get timetableLoaded() { distanceIcon: require("@/assets/icon-distance.svg"),
return this.timetableDataStatus == DataStatus.Loaded; sceneryIcon: require("@/assets/icon-scenery.svg"),
} signalIcon: require("@/assets/icon-signal.svg"),
routeIcon: require("@/assets/icon-route.svg"),
}),
get timetableError() { setup(props) {
return this.timetableDataStatus == DataStatus.Error; const store = useStore();
} const elList: Ref<HTMLElement[]> = ref([]);
get distanceLimitExceeded() { onBeforeUpdate(() => {
return ( elList.value.length = 0;
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",
});
}); });
}
@Watch("queryTrain") const timetableDataStatus: ComputedRef<DataStatus> = computed(
onSearchedTrainChange(trainNo: string) { () => store.getters[GETTERS.timetableDataStatus]
const timetableId = this.computedTrains.find( );
(train) => train.trainNo == parseInt(trainNo)
)?.timetableData?.timetableId;
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) { return {
const imageEl = e.target as HTMLImageElement; timetableLoaded,
timetableError,
distanceLimitExceeded,
elList,
};
},
imageEl.src = this.defaultLocoImage; methods: {
} changeScheduleShowState(timetableId: number) {
if (timetableId < 0) return;
generateStopList(stops: any): string | undefined { this.showedSchedule =
if (!stops) return ""; this.showedSchedule == timetableId ? 0 : timetableId;
return stops this.$nextTick(() => {
.reduce((acc, stop: TrainStop, i) => { const currentEl: HTMLElement = this.elList[timetableId];
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[]) { currentEl.scrollIntoView({
if (cars.length == 0 && locoType.includes("EN")) return "EZT"; behavior: "smooth",
else if (cars.length == 0) return "LOK"; 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> </script>
<style lang="scss" scoped> <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()
})
+7 -7
View File
@@ -78,11 +78,11 @@
}, },
"sceneries": { "sceneries": {
"station": "Station", "station": "Station",
"min-lvl": "Min. dispatcher <br> level", "min-lvl": "Min. dispatcher\nlevel",
"status": "Status", "status": "Status",
"dispatcher": "Dispatcher", "dispatcher": "Dispatcher",
"dispatcher-lvl": "Dispatcher <br> level", "dispatcher-lvl": "Dispatcher\nlevel",
"routes": "Routes <br> double | single", "routes": "Routes\ndouble / single",
"general": "General info", "general": "General info",
"users": "Drivers online", "users": "Drivers online",
"spawns": "Spawns online", "spawns": "Spawns online",
@@ -93,8 +93,8 @@
"no-trains": "Oops! No trains online!", "no-trains": "Oops! No trains online!",
"loading": "Loading train data...", "loading": "Loading train data...",
"stats": "TRAFFIC STATISTICS", "stats": "TRAFFIC STATISTICS",
"stats-speed": "TRAINS SPEED (MIN | AVG | MAX) [km/h]", "stats-speed": "TRAINS SPEED (MIN, AVG, MAX) [km/h]",
"stats-length": "TIMETABLES LENGTH (MIN | AVG | MAX) [km]", "stats-length": "TIMETABLES LENGTH (MIN, AVG, MAX) [km]",
"stats-categories": "TIMETABLE CATEGORIES", "stats-categories": "TIMETABLE CATEGORIES",
"stats-special-twr": "HIGH RISK", "stats-special-twr": "HIGH RISK",
"stats-special-skr": "EXCEEDED STRUCT. GAUGE", "stats-special-skr": "EXCEEDED STRUCT. GAUGE",
@@ -115,7 +115,7 @@
"journal": { "journal": {
"title": "SCENERY ACTIVITY JOURNAL", "title": "SCENERY ACTIVITY JOURNAL",
"subtitle": "Shows all recent dispatchers on a selected scenery", "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!", "disclaimer": "<b>This functionality is unfinished!</b> \n Information shown here could be false or incorrect!",
"select": "Select a scenery" "select": "Select a scenery"
}, },
"scenery": { "scenery": {
@@ -137,6 +137,6 @@
"stopped": "Stopped", "stopped": "Stopped",
"terminated": "Terminated", "terminated": "Terminated",
"begins": "BEGINS HERE", "begins": "BEGINS HERE",
"terminates": "TERMINATES <br /> HERE" "terminates": "TERMINATES\nHERE"
} }
} }
+8 -8
View File
@@ -78,12 +78,12 @@
}, },
"sceneries": { "sceneries": {
"station": "Stacja", "station": "Stacja",
"min-lvl": "Min. poziom <br/> dyżurnego", "min-lvl": "Min. poziom\ndyżurnego",
"status": "Status", "status": "Status",
"dispatcher": "Dyżurny", "dispatcher": "Dyżurny",
"dispatcher-lvl": "Poziom <br> dyżurnego", "dispatcher-lvl": "Poziom\ndyżurnego",
"routes": "Szlaki <br> 2tor | 1tor", "routes": "Szlaki\n2tor / 1tor",
"general": "Informacje <br> ogólne", "general": "Informacje\nogólne",
"users": "Maszyniści online", "users": "Maszyniści online",
"spawns": "Otwarte spawny", "spawns": "Otwarte spawny",
"timetables": "Aktywne rozkłady jazdy", "timetables": "Aktywne rozkłady jazdy",
@@ -93,8 +93,8 @@
"no-trains": "Brak pociągów online!", "no-trains": "Brak pociągów online!",
"loading": "Pobieranie danych o pociągach...", "loading": "Pobieranie danych o pociągach...",
"stats": "STATYSTYKI RUCHU", "stats": "STATYSTYKI RUCHU",
"stats-speed": "PRĘDKOŚCI POCIĄGÓW (MIN | ŚR | MAX) [km/h]", "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-length": "DŁUGOŚCI ROZKŁADÓW (MIN, ŚR, MAX) [km]",
"stats-categories": "KATEGORIE RJ", "stats-categories": "KATEGORIE RJ",
"stats-special-twr": "WYSOKIEGO RYZYKA", "stats-special-twr": "WYSOKIEGO RYZYKA",
"stats-special-skr": "PRZEKROCZONA SKRAJNIA", "stats-special-skr": "PRZEKROCZONA SKRAJNIA",
@@ -115,7 +115,7 @@
"journal": { "journal": {
"title": "DZIENNIK AKTYWNOŚCI SCENERII", "title": "DZIENNIK AKTYWNOŚCI SCENERII",
"subtitle": "Pokazuje dyżurnych, którzy ostatnio byli aktywni na wybranej 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!", "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ę" "select": "Wybierz scenerię"
}, },
"scenery": { "scenery": {
@@ -136,7 +136,7 @@
"arriving": "W drodze", "arriving": "W drodze",
"stopped": "Postój", "stopped": "Postój",
"terminated": "Skończył bieg", "terminated": "Skończył bieg",
"begins": "ROZPOCZYNA <br /> BIEG", "begins": "ROZPOCZYNA\nBIEG",
"terminates": "KOŃCZY BIEG" "terminates": "KOŃCZY BIEG"
} }
} }
+15 -18
View File
@@ -1,28 +1,25 @@
import Vue from 'vue'; import { createApp } from 'vue'
import App from './App.vue'; import App from './App.vue'
import router from './router'; import router from './router'
import store from './store'; import { store, key } from './store'
import VueI18n from 'vue-i18n';
import enLang from '@/lang/en.json'; import enLang from '@/locales/en.json';
import plLang from '@/lang/pl.json'; import plLang from '@/locales/pl.json';
Vue.use(VueI18n); import { createI18n } from 'vue-i18n'
const i18n = new VueI18n({ const i18n = createI18n({
locale: 'pl', locale: 'pl',
fallbackLocale: 'pl', fallbackLocale: 'pl',
messages: { messages: {
en: enLang, en: enLang,
pl: plLang, pl: plLang,
}, },
}); enableLegacy: false
})
Vue.config.productionTip = false; createApp(App)
.use(store, key)
new Vue({ .use(router)
router, .use(i18n)
store, .mount('#app')
i18n,
render: h => h(App),
}).$mount('#app');
View File
+39 -40
View File
@@ -1,47 +1,46 @@
import Vue from 'vue'; import { defineComponent } from 'vue';
import Component from 'vue-class-component';
// You can declare mixins as the same style as components. export default defineComponent({
@Component methods: {
export default class styleMixin extends Vue { calculateExpStyle(exp: string | number, isSupporter = false): string {
calculateExpStyle(exp: string | number, isSupporter: boolean = false): string { const bgColor = exp > -1 ? (exp < 2 ? '#26B0D9' : `hsl(${-exp * 5 + 100}, 85%, 50%)`) : '#666';
const bgColor = exp > -1 ? (exp < 2 ? '#26B0D9' : `hsl(${-exp * 5 + 100}, 85%, 50%)`) : '#666';
const fontColor = exp > 15 || exp == -1 ? 'white' : 'black'; const fontColor = exp > 15 || exp == -1 ? 'white' : 'black';
const boxShadow = isSupporter ? `0 0 10px 2px ${bgColor}` : ''; const boxShadow = isSupporter ? `box-shadow: 0 0 10px 2px ${bgColor};` : '';
return `backgroundColor: ${bgColor}; color: ${fontColor}; box-shadow: ${boxShadow};`; return `background-color: ${bgColor}; color: ${fontColor}; ${boxShadow}`;
} },
statusClasses(occupiedTo: string) { statusClasses(occupiedTo: string) {
let className = ''; let className = '';
switch (occupiedTo) { switch (occupiedTo) {
case 'WOLNA': case 'WOLNA':
className = 'free'; className = 'free';
break; break;
case 'KOŃCZY': case 'KOŃCZY':
className = 'ending'; className = 'ending';
break; break;
case 'NIEZALOGOWANY': case 'NIEZALOGOWANY':
className = 'not-signed'; className = 'not-signed';
break; break;
case 'BEZ LIMITU': case 'BEZ LIMITU':
className = 'no-limit'; className = 'no-limit';
break; break;
case 'NIEDOSTĘPNY': case 'NIEDOSTĘPNY':
className = 'unavailable'; className = 'unavailable';
break; break;
case 'Z/W': case 'Z/W':
className = 'brb'; className = 'brb';
break; break;
case 'BRAK MIEJSCA': case 'BRAK MIEJSCA':
className = 'no-space'; className = 'no-space';
break; break;
default: default:
break; break;
}
return className;
} }
return className;
} }
} })
+14 -23
View File
@@ -1,22 +1,19 @@
import Vue from "vue"; import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
import VueRouter, { RouteConfig } from "vue-router";
import StationsView from "../views/StationsView.vue"; import StationsView from "@/views/StationsView.vue";
import TrainsView from "../views/TrainsView.vue";
Vue.use(VueRouter); const routes: Array<RouteRecordRaw> = [
const routes: Array<RouteConfig> = [
{ {
path: "/", path: '/',
name: "StationsView", name: 'StationsView',
component: StationsView component: StationsView
}, },
{ {
path: "/trains", path: "/trains",
name: "TrainsView", name: "TrainsView",
component: TrainsView, component: () => import("@/views/TrainsView.vue"),
props: true props: true,
}, },
{ {
path: "/scenery", path: "/scenery",
@@ -24,17 +21,11 @@ const routes: Array<RouteConfig> = [
component: () => import("@/views/SceneryView.vue"), component: () => import("@/views/SceneryView.vue"),
props: true props: true
}, },
{ ]
path: "/history",
name: "HistoryView",
component: () => import("@/views/HistoryView.vue")
}
];
const router = new VueRouter({ const router = createRouter({
mode: "history", history: createWebHashHistory(),
base: process.env.BASE_URL, routes,
routes })
});
export default router; export default router
+5 -3
View File
@@ -1,11 +1,12 @@
export default interface Filter { export default interface Filter {
[key: string]: (boolean | number),
default: boolean; default: boolean;
notDefault: boolean; notDefault: boolean;
real: boolean; real: boolean;
fictional: boolean; fictional: boolean;
SPK: boolean; "SPK": boolean;
SCS: boolean; "SCS": boolean;
SPE: boolean; "SPE": boolean;
ręczne: boolean; ręczne: boolean;
mechaniczne: boolean; mechaniczne: boolean;
współczesna: boolean; współczesna: boolean;
@@ -23,4 +24,5 @@ export default interface Filter {
free: boolean; free: boolean;
occupied: boolean; occupied: boolean;
ending: boolean; ending: boolean;
nonPublic: boolean;
} }
+1
View File
@@ -54,6 +54,7 @@ export default interface Station {
stationTrains: { stationTrains: {
driverName: number; driverName: number;
trainNo: number; trainNo: number;
stopStatus?: string;
}[]; }[];
scheduledTrains: ScheduledTrain[]; 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, free: true,
occupied: false, occupied: false,
ending: false, ending: false,
nonPublic: false
}; };
private filters: Filter = { ...this.filterInitStates }; 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`
};
+10 -22
View File
@@ -1,10 +1,9 @@
import Station from "../interfaces/Station"; import Station from "../interfaces/Station";
import TrainStop from "../interfaces/TrainStop"; import TrainStop from "../interfaces/TrainStop";
const timetableURL = (trainNo: number) => `https://api.td2.info.pl:9640/?method=readFromSWDR&value=getTimetable%3B${trainNo}%3Beu`; export const getLocoURL = (locoType: string): string => (`https://rj.td2.info.pl/dist/img/thumbnails/${locoType.includes("EN") ? locoType + "rb" : locoType}.png`)
const getLocoURL = (locoType: 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"; if (!stationStatus) return "not-signed";
const statusCode = stationStatus[2]; const statusCode = stationStatus[2];
@@ -33,7 +32,7 @@ const getStatusID = (stationStatus: any) => {
return "unavailable"; return "unavailable";
}; };
const getStatusTimestamp = (stationStatus: any) => { export const getStatusTimestamp = (stationStatus: any): number => {
if (!stationStatus) return -2; if (!stationStatus) return -2;
const statusCode = stationStatus[2]; const statusCode = stationStatus[2];
@@ -56,7 +55,7 @@ const getStatusTimestamp = (stationStatus: any) => {
return -1; return -1;
}; };
const parseSpawns = (spawnString: string) => { export const parseSpawns = (spawnString: string) => {
if (!spawnString) return []; if (!spawnString) return [];
if (spawnString === "NO_SPAWN") 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 timestamp
? new Date(timestamp).toLocaleTimeString("pl-PL", { ? new Date(timestamp).toLocaleTimeString("pl-PL", {
hour: "2-digit", hour: "2-digit",
@@ -79,10 +78,10 @@ const timestampToString = (timestamp: number | null) =>
}) })
: ""; : "";
const getTrainStopStatus = (stopInfo: TrainStop, timetableData: { currentStationName: string }, station: Station) => { export const getTrainStopStatus = (stopInfo: TrainStop, timetableData: { currentStationName: string }, station: Station) => {
let stopStatus: string = "", let stopStatus = "",
stopLabel: string = "", stopLabel = "",
stopStatusID: number = -1; stopStatusID = -1;
if (stopInfo.terminatesHere && stopInfo.confirmed) { if (stopInfo.terminatesHere && stopInfo.confirmed) {
stopStatus = "terminated"; stopStatus = "terminated";
@@ -112,14 +111,3 @@ const getTrainStopStatus = (stopInfo: TrainStop, timetableData: { currentStation
return { stopStatus, stopLabel, stopStatusID }; 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' { declare module '*.vue' {
import Vue from 'vue' import type { DefineComponent } from 'vue'
export default Vue const component: DefineComponent<{}, {}, any>
export default component
} }
+455 -9
View File
@@ -1,12 +1,458 @@
import Vue from 'vue'; /* eslint-disable */
import Vuex from 'vuex';
import Store from '@/store/store'; import { InjectionKey } from 'vue'
import { createStore, useStore as baseUseStore, Store } from 'vuex'
Vue.use(Vuex); import axios from "axios";
const store = new Vuex.Store({
modules: { import JSONStationData from "@/data/stationData.json";
Store,
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> </action-button>
</div> </div>
<div class="scenery-wrapper" v-if="stationInfo"> <div
<SceneryInfo :stationInfo="stationInfo" :timetableOnly="timetableOnly" /> class="scenery-wrapper"
v-if="stationInfo"
>
<SceneryInfo
:stationInfo="stationInfo"
:timetableOnly="timetableOnly"
/>
<SceneryTimetable <SceneryTimetable
:stationInfo="stationInfo" :stationInfo="stationInfo"
@@ -24,49 +30,58 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Vue, Component } from "vue-property-decorator"; import { StoreData } from "@/scripts/interfaces/StoreData";
import { DataStatus } from "@/scripts/enums/DataStatus";
import { Getter } from "vuex-class";
import Station from "@/scripts/interfaces/Station";
import SceneryInfo from "@/components/SceneryView/SceneryInfo.vue"; import SceneryInfo from "@/components/SceneryView/SceneryInfo.vue";
import SceneryTimetable from "@/components/SceneryView/SceneryTimetable.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"; 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 }, 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() { const data: ComputedRef<StoreData> = computed(
this.timetableOnly = () => store.getters[GETTERS.allData]
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 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> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
+82 -113
View File
@@ -1,14 +1,9 @@
<template> <template>
<div class="stations-view"> <div class="stations-view">
<DonationModal
:modalHidden="modalHidden"
@toggleModal="toggleModal"
/>
<div class="wrapper"> <div class="wrapper">
<div class="body"> <div class="body">
<div class="options-bar"> <div class="options-bar">
<action-button @click.native="() => toggleCardsState('filter')"> <action-button @click="() => toggleCardsState('filter')">
<img <img
class="button_icon" class="button_icon"
:src="require('@/assets/icon-filter2.svg')" :src="require('@/assets/icon-filter2.svg')"
@@ -42,7 +37,7 @@
<transition name="card-anim"> <transition name="card-anim">
<FilterCard <FilterCard
v-if="filterCardOpen" :showCard="filterCardOpen"
:exit="() => toggleCardsState('filter')" :exit="() => toggleCardsState('filter')"
@changeFilterValue="changeFilterValue" @changeFilterValue="changeFilterValue"
@resetFilters="resetFilters" @resetFilters="resetFilters"
@@ -52,10 +47,6 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Vue, Component } from "vue-property-decorator";
import { Getter } from "vuex-class";
import Station from "@/scripts/interfaces/Station"; import Station from "@/scripts/interfaces/Station";
import StorageManager from "@/scripts/managers/storageManager"; 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 ActionButton from "@/components/Global/ActionButton.vue";
import { StoreData } from "@/scripts/interfaces/StoreData"; import { StoreData } from "@/scripts/interfaces/StoreData";
import { DataStatus } from "@/scripts/enums/DataStatus"; 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: { components: {
StationTable, StationTable,
FilterCard, FilterCard,
ActionButton, ActionButton,
}, },
}) data: () => ({
export default class StationsView extends Vue { trainIcon: require("@/assets/icon-train.svg"),
STORAGE_KEY: string = "options_saved"; timetableIcon: require("@/assets/icon-timetable.svg"),
STORAGE_MODAL: string = "modal"; 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"); const data: ComputedRef<StoreData> = computed(
timetableIcon: string = require("@/assets/icon-timetable.svg"); () => store.getters[GETTERS.allData]
dolarIcon: string = require("@/assets/icon-dolar.svg"); );
filterManager: StationFilterManager = new StationFilterManager(); const computedStations: ComputedRef<Station[]> = computed(() => {
return filterManager.getFilteredStationList(
focusedStationName: string = ""; store.getters[GETTERS.stationList]
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 = "";
}
}); });
}
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; if (!StorageManager.isRegistered(this.STORAGE_KEY)) return;
this.inputs.options.forEach((option) => { this.inputs.options.forEach((option) => {
const value = StorageManager.getBooleanValue(option.name); const value = StorageManager.getBooleanValue(option.name);
this.changeFilterValue({ name: option.name, value: value ? 0 : 1 }); this.changeFilterValue({ name: option.name, value: value ? 0 : 1 });
option.value = value; option.value = value;
}); });
this.inputs.sliders.forEach((slider) => { this.inputs.sliders.forEach((slider) => {
const value = StorageManager.getNumericValue(slider.name); const value = StorageManager.getNumericValue(slider.name);
this.changeFilterValue({ name: slider.name, value }); this.changeFilterValue({ name: slider.name, value });
slider.value = value; slider.value = value;
}); });
} },
methods: {
initializeModalStorage() { toggleCardsState(name: string): void {
if (StorageManager.isRegistered(`${this.STORAGE_MODAL}_hidden`)) if (name == "filter") {
this.modalHidden = StorageManager.getBooleanValue( this.filterCardOpen = !this.filterCardOpen;
`${this.STORAGE_MODAL}_hidden` }
); },
} changeSorter(index: number) {
this.filterManager.changeSorter(index);
toggleModal() { },
this.modalHidden = !this.modalHidden; changeFilterValue(filter: { name: string; value: number }) {
this.filterManager.changeFilterValue(filter);
StorageManager.setBooleanValue( },
`${this.STORAGE_MODAL}_hidden`, resetFilters() {
this.modalHidden this.filterManager.resetFilters();
); },
} closeCard() {
this.focusedStationName = "";
toggleCardsState(name: string): void { },
if (name == "filter") { setFocusedStation(name: string) {
this.filterCardOpen = !this.filterCardOpen; this.focusedStationName = this.focusedStationName == name ? "" : name;
} },
} },
});
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
);
}
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@@ -202,7 +171,7 @@ export default class StationsView extends Vue {
transition: all $animDuration $animType; transition: all $animDuration $animType;
} }
&-enter, &-enter-from,
&-leave-to { &-leave-to {
transform: translate(-50%, -50%) scale(0.85); transform: translate(-50%, -50%) scale(0.85);
opacity: 0; opacity: 0;
+110 -78
View File
@@ -2,11 +2,14 @@
<section class="trains-view"> <section class="trains-view">
<div class="wrapper"> <div class="wrapper">
<div class="options-bar"> <div class="options-bar">
<TrainStats :trains="trains" :trainStatsOpen="trainStatsOpen" /> <TrainStats
:trains="trainList"
:trainStatsOpen="trainStatsOpen"
/>
<TrainOptions <TrainOptions
:queryTrain="queryTrain" :queryTrain="queryTrain"
@change-sorter="changeSorter" @changeSorter="changeSorter"
@changeSearchedTrain="changeSearchedTrain" @changeSearchedTrain="changeSearchedTrain"
@changeSearchedDriver="changeSearchedDriver" @changeSearchedDriver="changeSearchedDriver"
/> />
@@ -14,7 +17,6 @@
<TrainTable <TrainTable
:computedTrains="computedTrains" :computedTrains="computedTrains"
:timetableDataStatus="timetableDataStatus"
:queryTrain="queryTrain" :queryTrain="queryTrain"
/> />
</div> </div>
@@ -22,105 +24,135 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import Vue from "vue"; import { computed, ComputedRef, defineComponent, ref } from "vue";
import { Component, Prop } from "vue-property-decorator";
import { Getter } from "vuex-class";
import { DataStatus } from "@/scripts/enums/DataStatus";
import Train from "@/scripts/interfaces/Train"; import Train from "@/scripts/interfaces/Train";
import TrainTable from "@/components/TrainsView/TrainTable.vue"; import TrainTable from "@/components/TrainsView/TrainTable.vue";
import TrainStats from "@/components/TrainsView/TrainStats.vue"; import TrainStats from "@/components/TrainsView/TrainStats.vue";
import TrainOptions from "@/components/TrainsView/TrainOptions.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: { components: {
TrainTable, TrainTable,
TrainStats, TrainStats,
TrainOptions, TrainOptions,
ActionButton,
}, },
})
export default class TrainsView extends Vue {
@Getter("getTrainList") trains!: Train[];
@Getter("getTimetableDataStatus") timetableDataStatus!: DataStatus;
// Passed in route as query parameters props: ["queryTrain"],
@Prop() readonly queryTrain!: string;
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 = ""; const timetableDataStatus: ComputedRef<DataStatus> = computed(
searchedDriver: string = ""; () => store.getters[GETTERS.timetableDataStatus]
);
changeSearchedTrain(trainNo: string) { const sorterActive = ref({ id: "distance", dir: -1 });
this.searchedTrain = trainNo; const searchedDriver = ref("");
} const searchedTrain = ref("");
changeSearchedDriver(name: string) { const computedTrains: ComputedRef<Train[]> = computed(() => {
this.searchedDriver = name; if (timetableDataStatus.value != DataStatus.Loaded) return [];
}
changeSorter(sorter: { id: string; dir: number }) { return filteredTrainList(
this.sorterActive = sorter; trainList.value,
} searchedTrain.value,
searchedDriver.value,
sorterActive.value
);
});
get computedTrains() { return {
return this.timetableDataStatus != DataStatus.Loaded trainList,
? [] computedTrains,
: this.trains searchedTrain,
.filter( searchedDriver,
(train) => sorterActive,
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;
case "distance": methods: {
if ( changeSearchedTrain(trainNo: string) {
(a.timetableData?.routeDistance || -1) > this.searchedTrain = trainNo;
(b.timetableData?.routeDistance || -1) },
)
return this.sorterActive.dir;
return -this.sorterActive.dir; changeSearchedDriver(name: string) {
this.searchedDriver = name;
},
case "speed": changeSorter(sorter: { id: string; dir: number }) {
if (a.speed > b.speed) return this.sorterActive.dir; this.sorterActive = sorter;
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;
});
}
}
</script> </script>
<style lang="scss" scoped> <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>
}
}
-20
View File
@@ -1,23 +1,3 @@
// module.exports = { // module.exports = {
// publicPath: process.env.NODE_ENV === "production" ? "/dist" : "/", // 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
}
}
}
})
}
}