Compare commits

..

29 Commits

Author SHA1 Message Date
Spythere 826d51f66c Merge pull request #58 - Wersja 1.18
Wersja 1.18
2023-11-02 01:27:29 +01:00
Spythere 1d7fc2955f animacje userów i spawnów scenerii 2023-11-01 17:20:05 +01:00
Spythere c550e7598a bump 1.18.0 2023-10-31 23:03:51 +01:00
Spythere d5168ce59d lock sync 2023-10-31 22:54:53 +01:00
Spythere 380c97655c filtrowanie statusów; poprawki w statystykach 2023-10-31 22:53:18 +01:00
Spythere e4ed24de80 region query 2023-10-31 02:35:41 +01:00
Spythere 8de03b9210 rework reaktywności danych z API i WS 2023-10-30 23:19:17 +01:00
Spythere 12ece46089 migracja assetów 2023-10-04 17:30:30 +02:00
Spythere 085238fada hotfix: zapamiętywanie stanu statystyk 2023-10-04 15:32:36 +02:00
Spythere 45c1d83512 format & lint 2023-10-04 15:01:01 +02:00
Spythere af9073ab98 Merge do wersji 1.17.1
Wersja 1.17.1
2023-10-03 21:58:23 +02:00
Spythere 800fc35e63 pliki lock 2023-10-03 21:53:59 +02:00
Spythere d5649d221b hotfixy 2023-10-03 21:51:00 +02:00
Spythere 5b35fac512 miniaturki c.d. 2023-10-03 21:31:06 +02:00
Spythere d5bc90f668 mock data 2023-10-02 22:09:27 +02:00
Spythere 6d663886f0 bump wersji 2023-10-02 22:06:58 +02:00
Spythere 85a1a0216e poprawki miniatur 2023-10-02 22:05:54 +02:00
Spythere 4ac054e947 miniaturki 2EN57 2023-10-01 15:11:53 +02:00
Spythere ba70fa1316 miniaturki pojazdów c.d. 2023-10-01 15:08:01 +02:00
Spythere 77e6b20d0c obsługa niezaładowanych miniaturek pojazdów 2023-10-01 01:41:14 +02:00
Spythere f60263c923 mocking danych 2023-10-01 00:23:17 +02:00
Spythere 6aec1a75c9 cleanup c.d. 2023-09-29 16:49:37 +02:00
Spythere d28d600833 code cleanup dziennika 2023-09-29 03:03:59 +02:00
Spythere a353eb3185 Meta tagi; aktualizacja paczek
Meta tagi; aktualizacja paczek
2023-09-26 14:44:48 +02:00
Spythere c5735a6953 pliki lock 2023-09-26 14:42:11 +02:00
Spythere 7930f7fc8a package.json 2023-09-26 14:36:01 +02:00
Spythere 68f4d54619 index: meta tagi 2023-09-26 14:33:58 +02:00
Spythere c4f9738589 Merge #54 (hotfix)
hotfix: wersja WS
2023-09-07 15:21:28 +02:00
Spythere dd916afd1d hotfix: wersja WS 2023-09-07 15:20:37 +02:00
214 changed files with 36767 additions and 17171 deletions
+18
View File
@@ -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'
}
}
+1
View File
@@ -31,6 +31,7 @@ node_modules
.firebase .firebase
.firebaserc .firebaserc
# Env
.env .env
.fake .fake
+7
View File
@@ -0,0 +1,7 @@
{
"$schema": "https://json.schemastore.org/prettierrc",
"tabWidth": 2,
"singleQuote": true,
"printWidth": 100,
"trailingComma": "none"
}
+18 -3
View File
@@ -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>
+6581 -2901
View File
File diff suppressed because it is too large Load Diff
+30 -19
View File
@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 932 B

Binary file not shown.

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 799 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

-3
View File
@@ -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

+106 -105
View File
@@ -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;
}
+158 -178
View File
@@ -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">
&copy;
<footer class="app_footer"> <a href="https://td2.info.pl/profile/?u=20777" target="_blank">Spythere</a>
&copy; {{ 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="" />&nbsp;<b>{{ $t('footer.discord') }}</b>
<a href="https://discord.gg/x2mpNN3svk"><img :src="getIcon('discord', 'png')" alt="">&nbsp;<b>{{ $t('footer.discord') }}</b></a> </a>
<div style="display: none">&int; ukryta taktyczna całka do programowania w HTMLu</div> <div style="display: none">&int; 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>
+249 -237
View File
@@ -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>
+37 -36
View File
@@ -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>
+45 -14
View File
@@ -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>
-168
View File
@@ -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>
-69
View File
@@ -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>
+24 -24
View File
@@ -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>
+42
View File
@@ -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>
+1 -1
View File
@@ -12,7 +12,7 @@ import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
setup() { setup() {
return {}; return {};
}, }
}); });
</script> </script>
+65 -62
View File
@@ -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>
+17 -25
View File
@@ -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>
+235 -217
View File
@@ -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>
+90 -90
View File
@@ -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>

Some files were not shown because too many files have changed in this diff Show More