Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 826d51f66c | |||
| 1d7fc2955f | |||
| c550e7598a | |||
| d5168ce59d | |||
| 380c97655c | |||
| e4ed24de80 | |||
| 8de03b9210 | |||
| 12ece46089 | |||
| 085238fada | |||
| 45c1d83512 | |||
| af9073ab98 | |||
| 800fc35e63 | |||
| d5649d221b | |||
| 5b35fac512 | |||
| d5bc90f668 | |||
| 6d663886f0 | |||
| 85a1a0216e | |||
| 4ac054e947 | |||
| ba70fa1316 | |||
| 77e6b20d0c | |||
| f60263c923 | |||
| 6aec1a75c9 | |||
| d28d600833 | |||
| a353eb3185 | |||
| c5735a6953 | |||
| 7930f7fc8a | |||
| 68f4d54619 | |||
| c4f9738589 | |||
| dd916afd1d |
@@ -0,0 +1,18 @@
|
|||||||
|
/* eslint-env node */
|
||||||
|
require('@rushstack/eslint-patch/modern-module-resolution')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
extends: [
|
||||||
|
'plugin:vue/vue3-essential',
|
||||||
|
'eslint:recommended',
|
||||||
|
'@vue/eslint-config-typescript',
|
||||||
|
'@vue/eslint-config-prettier/skip-formatting'
|
||||||
|
],
|
||||||
|
rules: {
|
||||||
|
'vue/multi-word-component-names': 'off'
|
||||||
|
},
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 'latest'
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,6 +31,7 @@ node_modules
|
|||||||
.firebase
|
.firebase
|
||||||
.firebaserc
|
.firebaserc
|
||||||
|
|
||||||
|
# Env
|
||||||
.env
|
.env
|
||||||
|
|
||||||
.fake
|
.fake
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/prettierrc",
|
||||||
|
"tabWidth": 2,
|
||||||
|
"singleQuote": true,
|
||||||
|
"printWidth": 100,
|
||||||
|
"trailingComma": "none"
|
||||||
|
}
|
||||||
@@ -16,14 +16,30 @@
|
|||||||
<link rel="manifest" href="/site.webmanifest" />
|
<link rel="manifest" href="/site.webmanifest" />
|
||||||
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5" />
|
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5" />
|
||||||
<meta name="msapplication-TileColor" content="#da532c" />
|
<meta name="msapplication-TileColor" content="#da532c" />
|
||||||
<meta name="theme-color" content="#ffffff" />
|
<meta name="theme-color" content="#222222" />
|
||||||
|
|
||||||
<link rel="icon" href="favicon-64.png" sizes="64x64" type="image/png" />
|
<link rel="icon" href="favicon-64.png" sizes="64x64" type="image/png" />
|
||||||
<link rel="icon" href="favicon-32.png" sizes="32x32" type="image/png" />
|
|
||||||
<link rel="icon" href="favicon-62.png" sizes="62x62" type="image/png" />
|
<link rel="icon" href="favicon-62.png" sizes="62x62" type="image/png" />
|
||||||
|
<link rel="icon" href="favicon-32.png" sizes="32x32" type="image/png" />
|
||||||
<link rel="icon" href="favicon-16.png" sizes="16x16" type="image/png" />
|
<link rel="icon" href="favicon-16.png" sizes="16x16" type="image/png" />
|
||||||
<link rel="icon" href="favicon.ico" />
|
<link rel="icon" href="favicon.ico" />
|
||||||
|
|
||||||
|
<!-- Static OpenGraph meta -->
|
||||||
|
<meta name="description" content="Pomocnik maszynisty i dyżurnego symulatora Train Driver 2" />
|
||||||
|
<meta property="og:url" content="https://stacjownik-td2.web.app/" />
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<meta property="og:title" content="Stacjownik" />
|
||||||
|
<meta property="og:description" content="Pomocnik maszynisty i dyżurnego symulatora Train Driver 2" />
|
||||||
|
<meta property="og:image" content="https://raw.githubusercontent.com/Spythere/api/main/thumbnails/stacjownik.jpg" />
|
||||||
|
<meta property="og:image:width" content="1200" />
|
||||||
|
<meta property="og:image:height" content="630" />
|
||||||
|
<meta property="og:site_name" content="Stacjownik" />
|
||||||
|
|
||||||
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
|
<meta name="twitter:title" content="Stacjownik" />
|
||||||
|
<meta name="twitter:description" content="Pomocnik maszynisty i dyżurnego symulatora Train Driver 2" />
|
||||||
|
<meta name="twitter:image" content="https://raw.githubusercontent.com/Spythere/api/main/thumbnails/stacjownik.jpg" />
|
||||||
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Quicksand:wght@500;700&display=swap" rel="stylesheet" />
|
<link href="https://fonts.googleapis.com/css2?family=Quicksand:wght@500;700&display=swap" rel="stylesheet" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
@@ -32,4 +48,3 @@
|
|||||||
<script type="module" src="/src/main.ts"></script>
|
<script type="module" src="/src/main.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|||||||
@@ -1,33 +1,44 @@
|
|||||||
{
|
{
|
||||||
"name": "stacjownik",
|
"name": "stacjownik",
|
||||||
"version": "1.17.0",
|
"version": "1.18.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vue-tsc --noEmit && vite build",
|
"build": "vue-tsc --noEmit && vite build",
|
||||||
"deploy": "yarn build && firebase deploy --only hosting",
|
"deploy": "yarn build && firebase deploy --only hosting",
|
||||||
"preview": "yarn build && vite preview"
|
"preview": "yarn build && vite preview",
|
||||||
|
"type-check": "vue-tsc --noEmit -p tsconfig.app.json --composite false",
|
||||||
|
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
|
||||||
|
"format": "prettier --write src/"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"core-js": "^3.12.1",
|
"core-js": "^3.32.2",
|
||||||
"dotenv": "^16.0.3",
|
"dotenv": "^16.3.1",
|
||||||
"firebase": "^9.8.1",
|
"firebase": "^10.4.0",
|
||||||
"howler": "^2.2.1",
|
"howler": "^2.2.4",
|
||||||
"pinia": "^2.0.14",
|
"pinia": "^2.1.6",
|
||||||
"sass": "^1.53.0",
|
"sass": "^1.67.0",
|
||||||
"socket.io-client": "^4.4.1",
|
"socket.io-client": "^4.7.2",
|
||||||
"vue": "^3.2.37",
|
"vue": "^3.3.4",
|
||||||
"vue-i18n": "^9.1.6",
|
"vue-i18n": "^9.4.1",
|
||||||
"vue-router": "^4.0.0-0"
|
"vue-router": "^4.2.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^18.11.17",
|
"@types/node": "^20.6.2",
|
||||||
"@vitejs/plugin-vue": "^4.0.0",
|
"@vite-pwa/assets-generator": "^0.0.10",
|
||||||
"axios": "^1.2.1",
|
"@vitejs/plugin-vue": "^4.3.4",
|
||||||
"typescript": "^4.9.4",
|
"axios": "^1.5.0",
|
||||||
"vite": "^4.0.3",
|
"prettier": "^3.0.3",
|
||||||
"vite-plugin-pwa": "^0.14.0",
|
"typescript": "^5.2.2",
|
||||||
"vue-tsc": "^1.0.18"
|
"vite": "^4.4.9",
|
||||||
|
"vite-plugin-pwa": "^0.16.5",
|
||||||
|
"vue-tsc": "^1.8.11",
|
||||||
|
"@vue/eslint-config-prettier": "^8.0.0",
|
||||||
|
"@vue/eslint-config-typescript": "^12.0.0",
|
||||||
|
"@vue/tsconfig": "^0.4.0",
|
||||||
|
"eslint": "^8.49.0",
|
||||||
|
"eslint-plugin-vue": "^9.17.0",
|
||||||
|
"@rushstack/eslint-patch": "^1.3.3"
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
"> 1%",
|
"> 1%",
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 363 B After Width: | Height: | Size: 363 B |
|
Before Width: | Height: | Size: 537 B After Width: | Height: | Size: 537 B |
|
Before Width: | Height: | Size: 482 B After Width: | Height: | Size: 482 B |
|
Before Width: | Height: | Size: 271 B After Width: | Height: | Size: 271 B |
|
Before Width: | Height: | Size: 201 B After Width: | Height: | Size: 201 B |
|
Before Width: | Height: | Size: 212 B After Width: | Height: | Size: 212 B |
|
Before Width: | Height: | Size: 170 B After Width: | Height: | Size: 170 B |
|
After Width: | Height: | Size: 932 B |
|
After Width: | Height: | Size: 953 B |
|
Before Width: | Height: | Size: 350 B After Width: | Height: | Size: 350 B |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 264 B After Width: | Height: | Size: 264 B |
|
Before Width: | Height: | Size: 582 B After Width: | Height: | Size: 582 B |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 256 B After Width: | Height: | Size: 256 B |
|
Before Width: | Height: | Size: 201 B After Width: | Height: | Size: 201 B |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 632 B After Width: | Height: | Size: 632 B |
|
Before Width: | Height: | Size: 398 B After Width: | Height: | Size: 398 B |
|
Before Width: | Height: | Size: 938 B After Width: | Height: | Size: 938 B |
|
Before Width: | Height: | Size: 356 B After Width: | Height: | Size: 356 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 1020 B |
|
Before Width: | Height: | Size: 369 B After Width: | Height: | Size: 369 B |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 252 B After Width: | Height: | Size: 252 B |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 264 B After Width: | Height: | Size: 264 B |
|
Before Width: | Height: | Size: 199 B After Width: | Height: | Size: 199 B |
|
Before Width: | Height: | Size: 384 B After Width: | Height: | Size: 384 B |
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 270 B After Width: | Height: | Size: 270 B |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 476 B After Width: | Height: | Size: 476 B |
|
Before Width: | Height: | Size: 863 B After Width: | Height: | Size: 863 B |
|
Before Width: | Height: | Size: 304 B After Width: | Height: | Size: 304 B |
|
Before Width: | Height: | Size: 546 B After Width: | Height: | Size: 546 B |
|
Before Width: | Height: | Size: 230 B After Width: | Height: | Size: 230 B |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 199 B After Width: | Height: | Size: 199 B |
|
Before Width: | Height: | Size: 522 B After Width: | Height: | Size: 522 B |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 268 B After Width: | Height: | Size: 268 B |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 478 B After Width: | Height: | Size: 478 B |
|
Before Width: | Height: | Size: 477 B After Width: | Height: | Size: 477 B |
|
Before Width: | Height: | Size: 477 B After Width: | Height: | Size: 477 B |
|
Before Width: | Height: | Size: 477 B After Width: | Height: | Size: 477 B |
|
Before Width: | Height: | Size: 457 B After Width: | Height: | Size: 457 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 326 B After Width: | Height: | Size: 326 B |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 285 B After Width: | Height: | Size: 285 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 9.2 KiB |
|
Before Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 799 B |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |
@@ -1,3 +0,0 @@
|
|||||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M8.00251 14.9297L0 1.07422H6.14651L8.00251 4.27503L9.84583 1.07422H16L8.00251 14.9297Z" fill="black"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 215 B |
@@ -1,105 +1,106 @@
|
|||||||
@import './styles/responsive.scss';
|
@import './styles/responsive.scss';
|
||||||
@import './styles/variables.scss';
|
@import './styles/variables.scss';
|
||||||
@import './styles/global.scss';
|
@import './styles/global.scss';
|
||||||
|
|
||||||
// VUE ROUTE CHANGE ANIMATION
|
// VUE ROUTE CHANGE ANIMATION
|
||||||
.view-anim {
|
.view-anim {
|
||||||
&-enter-from,
|
&-enter-from,
|
||||||
&-leave-to {
|
&-leave-to {
|
||||||
opacity: 0.02;
|
opacity: 0.02;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-enter-active,
|
&-enter-active,
|
||||||
&-leave-active {
|
&-leave-active {
|
||||||
transition: all $animDuration $animType;
|
transition: all $animDuration $animType;
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-anim {
|
.modal-anim {
|
||||||
&-enter-active,
|
&-enter-active,
|
||||||
&-leave-active {
|
&-leave-active {
|
||||||
transition: all $animDuration $animType;
|
transition: all $animDuration $animType;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-enter-from,
|
&-enter-from,
|
||||||
&-leave-to {
|
&-leave-to {
|
||||||
transform: translateY(-25%);
|
transform: translateY(-25%);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.route {
|
.route {
|
||||||
margin: 0 0.2em;
|
margin: 0 0.2em;
|
||||||
|
|
||||||
&-active,
|
&-active,
|
||||||
&[data-active='true'] {
|
&[data-active='true'] {
|
||||||
color: $accentCol;
|
color: $accentCol;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// APP
|
// APP
|
||||||
#app {
|
#app {
|
||||||
color: white;
|
color: white;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
|
overflow-x: hidden;
|
||||||
@include smallScreen() {
|
|
||||||
font-size: calc(0.55rem + 1.1vw);
|
@include smallScreen() {
|
||||||
}
|
font-size: calc(0.65rem + 0.8vw);
|
||||||
|
}
|
||||||
@include screenLandscape() {
|
|
||||||
font-size: calc(0.45rem + 0.8vw);
|
@include screenLandscape() {
|
||||||
}
|
font-size: calc(0.45rem + 0.8vw);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// CONTAINER
|
|
||||||
.app_container {
|
// CONTAINER
|
||||||
display: flex;
|
.app_container {
|
||||||
flex-flow: column;
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
min-height: 100vh;
|
|
||||||
|
min-height: 100vh;
|
||||||
header {
|
|
||||||
flex: 0 0 auto;
|
header {
|
||||||
}
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
main {
|
|
||||||
flex: 1 1 auto;
|
main {
|
||||||
|
flex: 1 1 auto;
|
||||||
padding: 0 0.5em;
|
|
||||||
}
|
padding: 0 0.5em;
|
||||||
|
}
|
||||||
footer {
|
|
||||||
flex: 0 1 0.2em;
|
footer {
|
||||||
}
|
flex: 0 1 0.2em;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.warning {
|
|
||||||
background-color: firebrick;
|
.warning {
|
||||||
text-align: center;
|
background-color: firebrick;
|
||||||
padding: 0.5em 0.4em;
|
text-align: center;
|
||||||
max-width: 1100px;
|
padding: 0.5em 0.4em;
|
||||||
margin: 0 auto;
|
max-width: 1100px;
|
||||||
|
margin: 0 auto;
|
||||||
border-radius: 0 0 1em 1em;
|
|
||||||
}
|
border-radius: 0 0 1em 1em;
|
||||||
|
}
|
||||||
// FOOTER
|
|
||||||
footer.app_footer {
|
// FOOTER
|
||||||
max-width: 100%;
|
footer.app_footer {
|
||||||
padding: 0.5em;
|
max-width: 100%;
|
||||||
|
padding: 0.5em;
|
||||||
img {
|
|
||||||
width: 1.1em;
|
img {
|
||||||
vertical-align: text-bottom;
|
width: 1.1em;
|
||||||
}
|
vertical-align: text-bottom;
|
||||||
|
}
|
||||||
z-index: 10;
|
|
||||||
|
z-index: 10;
|
||||||
background: #111;
|
|
||||||
color: white;
|
background: #111;
|
||||||
|
color: white;
|
||||||
text-align: center;
|
|
||||||
vertical-align: middle;
|
text-align: center;
|
||||||
}
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,178 +1,158 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="app_container">
|
<div class="app_container">
|
||||||
<transition name="modal-anim">
|
<transition name="modal-anim">
|
||||||
<keep-alive>
|
<keep-alive>
|
||||||
<TrainModal v-if="store.chosenModalTrainId" />
|
<TrainModal v-if="store.chosenModalTrainId" />
|
||||||
</keep-alive>
|
</keep-alive>
|
||||||
</transition>
|
</transition>
|
||||||
|
|
||||||
<UpdatePrompt />
|
<AppHeader :current-lang="currentLang" @change-lang="changeLang" />
|
||||||
|
|
||||||
<AppHeader :current-lang="currentLang" @change-lang="changeLang" />
|
<main class="app_main">
|
||||||
|
<router-view v-slot="{ Component }">
|
||||||
<main class="app_main">
|
<keep-alive exclude="JournalView">
|
||||||
<router-view v-slot="{ Component }">
|
<component :is="Component" :key="$route.name" />
|
||||||
<keep-alive exclude="JournalView">
|
</keep-alive>
|
||||||
<component :is="Component" :key="$route.name" />
|
</router-view>
|
||||||
</keep-alive>
|
</main>
|
||||||
</router-view>
|
|
||||||
</main>
|
<footer class="app_footer">
|
||||||
|
©
|
||||||
<footer class="app_footer">
|
<a href="https://td2.info.pl/profile/?u=20777" target="_blank">Spythere</a>
|
||||||
©
|
{{ new Date().getUTCFullYear() }} |
|
||||||
<a href="https://td2.info.pl/profile/?u=20777" target="_blank">Spythere</a>
|
<a :href="releaseURL" target="_blank">v{{ VERSION }}{{ isOnProductionHost ? '' : 'dev' }}</a>
|
||||||
{{ new Date().getUTCFullYear() }} |
|
<br />
|
||||||
<a :href="releaseURL" target="_blank">v{{ VERSION }}{{ isOnProductionHost ? '' : 'dev' }}</a>
|
<a href="https://discord.gg/x2mpNN3svk">
|
||||||
<br />
|
<img src="/images/icon-discord.png" alt="" /> <b>{{ $t('footer.discord') }}</b>
|
||||||
<a href="https://discord.gg/x2mpNN3svk"><img :src="getIcon('discord', 'png')" alt=""> <b>{{ $t('footer.discord') }}</b></a>
|
</a>
|
||||||
|
|
||||||
<div style="display: none">∫ ukryta taktyczna całka do programowania w HTMLu</div>
|
<div style="display: none">∫ ukryta taktyczna całka do programowania w HTMLu</div>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, KeepAlive, provide, ref, watch } from 'vue';
|
import { defineComponent, watch } from 'vue';
|
||||||
|
|
||||||
import Clock from './components/App/Clock.vue';
|
import Clock from './components/App/Clock.vue';
|
||||||
|
|
||||||
import packageInfo from '.././package.json';
|
import packageInfo from '.././package.json';
|
||||||
|
|
||||||
import StatusIndicator from './components/App/StatusIndicator.vue';
|
import { useStore } from './store/store';
|
||||||
import SelectBox from './components/Global/SelectBox.vue';
|
import StatusIndicator from './components/App/StatusIndicator.vue';
|
||||||
import { useStore } from './store/store';
|
import SelectBox from './components/Global/SelectBox.vue';
|
||||||
import TrainModal from './components/Global/TrainModal.vue';
|
import TrainModal from './components/Global/TrainModal.vue';
|
||||||
import StorageManager from './scripts/managers/storageManager';
|
import StorageManager from './scripts/managers/storageManager';
|
||||||
import imageMixin from './mixins/imageMixin';
|
import AppHeader from './components/App/AppHeader.vue';
|
||||||
import AppHeader from './components/App/AppHeader.vue';
|
import axios from 'axios';
|
||||||
import axios from 'axios';
|
|
||||||
import UpdatePrompt from './components/App/UpdatePrompt.vue';
|
export default defineComponent({
|
||||||
import { VERSION } from 'vue-i18n';
|
components: {
|
||||||
import { RouterView } from 'vue-router';
|
Clock,
|
||||||
import useCustomSW from './mixins/useCustomSW';
|
StatusIndicator,
|
||||||
|
SelectBox,
|
||||||
export default defineComponent({
|
TrainModal,
|
||||||
components: {
|
AppHeader
|
||||||
Clock,
|
},
|
||||||
StatusIndicator,
|
|
||||||
SelectBox,
|
data: () => ({
|
||||||
TrainModal,
|
VERSION: packageInfo.version,
|
||||||
AppHeader,
|
store: useStore(),
|
||||||
UpdatePrompt,
|
|
||||||
},
|
currentLang: 'pl',
|
||||||
|
releaseURL: '',
|
||||||
mixins: [imageMixin],
|
isOnProductionHost: location.hostname == 'stacjownik-td2.web.app'
|
||||||
|
}),
|
||||||
setup() {
|
|
||||||
const store = useStore();
|
created() {
|
||||||
store.connectToAPI();
|
this.loadLang();
|
||||||
|
this.store.connectToAPI();
|
||||||
const { offlineReady } = useCustomSW();
|
|
||||||
|
this.store.isOffline = !window.navigator.onLine;
|
||||||
const isFilterCardVisible = ref(false);
|
|
||||||
|
window.addEventListener('offline', () => {
|
||||||
provide('isFilterCardVisible', isFilterCardVisible);
|
this.store.isOffline = true;
|
||||||
|
|
||||||
return {
|
this.store.apiData = {
|
||||||
store,
|
stations: [],
|
||||||
isFilterCardVisible,
|
dispatchers: [],
|
||||||
onlineDispatchers: computed(() =>
|
trains: [],
|
||||||
store.stationList.filter((station) => station.onlineInfo && station.onlineInfo.region == store.region.id)
|
connectedSocketCount: 0
|
||||||
),
|
};
|
||||||
|
|
||||||
dispatcherDataStatus: store.dataStatuses.dispatchers,
|
this.store.setStatuses();
|
||||||
};
|
});
|
||||||
},
|
|
||||||
|
window.addEventListener('online', () => {
|
||||||
data: () => ({
|
this.store.isOffline = false;
|
||||||
VERSION: packageInfo.version,
|
});
|
||||||
|
},
|
||||||
currentLang: 'pl',
|
|
||||||
releaseURL: '',
|
async mounted() {
|
||||||
isOnProductionHost: location.hostname == 'stacjownik-td2.web.app',
|
this.setReleaseURL();
|
||||||
}),
|
|
||||||
|
watch(
|
||||||
created() {
|
() => this.store.blockScroll,
|
||||||
this.loadLang();
|
(value) => {
|
||||||
|
if (value) document.body.classList.add('no-scroll');
|
||||||
this.store.isOffline = !window.navigator.onLine;
|
else document.body.classList.remove('no-scroll');
|
||||||
|
}
|
||||||
window.addEventListener('offline', () => {
|
);
|
||||||
this.store.isOffline = true;
|
},
|
||||||
|
|
||||||
this.store.apiData = {
|
watch: {
|
||||||
stations: [],
|
'$route.query.region': {
|
||||||
dispatchers: [],
|
immediate: true,
|
||||||
trains: [],
|
handler(regionQuery: string) {
|
||||||
connectedSocketCount: 0,
|
if (regionQuery) {
|
||||||
};
|
this.store.region.id = regionQuery;
|
||||||
|
}
|
||||||
this.store.setOnlineData();
|
}
|
||||||
});
|
}
|
||||||
|
},
|
||||||
window.addEventListener('online', () => {
|
|
||||||
this.store.isOffline = false;
|
methods: {
|
||||||
});
|
changeLang(lang: string) {
|
||||||
},
|
this.$i18n.locale = lang;
|
||||||
|
this.currentLang = lang;
|
||||||
async mounted() {
|
|
||||||
this.setReleaseURL();
|
StorageManager.setStringValue('lang', lang);
|
||||||
|
},
|
||||||
watch(
|
|
||||||
() => this.store.blockScroll,
|
async setReleaseURL() {
|
||||||
(value) => {
|
try {
|
||||||
if (value) {
|
const releaseData = await (
|
||||||
document.body.classList.add('no-scroll');
|
await axios.get('https://api.github.com/repos/Spythere/stacjownik/releases/latest')
|
||||||
return;
|
).data;
|
||||||
}
|
|
||||||
|
if (!releaseData) return;
|
||||||
document.body.classList.remove('no-scroll');
|
|
||||||
}
|
this.releaseURL = releaseData.html_url;
|
||||||
);
|
} catch (error) {
|
||||||
},
|
console.error(`Wystąpił błąd podczas pobierania danych z API GitHuba: ${error}`);
|
||||||
|
return;
|
||||||
methods: {
|
}
|
||||||
changeLang(lang: string) {
|
},
|
||||||
this.$i18n.locale = lang;
|
|
||||||
this.currentLang = lang;
|
loadLang() {
|
||||||
|
const storageLang = StorageManager.getStringValue('lang');
|
||||||
StorageManager.setStringValue('lang', lang);
|
|
||||||
},
|
if (storageLang) {
|
||||||
|
this.changeLang(storageLang);
|
||||||
async setReleaseURL() {
|
return;
|
||||||
try {
|
}
|
||||||
const releaseData = await (
|
|
||||||
await axios.get('https://api.github.com/repos/Spythere/stacjownik/releases/latest')
|
if (!window.navigator.language) return;
|
||||||
).data;
|
|
||||||
|
const naviLanguage = window.navigator.language.toString();
|
||||||
if (!releaseData) return;
|
|
||||||
|
if (naviLanguage.includes('en')) {
|
||||||
this.releaseURL = releaseData.html_url;
|
this.changeLang('en');
|
||||||
} catch (error) {
|
return;
|
||||||
console.error(`Wystąpił błąd podczas pobierania danych z API GitHuba: ${error}`);
|
}
|
||||||
return;
|
}
|
||||||
}
|
}
|
||||||
},
|
});
|
||||||
|
</script>
|
||||||
loadLang() {
|
|
||||||
const storageLang = StorageManager.getStringValue('lang');
|
<style lang="scss" src="./App.scss"></style>
|
||||||
|
|
||||||
if (storageLang) {
|
|
||||||
this.changeLang(storageLang);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!window.navigator.language) return;
|
|
||||||
|
|
||||||
const naviLanguage = window.navigator.language.toString();
|
|
||||||
|
|
||||||
if (naviLanguage.includes('en')) {
|
|
||||||
this.changeLang('en');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" src="./App.scss"></style>
|
|
||||||
|
|||||||
@@ -1,237 +1,249 @@
|
|||||||
<template>
|
<template>
|
||||||
<header class="app_header">
|
<header class="app_header">
|
||||||
<div class="header_container">
|
<div class="header_container">
|
||||||
<div class="header_icons">
|
<div class="header_icons">
|
||||||
<span class="icons-top">
|
<span class="icons-top">
|
||||||
<img :src="getIcon('pl')" alt="icon-pl" @click="changeLang('en')" v-if="currentLang == 'pl'" />
|
<img
|
||||||
<img :src="getIcon('en', 'jpg')" alt="icon-en" @click="changeLang('pl')" v-else />
|
src="/images/icon-pl.svg"
|
||||||
</span>
|
alt="icon-pl"
|
||||||
</div>
|
@click="changeLang('en')"
|
||||||
|
v-if="currentLang == 'pl'"
|
||||||
<div class="header_body">
|
/>
|
||||||
<StatusIndicator />
|
<img src="/images/icon-en.jpg" alt="icon-en" @click="changeLang('pl')" v-else />
|
||||||
|
</span>
|
||||||
<span class="header_brand">
|
</div>
|
||||||
<router-link to="/">
|
|
||||||
<img :src="getImage('stacjownik-header-logo.svg')" alt="Stacjownik" />
|
<div class="header_body">
|
||||||
</router-link>
|
<StatusIndicator />
|
||||||
</span>
|
|
||||||
|
<span class="header_brand">
|
||||||
<span class="header_info">
|
<router-link to="/">
|
||||||
<Clock />
|
<img src="/images/stacjownik-header-logo.svg" alt="Stacjownik" />
|
||||||
|
</router-link>
|
||||||
<div class="info_counter">
|
</span>
|
||||||
<img :src="getIcon('dispatcher')" alt="icon dispatcher" />
|
|
||||||
<span class="text--primary">{{ onlineDispatchersCount }}</span>
|
<span class="header_info">
|
||||||
|
<Clock />
|
||||||
<!-- <span class="g-tooltip">
|
|
||||||
<b class="text--primary">{{ factorU }}U</b>
|
<div class="info_counter">
|
||||||
<div class="content">Test</div>
|
<img src="/images/icon-dispatcher.svg" alt="icon dispatcher" />
|
||||||
</span> -->
|
<span class="text--primary">{{ onlineDispatchersCount }}</span>
|
||||||
|
|
||||||
<span class="text--grayed"> / </span>
|
<!-- <span class="g-tooltip">
|
||||||
<span class="text--primary">{{ onlineTrainsCount }}</span>
|
<b class="text--primary">{{ factorU }}U</b>
|
||||||
<img :src="getIcon('train')" alt="icon train" />
|
<div class="content">Test</div>
|
||||||
</div>
|
</span> -->
|
||||||
|
|
||||||
<span class="info_region">
|
<span class="text--grayed"> / </span>
|
||||||
<SelectBox :itemList="computedRegions" :defaultItemIndex="0" @selected="changeRegion" />
|
<span class="text--primary">{{ onlineTrainsCount }}</span>
|
||||||
</span>
|
<img src="/images/icon-train.svg" alt="icon train" />
|
||||||
</span>
|
</div>
|
||||||
|
|
||||||
<span class="header_links">
|
<span class="info_region">
|
||||||
<router-link class="route" active-class="route-active" to="/" exact>
|
<SelectBox :itemList="computedRegions" :defaultItemIndex="0" @selected="changeRegion" />
|
||||||
{{ $t('app.sceneries') }}
|
</span>
|
||||||
</router-link>
|
</span>
|
||||||
/
|
|
||||||
<router-link class="route" active-class="route-active" to="/trains">{{ $t('app.trains') }}</router-link>
|
<span class="header_links">
|
||||||
/
|
<router-link class="route" active-class="route-active" to="/" exact>
|
||||||
<router-link
|
{{ $t('app.sceneries') }}
|
||||||
class="route"
|
</router-link>
|
||||||
active-class="route-active"
|
/
|
||||||
:data-active="$route.path.startsWith('/journal')"
|
<router-link class="route" active-class="route-active" to="/trains">{{
|
||||||
to="/journal"
|
$t('app.trains')
|
||||||
>
|
}}</router-link>
|
||||||
{{ $t('app.journal') }}
|
/
|
||||||
</router-link>
|
<router-link
|
||||||
</span>
|
class="route"
|
||||||
</div>
|
active-class="route-active"
|
||||||
</div>
|
:data-active="$route.path.startsWith('/journal')"
|
||||||
</header>
|
to="/journal"
|
||||||
</template>
|
>
|
||||||
<script lang="ts">
|
{{ $t('app.journal') }}
|
||||||
import { defineComponent } from 'vue';
|
</router-link>
|
||||||
import { useStore } from '../../store/store';
|
</span>
|
||||||
import options from '../../data/options.json';
|
</div>
|
||||||
import imageMixin from '../../mixins/imageMixin';
|
</div>
|
||||||
import SelectBox from '../Global/SelectBox.vue';
|
</header>
|
||||||
import StatusIndicator from './StatusIndicator.vue';
|
</template>
|
||||||
import Clock from './Clock.vue';
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
export default defineComponent({
|
import { useStore } from '../../store/store';
|
||||||
emits: ['changeLang'],
|
import options from '../../data/options.json';
|
||||||
mixins: [imageMixin],
|
import SelectBox from '../Global/SelectBox.vue';
|
||||||
props: {
|
import StatusIndicator from './StatusIndicator.vue';
|
||||||
currentLang: {
|
import Clock from './Clock.vue';
|
||||||
type: String,
|
|
||||||
required: true,
|
export default defineComponent({
|
||||||
},
|
emits: ['changeLang'],
|
||||||
},
|
props: {
|
||||||
setup() {
|
currentLang: {
|
||||||
return {
|
type: String,
|
||||||
store: useStore(),
|
required: true
|
||||||
};
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
|
||||||
changeRegion(region: { id: string; value: string }) {
|
setup() {
|
||||||
this.store.changeRegion(region);
|
return {
|
||||||
},
|
store: useStore()
|
||||||
changeLang(lang: string) {
|
};
|
||||||
this.$emit('changeLang', lang);
|
},
|
||||||
},
|
|
||||||
},
|
methods: {
|
||||||
computed: {
|
changeRegion(region: { id: string; value: string }) {
|
||||||
onlineTrainsCount() {
|
this.store.changeRegion(region);
|
||||||
return this.store.trainList.filter((train) => train.online).length;
|
},
|
||||||
},
|
|
||||||
|
changeLang(lang: string) {
|
||||||
onlineDispatchersCount() {
|
this.$emit('changeLang', lang);
|
||||||
return this.store.stationList.filter(
|
}
|
||||||
(station) => station.onlineInfo && station.onlineInfo.region == this.store.region.id
|
},
|
||||||
).length;
|
|
||||||
},
|
computed: {
|
||||||
|
onlineTrainsCount() {
|
||||||
factorU() {
|
return this.store.trainList.filter((train) => train.online).length;
|
||||||
return this.onlineDispatchersCount == 0 ? '-' : (this.onlineTrainsCount / this.onlineDispatchersCount).toFixed(2);
|
},
|
||||||
},
|
|
||||||
|
onlineDispatchersCount() {
|
||||||
computedRegions() {
|
return this.store.onlineSceneryList.length;
|
||||||
return options.regions.map((region) => {
|
},
|
||||||
const regionStationCount =
|
|
||||||
this.store.apiData.stations?.filter((station) => station.region == region.id && station.isOnline).length || 0;
|
factorU() {
|
||||||
const regionTrainCount =
|
return this.onlineDispatchersCount == 0
|
||||||
this.store.apiData.trains?.filter((train) => train.region == region.id && train.online).length || 0;
|
? '-'
|
||||||
return {
|
: (this.onlineTrainsCount / this.onlineDispatchersCount).toFixed(2);
|
||||||
id: region.id,
|
},
|
||||||
value: `${region.value} <div class='text--grayed'>${regionStationCount} / ${regionTrainCount}</div>`,
|
|
||||||
selectedValue: region.value,
|
computedRegions() {
|
||||||
};
|
return options.regions.map((region) => {
|
||||||
});
|
const regionStationCount =
|
||||||
},
|
this.store.apiData.stations?.filter(
|
||||||
},
|
(station) => station.region == region.id && station.isOnline
|
||||||
components: { SelectBox, StatusIndicator, Clock },
|
).length || 0;
|
||||||
});
|
const regionTrainCount =
|
||||||
</script>
|
this.store.apiData.trains?.filter((train) => train.region == region.id && train.online)
|
||||||
<style lang="scss" scoped>
|
.length || 0;
|
||||||
@import '../../styles/variables.scss';
|
return {
|
||||||
@import '../../styles/responsive.scss';
|
id: region.id,
|
||||||
|
value: `${region.value} <div class='text--grayed'>${regionStationCount} / ${regionTrainCount}</div>`,
|
||||||
// HEADER
|
selectedValue: region.value
|
||||||
.app_header {
|
};
|
||||||
display: flex;
|
});
|
||||||
justify-content: center;
|
}
|
||||||
|
},
|
||||||
position: relative;
|
components: { SelectBox, StatusIndicator, Clock }
|
||||||
background-color: $primaryCol;
|
});
|
||||||
}
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
.header {
|
@import '../../styles/variables.scss';
|
||||||
&_body {
|
@import '../../styles/responsive.scss';
|
||||||
position: relative;
|
|
||||||
max-width: 20em;
|
// HEADER
|
||||||
}
|
.app_header {
|
||||||
|
display: flex;
|
||||||
&_container {
|
justify-content: center;
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
position: relative;
|
||||||
|
background-color: $primaryCol;
|
||||||
border-radius: 0 0 1em 1em;
|
}
|
||||||
|
|
||||||
@include smallScreen {
|
.header {
|
||||||
position: relative;
|
&_body {
|
||||||
margin-top: 0.5em;
|
position: relative;
|
||||||
}
|
max-width: 20em;
|
||||||
}
|
}
|
||||||
|
|
||||||
&_brand {
|
&_container {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
img {
|
|
||||||
width: 100%;
|
border-radius: 0 0 1em 1em;
|
||||||
|
|
||||||
margin: 0 auto;
|
@include smallScreen {
|
||||||
}
|
position: relative;
|
||||||
}
|
margin-top: 0.5em;
|
||||||
|
}
|
||||||
&_info {
|
}
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr 1fr;
|
&_brand {
|
||||||
font-size: 1.15em;
|
display: flex;
|
||||||
}
|
|
||||||
|
img {
|
||||||
&_links {
|
width: 100%;
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
margin: 0 auto;
|
||||||
|
}
|
||||||
border-radius: 0.7em;
|
}
|
||||||
|
|
||||||
font-size: 1.25em;
|
&_info {
|
||||||
padding: 0.5em;
|
display: grid;
|
||||||
}
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
|
font-size: 1.15em;
|
||||||
&_icons {
|
}
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
&_links {
|
||||||
top: 0;
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
padding: 0.5em;
|
|
||||||
|
border-radius: 0.7em;
|
||||||
@include smallScreen {
|
|
||||||
transform: translateX(85%);
|
font-size: 1.25em;
|
||||||
}
|
padding: 0.5em;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
&_icons {
|
||||||
// ICONS
|
position: absolute;
|
||||||
.icons-top {
|
right: 0;
|
||||||
img {
|
top: 0;
|
||||||
width: 2.5em;
|
|
||||||
cursor: pointer;
|
padding: 0.5em;
|
||||||
}
|
|
||||||
}
|
@include smallScreen {
|
||||||
|
transform: translateX(85%);
|
||||||
// COUNTER
|
}
|
||||||
.info_counter {
|
}
|
||||||
display: flex;
|
}
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
// ICONS
|
||||||
|
.icons-top {
|
||||||
span {
|
img {
|
||||||
margin: 0 0.15em;
|
width: 2.5em;
|
||||||
}
|
cursor: pointer;
|
||||||
|
}
|
||||||
img {
|
}
|
||||||
width: 1.35em;
|
|
||||||
}
|
// COUNTER
|
||||||
}
|
.info_counter {
|
||||||
|
display: flex;
|
||||||
// REGION SELECTION
|
justify-content: center;
|
||||||
.info_region {
|
align-items: center;
|
||||||
color: white;
|
|
||||||
font-weight: bold;
|
span {
|
||||||
|
margin: 0 0.15em;
|
||||||
display: flex;
|
}
|
||||||
justify-content: flex-end;
|
|
||||||
|
img {
|
||||||
.select-box_content button {
|
width: 1.35em;
|
||||||
background-color: transparent;
|
}
|
||||||
font-weight: bold;
|
}
|
||||||
padding: 0.1em 0.5em;
|
|
||||||
color: paleturquoise;
|
// REGION SELECTION
|
||||||
}
|
.info_region {
|
||||||
|
color: white;
|
||||||
.options {
|
font-weight: bold;
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
display: flex;
|
||||||
}
|
justify-content: flex-end;
|
||||||
</style>
|
|
||||||
|
.select-box_content button {
|
||||||
|
background-color: transparent;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 0.1em 0.5em;
|
||||||
|
color: paleturquoise;
|
||||||
|
}
|
||||||
|
|
||||||
|
.options {
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,36 +1,37 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="clock">{{ computedDate }}</div>
|
<div class="clock">{{ computedDate }}</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
<script lang="ts">
|
import { computed, defineComponent, ref } from 'vue';
|
||||||
import { computed, defineComponent, ref } from "vue";
|
export default defineComponent({
|
||||||
export default defineComponent({
|
name: 'VueClock',
|
||||||
name: "clock",
|
data: () => ({
|
||||||
data: () => ({
|
timestamp: Date.now()
|
||||||
timestamp: Date.now(),
|
}),
|
||||||
}),
|
setup() {
|
||||||
setup() {
|
let timestamp = ref(Date.now());
|
||||||
let timestamp = ref(Date.now());
|
|
||||||
|
const computedDate = computed(() =>
|
||||||
const computedDate = computed(() => new Date(timestamp.value).toLocaleString("pl-PL", {
|
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);
|
|
||||||
|
setInterval(() => (timestamp.value = Date.now()), 1000);
|
||||||
return { computedDate }
|
|
||||||
}
|
return { computedDate };
|
||||||
});
|
}
|
||||||
</script>
|
});
|
||||||
|
</script>
|
||||||
<style lang="scss" scoped>
|
|
||||||
@import "../../styles/responsive.scss";
|
<style lang="scss" scoped>
|
||||||
|
@import '../../styles/responsive.scss';
|
||||||
.clock {
|
|
||||||
display: flex;
|
.clock {
|
||||||
align-items: center;
|
display: flex;
|
||||||
}
|
align-items: center;
|
||||||
</style>
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -43,7 +43,13 @@
|
|||||||
<g v-if="greenBlinkLight" filter="url(#filter0_d_843_28)">
|
<g v-if="greenBlinkLight" filter="url(#filter0_d_843_28)">
|
||||||
<circle cx="15" cy="17" r="7" fill="#00FF0A" />
|
<circle cx="15" cy="17" r="7" fill="#00FF0A" />
|
||||||
|
|
||||||
<animate attributeType="XML" attributeName="opacity" values="1;0;1" dur="1s" repeatCount="indefinite" />
|
<animate
|
||||||
|
attributeType="XML"
|
||||||
|
attributeName="opacity"
|
||||||
|
values="1;0;1"
|
||||||
|
dur="1s"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
/>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<g v-if="redTopLight" filter="url(#filter1_d_843_28)">
|
<g v-if="redTopLight" filter="url(#filter1_d_843_28)">
|
||||||
@@ -56,7 +62,13 @@
|
|||||||
<g v-if="redBottomLight" filter="url(#filter3_d_843_28)">
|
<g v-if="redBottomLight" filter="url(#filter3_d_843_28)">
|
||||||
<circle cx="15" cy="74" r="7" fill="#F40000" />
|
<circle cx="15" cy="74" r="7" fill="#F40000" />
|
||||||
|
|
||||||
<animate attributeType="XML" attributeName="opacity" values="1;0;1" dur="1s" repeatCount="indefinite" />
|
<animate
|
||||||
|
attributeType="XML"
|
||||||
|
attributeName="opacity"
|
||||||
|
values="1;0;1"
|
||||||
|
dur="1s"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
/>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
@@ -82,7 +94,12 @@
|
|||||||
<feComposite in2="hardAlpha" operator="out" />
|
<feComposite in2="hardAlpha" operator="out" />
|
||||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 1 0 0 0 0 0.04 0 0 0 1 0" />
|
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 1 0 0 0 0 0.04 0 0 0 1 0" />
|
||||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_843_28" />
|
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_843_28" />
|
||||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_843_28" result="shape" />
|
<feBlend
|
||||||
|
mode="normal"
|
||||||
|
in="SourceGraphic"
|
||||||
|
in2="effect1_dropShadow_843_28"
|
||||||
|
result="shape"
|
||||||
|
/>
|
||||||
</filter>
|
</filter>
|
||||||
<filter
|
<filter
|
||||||
id="filter1_d_843_28"
|
id="filter1_d_843_28"
|
||||||
@@ -104,7 +121,12 @@
|
|||||||
<feGaussianBlur stdDeviation="2.5" />
|
<feGaussianBlur stdDeviation="2.5" />
|
||||||
<feColorMatrix type="matrix" values="0 0 0 0 0.770833 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0" />
|
<feColorMatrix type="matrix" values="0 0 0 0 0.770833 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0" />
|
||||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_843_28" />
|
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_843_28" />
|
||||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_843_28" result="shape" />
|
<feBlend
|
||||||
|
mode="normal"
|
||||||
|
in="SourceGraphic"
|
||||||
|
in2="effect1_dropShadow_843_28"
|
||||||
|
result="shape"
|
||||||
|
/>
|
||||||
</filter>
|
</filter>
|
||||||
<filter
|
<filter
|
||||||
id="filter2_d_843_28"
|
id="filter2_d_843_28"
|
||||||
@@ -126,7 +148,12 @@
|
|||||||
<feGaussianBlur stdDeviation="2.5" />
|
<feGaussianBlur stdDeviation="2.5" />
|
||||||
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 0.72 0 0 0 0 0 0 0 0 1 0" />
|
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 0.72 0 0 0 0 0 0 0 0 1 0" />
|
||||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_843_28" />
|
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_843_28" />
|
||||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_843_28" result="shape" />
|
<feBlend
|
||||||
|
mode="normal"
|
||||||
|
in="SourceGraphic"
|
||||||
|
in2="effect1_dropShadow_843_28"
|
||||||
|
result="shape"
|
||||||
|
/>
|
||||||
</filter>
|
</filter>
|
||||||
<filter
|
<filter
|
||||||
id="filter3_d_843_28"
|
id="filter3_d_843_28"
|
||||||
@@ -148,7 +175,12 @@
|
|||||||
<feGaussianBlur stdDeviation="2.5" />
|
<feGaussianBlur stdDeviation="2.5" />
|
||||||
<feColorMatrix type="matrix" values="0 0 0 0 0.770833 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0" />
|
<feColorMatrix type="matrix" values="0 0 0 0 0.770833 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0" />
|
||||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_843_28" />
|
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_843_28" />
|
||||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_843_28" result="shape" />
|
<feBlend
|
||||||
|
mode="normal"
|
||||||
|
in="SourceGraphic"
|
||||||
|
in2="effect1_dropShadow_843_28"
|
||||||
|
result="shape"
|
||||||
|
/>
|
||||||
</filter>
|
</filter>
|
||||||
</defs>
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -173,14 +205,14 @@ export default defineComponent({
|
|||||||
indicator: {
|
indicator: {
|
||||||
offline: false,
|
offline: false,
|
||||||
status: DataStatus.Loading,
|
status: DataStatus.Loading,
|
||||||
message: 'data-status.S3',
|
message: 'data-status.S3'
|
||||||
},
|
},
|
||||||
|
|
||||||
greenLight: false,
|
greenLight: false,
|
||||||
greenBlinkLight: false,
|
greenBlinkLight: false,
|
||||||
redTopLight: false,
|
redTopLight: false,
|
||||||
orangeLight: false,
|
orangeLight: false,
|
||||||
redBottomLight: false,
|
redBottomLight: false
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -193,7 +225,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
dataStatus: store.dataStatuses,
|
dataStatus: store.dataStatuses,
|
||||||
store,
|
store
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -248,8 +280,8 @@ export default defineComponent({
|
|||||||
this.indicator.status = DataStatus.Loaded;
|
this.indicator.status = DataStatus.Loaded;
|
||||||
this.indicator.message = 'data-status.S2';
|
this.indicator.message = 'data-status.S2';
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
@@ -280,8 +312,8 @@ export default defineComponent({
|
|||||||
if (status == DataStatus.Loading) {
|
if (status == DataStatus.Loading) {
|
||||||
this.greenBlinkLight = true;
|
this.greenBlinkLight = true;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -375,4 +407,3 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -1,168 +0,0 @@
|
|||||||
<template>
|
|
||||||
<transition name="modal-anim">
|
|
||||||
<section class="update-modal card" v-if="releaseData && modalOpen">
|
|
||||||
<h2 class="modal_header text--primary">
|
|
||||||
<img :src="getImage('stacjownik-header-logo.svg')" alt="stacjownik logo" />
|
|
||||||
|
|
||||||
{{ releaseData.tag_name }}
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<div class="horizontal"></div>
|
|
||||||
|
|
||||||
<div class="modal_content">
|
|
||||||
<h3>{{ $t('update.title') }}</h3>
|
|
||||||
<a :href="releaseData.html_url" target="_blank">{{ $t('update.release-link') }}</a>
|
|
||||||
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
|
|
||||||
<p>{{ $t('update.paragraph1') }}</p>
|
|
||||||
|
|
||||||
<!-- <div class="modal_changelog" v-html="markdownReleaseBody"></div> -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="modal_actions">
|
|
||||||
<button class="btn btn--option" @click="modalOpen = false">{{ $t('update.confirm-button') }}</button>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</transition>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import axios from 'axios';
|
|
||||||
import { defineComponent } from 'vue';
|
|
||||||
import packageInfo from '../../../package.json';
|
|
||||||
import imageMixin from '../../mixins/imageMixin';
|
|
||||||
import { ReleaseAPIData } from '../../scripts/interfaces/github_api/ReleaseAPIData';
|
|
||||||
import StorageManager from '../../scripts/managers/storageManager';
|
|
||||||
import { useStore } from '../../store/store';
|
|
||||||
|
|
||||||
|
|
||||||
const GH_LASTEST_RELEASE_URL = 'https://api.github.com/repos/Spythere/stacjownik/releases/latest';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
mixins: [imageMixin],
|
|
||||||
|
|
||||||
mounted() {
|
|
||||||
this.fetchReleases();
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
modalOpen: false,
|
|
||||||
|
|
||||||
releaseData: null as ReleaseAPIData | null,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
return {
|
|
||||||
store: useStore()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
async fetchReleases() {
|
|
||||||
const storedVersion = StorageManager.getStringValue('appVersion');
|
|
||||||
const appVersion = packageInfo.version;
|
|
||||||
|
|
||||||
// Zmiana
|
|
||||||
if (appVersion != storedVersion) {
|
|
||||||
StorageManager.setStringValue('appVersion', appVersion);
|
|
||||||
|
|
||||||
// Znajdź changelog na GitHubie, jeśli jest pokaż modal
|
|
||||||
try {
|
|
||||||
const releaseData: ReleaseAPIData = await (await axios.get(GH_LASTEST_RELEASE_URL)).data;
|
|
||||||
if (!releaseData) return;
|
|
||||||
|
|
||||||
const lastReleaseVersion = releaseData.tag_name.slice(1);
|
|
||||||
|
|
||||||
if (lastReleaseVersion == appVersion) {
|
|
||||||
this.releaseData = releaseData;
|
|
||||||
this.modalOpen = true;
|
|
||||||
|
|
||||||
StorageManager.setStringValue('releaseURL', releaseData.html_url);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Wystąpił błąd podczas pobierania danych z API GitHuba: ${error}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
@import '../../styles/card.scss';
|
|
||||||
@import '../../styles/responsive.scss';
|
|
||||||
|
|
||||||
|
|
||||||
.modal-anim {
|
|
||||||
&-enter-active,
|
|
||||||
&-leave-active {
|
|
||||||
transition: all $animDuration $animType;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-enter-from,
|
|
||||||
&-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translate(-50%, -50%) scale(0.45);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.update-modal {
|
|
||||||
text-align: center;
|
|
||||||
background-color: var(--clr-secondary);
|
|
||||||
|
|
||||||
padding: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.horizontal {
|
|
||||||
margin: 1em 0;
|
|
||||||
|
|
||||||
height: 2px;
|
|
||||||
width: 100%;
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal_header {
|
|
||||||
font-size: 1.6em;
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 50%;
|
|
||||||
vertical-align: text-top;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal_content {
|
|
||||||
font-size: 1.1em;
|
|
||||||
|
|
||||||
a {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal_actions {
|
|
||||||
margin-top: 2em;
|
|
||||||
|
|
||||||
button {
|
|
||||||
color: white;
|
|
||||||
padding: 0.5em;
|
|
||||||
font-size: 1.2em;
|
|
||||||
|
|
||||||
background-color: black;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal_changelog {
|
|
||||||
font-size: 0.8em;
|
|
||||||
margin-top: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
@include smallScreen {
|
|
||||||
.update-modal {
|
|
||||||
height: auto;
|
|
||||||
max-width: 95%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="update-prompt">
|
|
||||||
<transition name="prompt-anim">
|
|
||||||
<div class="prompt_content" v-if="!hidePrompt && needRefresh">
|
|
||||||
<div>{{ $t('update.title') }}</div>
|
|
||||||
|
|
||||||
<div class="prompt_actions">
|
|
||||||
<button class="btn btn--filled" @click="updateServiceWorker(true)">{{ $t('update.confirm-button') }}</button>
|
|
||||||
<button class="btn btn--filled" @click="hidePrompt = true">{{ $t('update.later-button') }}</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref } from 'vue';
|
|
||||||
import useCustomSW from '../../mixins/useCustomSW';
|
|
||||||
|
|
||||||
const hidePrompt = ref(false);
|
|
||||||
const { needRefresh, updateServiceWorker } = useCustomSW();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
@import '../../styles/variables.scss';
|
|
||||||
|
|
||||||
.update-prompt {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 0;
|
|
||||||
right: 0;
|
|
||||||
z-index: 200;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prompt_content {
|
|
||||||
margin: 1em;
|
|
||||||
padding: 1em;
|
|
||||||
|
|
||||||
font-weight: bold;
|
|
||||||
background-color: black;
|
|
||||||
|
|
||||||
box-shadow: 0 0 10px 1px $accentCol;
|
|
||||||
border-radius: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prompt_actions {
|
|
||||||
display: flex;
|
|
||||||
margin-top: 1em;
|
|
||||||
gap: 0.5em;
|
|
||||||
|
|
||||||
button {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Animation
|
|
||||||
.prompt-anim {
|
|
||||||
&-enter-active,
|
|
||||||
&-leave-active {
|
|
||||||
transition: all 120ms ease-in;
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
&-enter-from,
|
|
||||||
&-leave-to {
|
|
||||||
transform: translateY(100%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
@@ -1,24 +1,24 @@
|
|||||||
<template>
|
<template>
|
||||||
<button class="action-btn btn--filled">
|
<button class="action-btn btn--filled">
|
||||||
<div class="button_content">
|
<div class="button_content">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
export default defineComponent({});
|
export default defineComponent({});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import "../../styles/variables";
|
@import '../../styles/variables';
|
||||||
@import "../../styles/responsive";
|
@import '../../styles/responsive';
|
||||||
|
|
||||||
.button_content {
|
.button_content {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<template>
|
||||||
|
<button
|
||||||
|
class="btn btn--option btn--load-data"
|
||||||
|
v-if="!scrollNoMoreData && scrollDataLoaded && list.length >= 15"
|
||||||
|
@click="addHistoryData"
|
||||||
|
>
|
||||||
|
{{ $t('journal.load-data') }}
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { PropType, defineComponent } from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
|
scrollNoMoreData: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
|
||||||
|
scrollDataLoaded: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
|
||||||
|
list: {
|
||||||
|
type: Array as PropType<any[]>,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
emits: ['addHistoryData'],
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
addHistoryData() {
|
||||||
|
this.$emit('addHistoryData');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
@@ -12,7 +12,7 @@ import { defineComponent } from 'vue';
|
|||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
setup() {
|
setup() {
|
||||||
return {};
|
return {};
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,62 +1,65 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="progress-bar">
|
<div class="progress-bar">
|
||||||
<span class="bar-bg"></span>
|
<span class="bar-bg"></span>
|
||||||
<span class="bar-fg" :style="{ width: `${~~progressPercent}%`, backgroundColor: bgColor }"></span>
|
<span
|
||||||
</div>
|
class="bar-fg"
|
||||||
</template>
|
:style="{ width: `${~~progressPercent}%`, backgroundColor: bgColor }"
|
||||||
|
></span>
|
||||||
<script lang="ts">
|
</div>
|
||||||
import { defineComponent } from 'vue';
|
</template>
|
||||||
|
|
||||||
export default defineComponent({
|
<script lang="ts">
|
||||||
props: {
|
import { defineComponent } from 'vue';
|
||||||
progressPercent: {
|
|
||||||
type: Number,
|
export default defineComponent({
|
||||||
required: true,
|
props: {
|
||||||
},
|
progressPercent: {
|
||||||
progressType: {
|
type: Number,
|
||||||
type: String,
|
required: true
|
||||||
required: false,
|
},
|
||||||
},
|
progressType: {
|
||||||
},
|
type: String,
|
||||||
|
required: false
|
||||||
computed: {
|
}
|
||||||
bgColor() {
|
},
|
||||||
switch (this.progressType) {
|
|
||||||
case 'abandoned':
|
computed: {
|
||||||
return 'salmon';
|
bgColor() {
|
||||||
|
switch (this.progressType) {
|
||||||
default:
|
case 'abandoned':
|
||||||
return 'springgreen';
|
return 'salmon';
|
||||||
}
|
|
||||||
},
|
default:
|
||||||
},
|
return 'springgreen';
|
||||||
});
|
}
|
||||||
</script>
|
}
|
||||||
|
}
|
||||||
<style lang="scss" scoped>
|
});
|
||||||
.progress-bar {
|
</script>
|
||||||
position: relative;
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
width: 6em;
|
.progress-bar {
|
||||||
height: 1em;
|
position: relative;
|
||||||
margin: 0.5em 0;
|
|
||||||
|
width: 6em;
|
||||||
.bar-fg,
|
height: 1em;
|
||||||
.bar-bg {
|
margin: 0.5em 0;
|
||||||
position: absolute;
|
|
||||||
height: 1em;
|
.bar-fg,
|
||||||
width: 100%;
|
.bar-bg {
|
||||||
|
position: absolute;
|
||||||
left: 0;
|
height: 1em;
|
||||||
}
|
width: 100%;
|
||||||
|
|
||||||
.bar-fg {
|
left: 0;
|
||||||
background-color: springgreen;
|
}
|
||||||
}
|
|
||||||
|
.bar-fg {
|
||||||
.bar-bg {
|
background-color: springgreen;
|
||||||
background-color: #5b5b5b;
|
}
|
||||||
}
|
|
||||||
}
|
.bar-bg {
|
||||||
</style>
|
background-color: #5b5b5b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -7,39 +7,31 @@
|
|||||||
@keypress="updateValue"
|
@keypress="updateValue"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<img
|
<img class="search-exit" src="/images/icon-exit.svg" alt="exit-icon" @click="clearSearchValue" />
|
||||||
class="search-exit"
|
|
||||||
:src="getIcon('exit')"
|
|
||||||
alt="exit-icon"
|
|
||||||
@click="clearValue"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, ref, watch } from "vue";
|
import { defineComponent, ref, watch } from 'vue';
|
||||||
import imageMixin from "../../mixins/imageMixin";
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
mixins: [imageMixin],
|
emits: ['update:searchedValue', 'clearValue'],
|
||||||
|
|
||||||
emits: ["update:searchedValue", "clearValue"],
|
|
||||||
props: {
|
props: {
|
||||||
searchedValue: {
|
searchedValue: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true
|
||||||
},
|
},
|
||||||
updateOnInput: {
|
updateOnInput: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: true
|
||||||
},
|
},
|
||||||
titleToTranslate: {
|
titleToTranslate: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true
|
||||||
},
|
},
|
||||||
clearValue: {
|
clearValue: {
|
||||||
type: Function,
|
type: Function
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
@@ -49,32 +41,32 @@ export default defineComponent({
|
|||||||
watch(
|
watch(
|
||||||
() => compSearchedValue.value,
|
() => compSearchedValue.value,
|
||||||
(value) => {
|
(value) => {
|
||||||
emit("update:searchedValue", value);
|
emit('update:searchedValue', value);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const clearValue = () => {
|
const clearSearchValue = () => {
|
||||||
compSearchedValue.value = "";
|
compSearchedValue.value = '';
|
||||||
emit("clearValue");
|
emit('clearValue');
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateValue = (e: any) => {
|
const updateValue = (e: any) => {
|
||||||
if (!props.updateOnInput && e.keyCode == 13)
|
if (!props.updateOnInput && e.keyCode == 13)
|
||||||
emit("update:searchedValue", compSearchedValue.value);
|
emit('update:searchedValue', compSearchedValue.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
compSearchedValue,
|
compSearchedValue,
|
||||||
updateValue,
|
updateValue,
|
||||||
clearValue,
|
clearSearchValue
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "../../styles/responsive";
|
@import '../../styles/responsive';
|
||||||
|
|
||||||
.search {
|
.search {
|
||||||
&-box {
|
&-box {
|
||||||
@@ -109,4 +101,4 @@ export default defineComponent({
|
|||||||
width: 1em;
|
width: 1em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,217 +1,235 @@
|
|||||||
<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 class="selected" @click="toggleBox">
|
||||||
<span>{{ computedSelectedItem.selectedValue || computedSelectedItem.value }}</span>
|
<span>{{ computedSelectedItem.selectedValue || computedSelectedItem.value }}</span>
|
||||||
|
|
||||||
<div class="arrow">
|
<div class="arrow">
|
||||||
<img :src="listOpen ? getIcon('arrow-asc') : getIcon('arrow-desc')" alt="arrow-icon" />
|
<img :src="`/images/icon-arrow-${listOpen ? 'asc' : 'desc'}.svg`" alt="Arrow icon" />
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<ul class="options" :ref="(el) => (listRef = el as Element)">
|
<ul class="options" :ref="(el) => (listRef = el as Element)">
|
||||||
<li class="option" v-for="(item, i) in itemList" :key="item.id">
|
<li class="option" v-for="(item, i) in itemList" :key="item.id">
|
||||||
<transition
|
<transition
|
||||||
name="unfold"
|
name="unfold"
|
||||||
:style="`
|
:style="`
|
||||||
--delay-in: ${i * 55}ms;
|
--delay-in: ${i * 55}ms;
|
||||||
--delay-out: ${(itemList.length - 1 - i) * 55}ms`"
|
--delay-out: ${(itemList.length - 1 - i) * 55}ms`"
|
||||||
>
|
>
|
||||||
<label :for="item.id" v-if="listOpen">
|
<label :for="item.id" v-if="listOpen">
|
||||||
<input type="button" :id="item.id" name="select-box" @click="selectOption(item)" />
|
<input type="button" :id="item.id" name="select-box" @click="selectOption(item)" />
|
||||||
<span :style="computedSelectedItem.id == item.id ? 'color: gold;' : ''" v-html="item.value"> </span>
|
<span
|
||||||
</label>
|
:style="computedSelectedItem.id == item.id ? 'color: gold;' : ''"
|
||||||
</transition>
|
v-html="item.value"
|
||||||
</li>
|
>
|
||||||
</ul>
|
</span>
|
||||||
</div>
|
</label>
|
||||||
</div>
|
</transition>
|
||||||
</template>
|
</li>
|
||||||
|
</ul>
|
||||||
<script lang="ts">
|
</div>
|
||||||
import { defineComponent, Ref, ref, computed } from 'vue';
|
</div>
|
||||||
import imageMixin from '../../mixins/imageMixin';
|
</template>
|
||||||
|
|
||||||
interface Item {
|
<script lang="ts">
|
||||||
id: string;
|
import { defineComponent, Ref, ref, computed } from 'vue';
|
||||||
value: string;
|
|
||||||
selectedValue?: string;
|
interface Item {
|
||||||
}
|
id: string;
|
||||||
|
value: string;
|
||||||
export default defineComponent({
|
selectedValue?: string;
|
||||||
emits: ['selected'],
|
}
|
||||||
mixins: [imageMixin],
|
|
||||||
|
export default defineComponent({
|
||||||
props: {
|
emits: ['selected'],
|
||||||
itemList: {
|
|
||||||
type: Array as () => Item[],
|
props: {
|
||||||
required: true,
|
itemList: {
|
||||||
},
|
type: Array as () => Item[],
|
||||||
|
required: true
|
||||||
defaultItemIndex: {
|
},
|
||||||
type: Number,
|
|
||||||
default: 0,
|
defaultItemIndex: {
|
||||||
},
|
type: Number,
|
||||||
|
default: 0
|
||||||
prefix: {
|
},
|
||||||
type: String,
|
|
||||||
default: '',
|
prefix: {
|
||||||
},
|
type: String,
|
||||||
},
|
default: ''
|
||||||
|
}
|
||||||
setup(props) {
|
},
|
||||||
let listRef: Ref<Element | null> = ref(null);
|
|
||||||
let buttonRef: Ref<HTMLButtonElement | null> = ref(null);
|
setup(props) {
|
||||||
|
let listRef: Ref<Element | null> = ref(null);
|
||||||
let activeEl: Ref<Element | null> = ref(document.activeElement);
|
let buttonRef: Ref<HTMLButtonElement | null> = ref(null);
|
||||||
|
|
||||||
let listOpen = ref(false);
|
let activeEl: Ref<Element | null> = ref(document.activeElement);
|
||||||
let selectedItem: Ref<Item> = ref(props.itemList[props.defaultItemIndex]);
|
|
||||||
|
let listOpen = ref(false);
|
||||||
const computedSelectedItem = computed(() => {
|
let selectedItem: Ref<Item> = ref(props.itemList[props.defaultItemIndex]);
|
||||||
return props.itemList.find((item) => item.id === selectedItem.value.id) || props.itemList[props.defaultItemIndex];
|
|
||||||
});
|
const computedSelectedItem = computed(() => {
|
||||||
|
return (
|
||||||
return {
|
props.itemList.find((item) => item.id === selectedItem.value.id) ||
|
||||||
computedSelectedItem,
|
props.itemList[props.defaultItemIndex]
|
||||||
listOpen,
|
);
|
||||||
selectedItem,
|
});
|
||||||
listRef,
|
|
||||||
buttonRef,
|
return {
|
||||||
activeEl,
|
computedSelectedItem,
|
||||||
};
|
listOpen,
|
||||||
},
|
selectedItem,
|
||||||
|
listRef,
|
||||||
methods: {
|
buttonRef,
|
||||||
selectOption(item: Item) {
|
activeEl
|
||||||
this.selectedItem = item;
|
};
|
||||||
this.listOpen = false;
|
},
|
||||||
|
|
||||||
this.$emit('selected', item);
|
watch: {
|
||||||
},
|
'$route.query': {
|
||||||
|
immediate: true,
|
||||||
toggleBox(e: Event) {
|
handler(newVal) {
|
||||||
this.listOpen = !this.listOpen;
|
if (newVal.region) {
|
||||||
|
const item = this.itemList.find((it) => it.id == newVal.region);
|
||||||
if (!this.listOpen) (e.target as HTMLButtonElement).blur();
|
|
||||||
},
|
if (item) this.selectedItem = item;
|
||||||
|
}
|
||||||
clickedOutside() {
|
}
|
||||||
this.listOpen = false;
|
}
|
||||||
this.buttonRef?.blur();
|
},
|
||||||
},
|
|
||||||
},
|
methods: {
|
||||||
});
|
selectOption(item: Item) {
|
||||||
</script>
|
this.selectedItem = item;
|
||||||
|
this.listOpen = false;
|
||||||
<style lang="scss" scoped>
|
|
||||||
@import '../../styles/variables.scss';
|
this.$emit('selected', item);
|
||||||
|
},
|
||||||
.unfold {
|
|
||||||
&-enter-from,
|
toggleBox(e: Event) {
|
||||||
&-leave-to {
|
this.listOpen = !this.listOpen;
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(-10px) scale(0.85);
|
if (!this.listOpen) (e.target as HTMLButtonElement).blur();
|
||||||
}
|
},
|
||||||
|
|
||||||
&-enter-active,
|
clickedOutside() {
|
||||||
&-leave-active {
|
this.listOpen = false;
|
||||||
transition: all 110ms ease-out;
|
this.buttonRef?.blur();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
&-enter-active {
|
});
|
||||||
transition-delay: var(--delay-in);
|
</script>
|
||||||
}
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
&-leave-active {
|
@import '../../styles/variables.scss';
|
||||||
transition-delay: var(--delay-out);
|
|
||||||
}
|
.unfold {
|
||||||
}
|
&-enter-from,
|
||||||
|
&-leave-to {
|
||||||
.select-box {
|
opacity: 0;
|
||||||
display: flex;
|
transform: translateY(-10px) scale(0.85);
|
||||||
align-items: center;
|
}
|
||||||
}
|
|
||||||
|
&-enter-active,
|
||||||
.arrow {
|
&-leave-active {
|
||||||
img {
|
transition: all 110ms ease-out;
|
||||||
vertical-align: middle;
|
}
|
||||||
width: 1.35em;
|
|
||||||
}
|
&-enter-active {
|
||||||
}
|
transition-delay: var(--delay-in);
|
||||||
|
}
|
||||||
button.selected {
|
|
||||||
color: paleturquoise;
|
&-leave-active {
|
||||||
|
transition-delay: var(--delay-out);
|
||||||
font-weight: bold;
|
}
|
||||||
padding: 0.1em 0.5em;
|
}
|
||||||
|
|
||||||
&:focus {
|
.select-box {
|
||||||
background-color: #262626;
|
display: flex;
|
||||||
}
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.select-box_content {
|
.arrow {
|
||||||
position: relative;
|
img {
|
||||||
margin: 0 auto;
|
vertical-align: middle;
|
||||||
|
width: 1.35em;
|
||||||
height: 100%;
|
}
|
||||||
|
}
|
||||||
text-align: center;
|
|
||||||
}
|
button.selected {
|
||||||
|
color: paleturquoise;
|
||||||
ul.options {
|
|
||||||
position: absolute;
|
font-weight: bold;
|
||||||
top: 100%;
|
padding: 0.1em 0.5em;
|
||||||
left: 0;
|
|
||||||
|
&:focus {
|
||||||
height: auto;
|
background-color: #262626;
|
||||||
|
}
|
||||||
z-index: 100;
|
}
|
||||||
width: 100%;
|
|
||||||
|
.select-box_content {
|
||||||
font-size: 0.9em;
|
position: relative;
|
||||||
}
|
margin: 0 auto;
|
||||||
|
|
||||||
li.option {
|
height: 100%;
|
||||||
input {
|
|
||||||
position: absolute;
|
text-align: center;
|
||||||
top: 0;
|
}
|
||||||
left: 0;
|
|
||||||
|
ul.options {
|
||||||
-webkit-appearance: none;
|
position: absolute;
|
||||||
-moz-appearance: none;
|
top: 100%;
|
||||||
appearance: none;
|
left: 0;
|
||||||
border: none;
|
|
||||||
outline: none;
|
height: auto;
|
||||||
background: none;
|
|
||||||
|
z-index: 100;
|
||||||
&:focus + span {
|
width: 100%;
|
||||||
color: $accentCol;
|
|
||||||
font-weight: 800;
|
font-size: 0.9em;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
li.option {
|
||||||
&:last-child label {
|
input {
|
||||||
border-radius: 0 0 1em 1em;
|
position: absolute;
|
||||||
}
|
top: 0;
|
||||||
|
left: 0;
|
||||||
label {
|
|
||||||
position: relative;
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
display: inline-block;
|
appearance: none;
|
||||||
background-color: #262626f2;
|
border: none;
|
||||||
|
outline: none;
|
||||||
&:hover,
|
background: none;
|
||||||
&:focus {
|
|
||||||
background-color: #333333f2;
|
&:focus + span {
|
||||||
}
|
color: $accentCol;
|
||||||
|
font-weight: 800;
|
||||||
padding: 0.5em 0;
|
}
|
||||||
|
}
|
||||||
width: 100%;
|
|
||||||
|
&:last-child label {
|
||||||
cursor: pointer;
|
border-radius: 0 0 1em 1em;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
</style>
|
label {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
display: inline-block;
|
||||||
|
background-color: #262626f2;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
background-color: #333333f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
padding: 0.5em 0;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,90 +1,90 @@
|
|||||||
<template>
|
<template>
|
||||||
<span class="status-badge" :class="statusID" v-if="isOnline">
|
<span class="status-badge" :class="statusID" v-if="isOnline">
|
||||||
{{ $t(`status.${statusID}`) }}
|
{{ $t(`status.${statusID}`) }}
|
||||||
{{ statusID == 'online' ? timestampToString(statusTimestamp!) : '' }}
|
{{ statusID == 'online' ? timestampToString(statusTimestamp!) : '' }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="status-badge free" v-else>
|
<span class="status-badge free" v-else>
|
||||||
{{ $t('status.free') }}
|
{{ $t('status.free') }}
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import dateMixin from '../../mixins/dateMixin';
|
import dateMixin from '../../mixins/dateMixin';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
statusID: {
|
statusID: {
|
||||||
type: String,
|
type: String
|
||||||
},
|
},
|
||||||
statusTimestamp: {
|
statusTimestamp: {
|
||||||
type: Number,
|
type: Number
|
||||||
},
|
},
|
||||||
isOnline: {
|
isOnline: {
|
||||||
type: Boolean,
|
type: Boolean
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
mixins: [dateMixin],
|
mixins: [dateMixin]
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
$free: #8a8a8a;
|
$free: #8a8a8a;
|
||||||
$ending: #e6c300;
|
$ending: #e6c300;
|
||||||
$no-limit: #117fc9;
|
$no-limit: #117fc9;
|
||||||
$unav: #ff3d5d;
|
$unav: #ff3d5d;
|
||||||
$brb: #e6a100;
|
$brb: #e6a100;
|
||||||
$no-space: #222;
|
$no-space: #222;
|
||||||
$online: #09a116;
|
$online: #09a116;
|
||||||
$unknown: rgb(185, 60, 60);
|
$unknown: rgb(185, 60, 60);
|
||||||
|
|
||||||
.status-badge {
|
.status-badge {
|
||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
|
||||||
padding: 0.2em 0.55em;
|
padding: 0.2em 0.55em;
|
||||||
|
|
||||||
background-color: $online;
|
background-color: $online;
|
||||||
|
|
||||||
&.free {
|
&.free {
|
||||||
background-color: $free;
|
background-color: $free;
|
||||||
font-size: 0.95em;
|
font-size: 0.95em;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.ending {
|
&.ending {
|
||||||
background-color: $ending;
|
background-color: $ending;
|
||||||
color: black;
|
color: black;
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.no-limit {
|
&.no-limit {
|
||||||
background-color: $no-limit;
|
background-color: $no-limit;
|
||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.not-signed,
|
&.not-signed,
|
||||||
&.unavailable {
|
&.unavailable {
|
||||||
background-color: $unav;
|
background-color: $unav;
|
||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.brb {
|
&.brb {
|
||||||
background-color: $brb;
|
background-color: $brb;
|
||||||
color: black;
|
color: black;
|
||||||
font-size: 0.95em;
|
font-size: 0.95em;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.no-space {
|
&.no-space {
|
||||||
background-color: $no-space;
|
background-color: $no-space;
|
||||||
border: 1px solid white;
|
border: 1px solid white;
|
||||||
color: white;
|
color: white;
|
||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.unknown {
|
&.unknown {
|
||||||
background-color: $unknown;
|
background-color: $unknown;
|
||||||
font-size: 0.95em;
|
font-size: 0.95em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||