mirror of
https://github.com/Spythere/srjp-td2.git
synced 2026-05-03 21:48:13 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0e888544c1 |
@@ -5,7 +5,7 @@ name: Deploy to Firebase Hosting on merge
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main-old
|
- main
|
||||||
jobs:
|
jobs:
|
||||||
build_and_deploy:
|
build_and_deploy:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
on:
|
|
||||||
release:
|
|
||||||
types: [published]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
github-releases-to-discord:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
- name: Github Releases To Discord
|
|
||||||
uses: SethCohen/github-releases-to-discord@v1.13.1
|
|
||||||
with:
|
|
||||||
webhook_url: '${{ secrets.WEBHOOK_URL }}'
|
|
||||||
color: '10038562'
|
|
||||||
footer_title: 'Changelog - Rozkładownik SRJP'
|
|
||||||
footer_timestamp: true
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
name: Build & Deploy to VPS
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
env:
|
|
||||||
PROJECT_NAME: srjp-td2
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build_and_deploy:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- name: Build the app
|
|
||||||
run: yarn && yarn build
|
|
||||||
- name: Setup SSH key for connection with the server
|
|
||||||
run: |
|
|
||||||
mkdir -p ~/.ssh
|
|
||||||
echo "${{ secrets.VPS_SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa && chmod 600 ~/.ssh/id_rsa
|
|
||||||
- name: Send new files
|
|
||||||
run: rsync -avP -e "ssh -o StrictHostKeyChecking=no -i ~/.ssh/id_rsa -p 2022" ./dist/ ${{ secrets.VPS_USER }}@${{ secrets.VPS_HOST }}:/var/www/$PROJECT_NAME --delete
|
|
||||||
@@ -13,7 +13,6 @@ dist-ssr
|
|||||||
*.local
|
*.local
|
||||||
.env
|
.env
|
||||||
.env.*
|
.env.*
|
||||||
/dev-dist
|
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
.vscode/*
|
.vscode/*
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://json.schemastore.org/prettierrc",
|
|
||||||
"tabWidth": 2,
|
|
||||||
"singleQuote": true,
|
|
||||||
"printWidth": 100,
|
|
||||||
"trailingComma": "none"
|
|
||||||
}
|
|
||||||
@@ -19,7 +19,7 @@ async function main() {
|
|||||||
if (!existsSync('endpoints')) await mkdir('endpoints');
|
if (!existsSync('endpoints')) await mkdir('endpoints');
|
||||||
|
|
||||||
Promise.all(
|
Promise.all(
|
||||||
['getActiveData', 'getDonators', 'getSceneries', 'getVehiclesData'].map((endpointName) =>
|
['getActiveData', 'getDonators', 'getSceneries', 'getVehicles'].map((endpointName) =>
|
||||||
fetchJSONEndpointData(
|
fetchJSONEndpointData(
|
||||||
`https://stacjownik.spythere.eu/api/${endpointName}`,
|
`https://stacjownik.spythere.eu/api/${endpointName}`,
|
||||||
`${endpointName}.json`
|
`${endpointName}.json`
|
||||||
|
|||||||
+2
-2
@@ -15,8 +15,8 @@ app.get('/api/getSceneries', (_, res) => {
|
|||||||
res.sendFile(path.join(cwd(), 'endpoints', 'getSceneries.json'));
|
res.sendFile(path.join(cwd(), 'endpoints', 'getSceneries.json'));
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/api/getVehiclesData', (_, res) => {
|
app.get('/api/getVehicles', (_, res) => {
|
||||||
res.sendFile(path.join(cwd(), 'endpoints', 'getVehiclesData.json'));
|
res.sendFile(path.join(cwd(), 'endpoints', 'getVehicles.json'));
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/api/getDonators', (_, res) => {
|
app.get('/api/getDonators', (_, res) => {
|
||||||
|
|||||||
@@ -1,113 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Logger Function
|
|
||||||
log() {
|
|
||||||
local message="$1"
|
|
||||||
local type="$2"
|
|
||||||
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
|
||||||
local color
|
|
||||||
local endcolor="\033[0m"
|
|
||||||
|
|
||||||
case "$type" in
|
|
||||||
"info") color="\033[38;5;79m" ;;
|
|
||||||
"success") color="\033[1;32m" ;;
|
|
||||||
"error") color="\033[1;31m" ;;
|
|
||||||
*) color="\033[1;34m" ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
echo -e "${color}${timestamp} - ${message}${endcolor}"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Error handler function
|
|
||||||
handle_error() {
|
|
||||||
local exit_code=$1
|
|
||||||
local error_message="$2"
|
|
||||||
log "Error: $error_message (Exit Code: $exit_code)" "error"
|
|
||||||
exit $exit_code
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function to check for command availability
|
|
||||||
command_exists() {
|
|
||||||
command -v "$1" &> /dev/null
|
|
||||||
}
|
|
||||||
|
|
||||||
check_os() {
|
|
||||||
if ! [ -f "/etc/debian_version" ]; then
|
|
||||||
echo "Error: This script is only supported on Debian-based systems."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function to Install the script pre-requisites
|
|
||||||
install_pre_reqs() {
|
|
||||||
log "Installing pre-requisites" "info"
|
|
||||||
|
|
||||||
# Run 'apt-get update'
|
|
||||||
if ! apt-get update -y; then
|
|
||||||
handle_error "$?" "Failed to run 'apt-get update'"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Run 'apt-get install'
|
|
||||||
if ! apt-get install -y apt-transport-https ca-certificates curl gnupg; then
|
|
||||||
handle_error "$?" "Failed to install packages"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! mkdir -p /usr/share/keyrings; then
|
|
||||||
handle_error "$?" "Makes sure the path /usr/share/keyrings exist or run ' mkdir -p /usr/share/keyrings' with sudo"
|
|
||||||
fi
|
|
||||||
|
|
||||||
rm -f /usr/share/keyrings/nodesource.gpg || true
|
|
||||||
rm -f /etc/apt/sources.list.d/nodesource.list || true
|
|
||||||
|
|
||||||
# Run 'curl' and 'gpg' to download and import the NodeSource signing key
|
|
||||||
if ! curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /usr/share/keyrings/nodesource.gpg; then
|
|
||||||
handle_error "$?" "Failed to download and import the NodeSource signing key"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Explicitly set the permissions to ensure the file is readable by all
|
|
||||||
if ! chmod 644 /usr/share/keyrings/nodesource.gpg; then
|
|
||||||
handle_error "$?" "Failed to set correct permissions on /usr/share/keyrings/nodesource.gpg"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function to configure the Repo
|
|
||||||
configure_repo() {
|
|
||||||
local node_version=$1
|
|
||||||
|
|
||||||
arch=$(dpkg --print-architecture)
|
|
||||||
if [ "$arch" != "amd64" ] && [ "$arch" != "arm64" ] && [ "$arch" != "armhf" ]; then
|
|
||||||
handle_error "1" "Unsupported architecture: $arch. Only amd64, arm64, and armhf are supported."
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "deb [arch=$arch signed-by=/usr/share/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$node_version nodistro main" | tee /etc/apt/sources.list.d/nodesource.list > /dev/null
|
|
||||||
|
|
||||||
# N|solid Config
|
|
||||||
echo "Package: nsolid" | tee /etc/apt/preferences.d/nsolid > /dev/null
|
|
||||||
echo "Pin: origin deb.nodesource.com" | tee -a /etc/apt/preferences.d/nsolid > /dev/null
|
|
||||||
echo "Pin-Priority: 600" | tee -a /etc/apt/preferences.d/nsolid > /dev/null
|
|
||||||
|
|
||||||
# Nodejs Config
|
|
||||||
echo "Package: nodejs" | tee /etc/apt/preferences.d/nodejs > /dev/null
|
|
||||||
echo "Pin: origin deb.nodesource.com" | tee -a /etc/apt/preferences.d/nodejs > /dev/null
|
|
||||||
echo "Pin-Priority: 600" | tee -a /etc/apt/preferences.d/nodejs > /dev/null
|
|
||||||
|
|
||||||
# Run 'apt-get update'
|
|
||||||
if ! apt-get update -y; then
|
|
||||||
handle_error "$?" "Failed to run 'apt-get update'"
|
|
||||||
else
|
|
||||||
log "Repository configured successfully."
|
|
||||||
log "To install Node.js, run: apt-get install nodejs -y" "info"
|
|
||||||
log "You can use N|solid Runtime as a node.js alternative" "info"
|
|
||||||
log "To install N|solid Runtime, run: apt-get install nsolid -y \n" "success"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Define Node.js version
|
|
||||||
NODE_VERSION="23.x"
|
|
||||||
|
|
||||||
# Check OS
|
|
||||||
check_os
|
|
||||||
|
|
||||||
# Main execution
|
|
||||||
install_pre_reqs || handle_error $? "Failed installing pre-requisites"
|
|
||||||
configure_repo "$NODE_VERSION" || handle_error $? "Failed configuring repository"
|
|
||||||
+11
-11
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "srjp-td2",
|
"name": "srjp-td2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.1.3",
|
"version": "1.0.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --mode staging",
|
"dev": "vite --mode staging",
|
||||||
@@ -11,20 +11,20 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lucide-vue-next": "^0.577.0",
|
"@heroicons/vue": "^2.2.0",
|
||||||
"pinia": "^3.0.4",
|
"axios": "^1.7.9",
|
||||||
"vue": "^3.5.30",
|
"pinia": "^2.3.1",
|
||||||
"vue-i18n": "11.3.0"
|
"vue": "^3.5.13",
|
||||||
|
"vue-i18n": "10"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^6.0.4",
|
"@vitejs/plugin-vue": "^5.2.1",
|
||||||
"@vue/tsconfig": "^0.9.0",
|
"@vue/tsconfig": "^0.7.0",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"postcss": "^8.5.1",
|
"postcss": "^8.5.1",
|
||||||
"tailwindcss": "^3.4.17",
|
"tailwindcss": "^3.4.17",
|
||||||
"typescript": "~5.9.3",
|
"typescript": "~5.6.2",
|
||||||
"vite": "^7.3.1",
|
"vite": "^6.0.5",
|
||||||
"vite-plugin-pwa": "^1.2.0",
|
"vue-tsc": "^2.2.0"
|
||||||
"vue-tsc": "^3.2.5"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
<svg width="50" height="30" viewBox="0 0 50 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M25 30L0 0H50L25 30Z" fill="white"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 149 B |
@@ -5,16 +5,17 @@
|
|||||||
{
|
{
|
||||||
"src": "/web-app-manifest-192x192.png",
|
"src": "/web-app-manifest-192x192.png",
|
||||||
"sizes": "192x192",
|
"sizes": "192x192",
|
||||||
"type": "image/png"
|
"type": "image/png",
|
||||||
|
"purpose": "maskable"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "/web-app-manifest-512x512.png",
|
"src": "/web-app-manifest-512x512.png",
|
||||||
"sizes": "512x512",
|
"sizes": "512x512",
|
||||||
"type": "image/png"
|
"type": "image/png",
|
||||||
|
"purpose": "maskable"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"theme_color": "#ffffff",
|
"theme_color": "#ffffff",
|
||||||
"background_color": "#151414",
|
"background_color": "#151414",
|
||||||
"display": "standalone",
|
"display": "standalone"
|
||||||
"start_url": "."
|
}
|
||||||
}
|
|
||||||
+16
-79
@@ -1,34 +1,17 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="text-white min-h-screen bg-zinc-950 print:bg-white">
|
<div class="text-white min-h-screen bg-zinc-950">
|
||||||
<!-- PWA update prompt -->
|
<Navbar />
|
||||||
<transition name="slide-anim">
|
|
||||||
<UpdatePrompt v-if="needRefresh" @onUpdateClick="updateApp()" />
|
|
||||||
</transition>
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Content -->
|
|
||||||
<Navbar v-if="!globalStore.fullscreenMode" />
|
|
||||||
<MainContainer />
|
<MainContainer />
|
||||||
|
|
||||||
<!-- Migrate Info -->
|
|
||||||
<transition name="slide-anim">
|
|
||||||
<MigrateInfo v-if="globalStore.isMigrationInfoOpen" />
|
|
||||||
</transition>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import Navbar from './components/App/Navbar.vue';
|
import Navbar from './components/App/Navbar.vue';
|
||||||
import MainContainer from './components/App/MainContainer.vue';
|
import MainContainer from './components/App/MainContainer.vue';
|
||||||
import UpdatePrompt from './components/App/UpdatePrompt.vue';
|
|
||||||
|
|
||||||
import { onMounted } from 'vue';
|
import { onMounted } from 'vue';
|
||||||
import { useApiStore } from './stores/api.store';
|
import { useApiStore } from './stores/api.store';
|
||||||
import { useGlobalStore } from './stores/global.store';
|
import { useGlobalStore } from './stores/global.store';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useRegisterSW } from 'virtual:pwa-register/vue';
|
|
||||||
import { DataStatus } from './types/api.types';
|
|
||||||
import MigrateInfo from './components/App/MigrateInfo.vue';
|
|
||||||
|
|
||||||
const originalDocumentTitle = document.title;
|
const originalDocumentTitle = document.title;
|
||||||
|
|
||||||
@@ -36,24 +19,27 @@ const apiStore = useApiStore();
|
|||||||
const globalStore = useGlobalStore();
|
const globalStore = useGlobalStore();
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
|
|
||||||
const { needRefresh, updateServiceWorker } = useRegisterSW({ immediate: true });
|
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
setupLocale();
|
setupLocale();
|
||||||
setupDarkMode();
|
setupDarkMode();
|
||||||
handleMigrationInfo();
|
|
||||||
setupOfflineMode();
|
|
||||||
loadStorageTimetables();
|
loadStorageTimetables();
|
||||||
setupAfterPrintClose();
|
setupAfterPrintClose();
|
||||||
|
|
||||||
await apiStore.setupAPIData();
|
await apiStore.setupAPIData();
|
||||||
handleQueries();
|
|
||||||
});
|
|
||||||
|
|
||||||
function updateApp() {
|
const query = new URLSearchParams(window.location.search);
|
||||||
updateServiceWorker(true);
|
|
||||||
needRefresh.value = false;
|
if (query.has('id')) {
|
||||||
}
|
const id = query.get('id')!;
|
||||||
|
|
||||||
|
const queryTrain = apiStore.activeData?.trains.find((train) => train.id == id);
|
||||||
|
|
||||||
|
if (queryTrain) {
|
||||||
|
globalStore.selectedTrainId = id;
|
||||||
|
globalStore.selectedActiveTrain = queryTrain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
function loadStorageTimetables() {
|
function loadStorageTimetables() {
|
||||||
if (!window.localStorage.getItem('savedTimetables')) return;
|
if (!window.localStorage.getItem('savedTimetables')) return;
|
||||||
@@ -67,9 +53,7 @@ function loadStorageTimetables() {
|
|||||||
|
|
||||||
function setupDarkMode() {
|
function setupDarkMode() {
|
||||||
globalStore.darkMode =
|
globalStore.darkMode =
|
||||||
localStorage.currentTheme === 'dark' ||
|
localStorage.currentTheme === 'dark' || (!('currentTheme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
||||||
(!('currentTheme' in localStorage) &&
|
|
||||||
window.matchMedia('(prefers-color-scheme: dark)').matches);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupAfterPrintClose() {
|
function setupAfterPrintClose() {
|
||||||
@@ -87,51 +71,4 @@ function setupLocale() {
|
|||||||
i18n.locale.value = window.localStorage.getItem('locale')!;
|
i18n.locale.value = window.localStorage.getItem('locale')!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupOfflineMode() {
|
|
||||||
apiStore.connectionMode = !navigator.onLine ? 'offline' : 'online';
|
|
||||||
|
|
||||||
window.addEventListener('offline', () => {
|
|
||||||
apiStore.connectionMode = 'offline';
|
|
||||||
|
|
||||||
apiStore.journalTimetablesData = null;
|
|
||||||
apiStore.activeData = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
window.addEventListener('online', () => {
|
|
||||||
apiStore.connectionMode = 'online';
|
|
||||||
apiStore.journalDataStatus = DataStatus.SUCCESS;
|
|
||||||
|
|
||||||
apiStore.setupAPIData();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleQueries() {
|
|
||||||
const query = new URLSearchParams(window.location.search);
|
|
||||||
|
|
||||||
if (query.has('id')) {
|
|
||||||
const id = query.get('id')!;
|
|
||||||
|
|
||||||
const queryTrain = apiStore.activeData?.trains.find((train) => train.id == id);
|
|
||||||
|
|
||||||
if (queryTrain) {
|
|
||||||
globalStore.selectedTrainId = id;
|
|
||||||
globalStore.selectedActiveTrain = queryTrain;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleMigrationInfo() {
|
|
||||||
// Show only on old domain
|
|
||||||
if (location.hostname !== 'srjp-td2.web.app' && location.hostname != 'localhost') return;
|
|
||||||
|
|
||||||
const showInfo = localStorage.getItem('showMigrationInfo');
|
|
||||||
|
|
||||||
// Do not show if already acknowledged
|
|
||||||
if (showInfo === 'false') return;
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
globalStore.isMigrationInfoOpen = true;
|
|
||||||
}, 2000);
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 496 B |
@@ -1,22 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<main
|
<main class="grid print:block p-3 mx-auto max-w-[800px] h-screen grid-rows-[auto_auto_1fr] gap-1">
|
||||||
class="grid print:block print:bg-white p-3 mx-auto max-w-[800px] min-h-[300px] gap-1 relative"
|
<TimetableSelect />
|
||||||
:class="{
|
<TimetableWarnings />
|
||||||
'grid-rows-[auto_auto_1fr] h-[calc(100vh-40px)]': !globalStore.fullscreenMode,
|
<TrainTimetable />
|
||||||
'grid-rows-[1fr] h-screen': globalStore.fullscreenMode
|
<!-- <MainBottom /> -->
|
||||||
}"
|
|
||||||
>
|
|
||||||
<SearchContainer v-if="!globalStore.fullscreenMode" />
|
|
||||||
<TimetableWarnings v-if="!globalStore.fullscreenMode" />
|
|
||||||
<TimetableContainer />
|
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useGlobalStore } from '../../stores/global.store';
|
import TimetableSelect from '../Timetable/TimetableSelect.vue';
|
||||||
import TimetableContainer from '../Timetable/TimetableContainer.vue';
|
import TimetableWarnings from "../Timetable/TimetableWarnings.vue";
|
||||||
import TimetableWarnings from '../Timetable/TimetableWarnings.vue';
|
import TrainTimetable from '../Timetable/TrainTimetable.vue';
|
||||||
import SearchContainer from '../TimetableSearch/SearchContainer.vue';
|
// import MainBottom from './MainBottom.vue';
|
||||||
|
|
||||||
const globalStore = useGlobalStore();
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="fixed z-10 bottom-0 left-0 p-1 text-center w-full bg-yellow-400 text-black font-bold">
|
|
||||||
<div class="flex justify-center items-center flex-wrap gap-2">
|
|
||||||
<i18n-t keypath="migrate-info.line-1" for="migrate-info" tag="div">
|
|
||||||
<a href="https://srjp-td2.spythere.eu/" target="_blank" class="underline">
|
|
||||||
https://srjp-td2.spythere.eu/
|
|
||||||
</a>
|
|
||||||
</i18n-t>
|
|
||||||
<button
|
|
||||||
class="p-1 bg-zinc-700 text-white rounded-md hover:bg-zinc-500 focus-visible:bg-zinc-500"
|
|
||||||
@click="onAcceptButtonClick"
|
|
||||||
>
|
|
||||||
{{ t('migrate-info.accept-btn') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { useI18n } from 'vue-i18n';
|
|
||||||
import { useGlobalStore } from '../../stores/global.store';
|
|
||||||
|
|
||||||
const store = useGlobalStore();
|
|
||||||
const { t } = useI18n();
|
|
||||||
|
|
||||||
function onAcceptButtonClick() {
|
|
||||||
store.isMigrationInfoOpen = false;
|
|
||||||
localStorage.setItem('showMigrationInfo', 'false');
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,21 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<nav class="bg-zinc-900 w-full p-1 print:hidden flex justify-between items-center relative">
|
<nav class="bg-zinc-900 w-full p-1 print:hidden flex justify-between items-center relative">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<img src="/favicon.svg" class="size-8 inline" alt="SRJP logo" />
|
<img src="/favicon.svg" class="size-8 inline" />
|
||||||
<b class="ml-2 text-lg">
|
<b class="ml-2 text-lg"
|
||||||
Rozkładownik TD2
|
>Rozkładownik TD2 <sup class="font-semibold text-zinc-300">{{ version }}</sup></b
|
||||||
<sup class="font-semibold text-zinc-300">
|
>
|
||||||
<a :href="releaseHref" target="_blank">v{{ version }}</a>
|
|
||||||
</sup>
|
|
||||||
</b>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<button
|
<button class="bg-slate-600 p-1 px-2 rounded-md hover:bg-slate-500 flex items-center" @click="changeLang()">
|
||||||
class="bg-slate-600 p-1 px-2 rounded-md hover:bg-slate-500 flex items-center"
|
<LanguageIcon class="size-5 inline-block align-middle mr-2" /> {{ i18n.locale.value == 'pl' ? 'POL' : 'ENG' }}
|
||||||
@click="changeLang()"
|
|
||||||
>
|
|
||||||
<GlobeIcon :size="18" class="mr-2" /> {{ i18n.locale.value == 'pl' ? 'POL' : 'ENG' }}
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<!-- <div v-if="apiMode == 'mocking'"><ExclamationTriangleIcon class="size-6 inline mr-1 text-yellow-400" /> API mocking</div> -->
|
<!-- <div v-if="apiMode == 'mocking'"><ExclamationTriangleIcon class="size-6 inline mr-1 text-yellow-400" /> API mocking</div> -->
|
||||||
@@ -23,18 +17,15 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { GlobeIcon } from 'lucide-vue-next';
|
import { LanguageIcon } from '@heroicons/vue/16/solid';
|
||||||
import { version } from '../../../package.json';
|
import { version } from '../../../package.json';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { computed } from 'vue';
|
|
||||||
|
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
|
|
||||||
function changeLang(locale?: string) {
|
function changeLang(locale?: string) {
|
||||||
i18n.locale.value = (locale ?? i18n.locale.value == 'pl') ? 'en' : 'pl';
|
i18n.locale.value = locale ?? i18n.locale.value == 'pl' ? 'en' : 'pl';
|
||||||
window.localStorage.setItem('locale', i18n.locale.value);
|
window.localStorage.setItem('locale', i18n.locale.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
const releaseHref = computed(() => `https://github.com/Spythere/srjp-td2/releases/tag/v${version}`);
|
|
||||||
// const apiMode = import.meta.env.VITE_API_MODE;
|
// const apiMode = import.meta.env.VITE_API_MODE;
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="fixed z-50 bottom-0 right-0">
|
|
||||||
<button
|
|
||||||
@click="onUpdateClick"
|
|
||||||
class="p-3 m-3 bg-cyan-600 rounded-md text-xl hover:scale-105 transition-transform"
|
|
||||||
ref="updateBtnEl"
|
|
||||||
>
|
|
||||||
<div>{{ $t('update-prompt.line1') }}</div>
|
|
||||||
<u>{{ $t('update-prompt.line2') }}</u>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { onMounted } from 'vue';
|
|
||||||
import { ref } from 'vue';
|
|
||||||
|
|
||||||
const emit = defineEmits(['onUpdateClick']);
|
|
||||||
const updateBtnEl = ref<HTMLElement | null>(null);
|
|
||||||
|
|
||||||
function onUpdateClick() {
|
|
||||||
emit('onUpdateClick');
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
updateBtnEl.value?.focus();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@@ -0,0 +1,188 @@
|
|||||||
|
<template>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="(row, i) in computedTimetable">
|
||||||
|
<td
|
||||||
|
class="text-center align-top border-l border-l-black dark:border-l-white"
|
||||||
|
:class="{
|
||||||
|
'border-t border-t-black dark:border-t-white': i != 0 && computedTimetable[i - 1].realLine != row.realLine,
|
||||||
|
'border-b border-b-black dark:border-b-white': i == computedTimetable.length - 1,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ i == 0 || computedTimetable[i - 1].realLine != row.realLine ? row.realLine : ' ' }}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td class="border border-black dark:border-white relative">
|
||||||
|
<div class="absolute top-0 left-0 w-full h-full p-0.5">
|
||||||
|
<table class="h-full w-full border-collapse">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="align-top">{{ row.arrivalKm == '0.000' ? '' : row.arrivalKm }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="align-bottom">{{ row.departureKm == '0.000' ? '' : row.departureKm }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td
|
||||||
|
class="text-center align-top p-0 border-l-black dark:border-l-white relative"
|
||||||
|
:class="{
|
||||||
|
'border-t border-t-black dark:border-t-white': i != 0 && computedTimetable[i - 1].departureSpeed != row.arrivalSpeed,
|
||||||
|
'border-b border-b-black dark:border-b-white': i == computedTimetable.length - 1,
|
||||||
|
}"
|
||||||
|
colspan="2"
|
||||||
|
>
|
||||||
|
<div class="absolute top-0 left-0 w-full h-full">
|
||||||
|
<table class="h-full w-full border-collapse">
|
||||||
|
<tbody>
|
||||||
|
<tr class="align-top">
|
||||||
|
<td :colspan="row.arrivalTracks == 2 ? '1' : '2'" class="font-bold" width="35">
|
||||||
|
{{
|
||||||
|
i == 0 ||
|
||||||
|
computedTimetable[i - 1].departureSpeed != row.arrivalSpeed ||
|
||||||
|
computedTimetable[i - 1].departureTracks != row.arrivalTracks
|
||||||
|
? row.arrivalSpeed
|
||||||
|
: ' '
|
||||||
|
}}
|
||||||
|
</td>
|
||||||
|
<td v-if="row.arrivalTracks == 2" class="border-l border-l-black dark:border-l-white" width="35">
|
||||||
|
{{
|
||||||
|
i == 0 ||
|
||||||
|
computedTimetable[i - 1].departureSpeed != row.arrivalSpeed ||
|
||||||
|
computedTimetable[i - 1].departureTracks != row.arrivalTracks
|
||||||
|
? row.arrivalSpeed
|
||||||
|
: ' '
|
||||||
|
}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr
|
||||||
|
:class="{
|
||||||
|
'border-t border-t-black dark:border-t-white align-top':
|
||||||
|
row.arrivalTracks != row.departureTracks || row.departureSpeed != row.arrivalSpeed,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<td :colspan="row.departureTracks == 2 ? '1' : '2'" class="font-bold" width="35">
|
||||||
|
{{ row.departureSpeed != row.arrivalSpeed || row.departureTracks != row.arrivalTracks ? row.departureSpeed : ' ' }}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td v-if="row.departureTracks == 2" class="border-l border-l-black dark:border-l-white" width="35">
|
||||||
|
{{ row.departureSpeed != row.arrivalSpeed || row.departureTracks != row.arrivalTracks ? row.departureSpeed : ' ' }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td class="border border-black dark:border-white relative">
|
||||||
|
<div class="absolute top-0 left-0 w-full h-full">
|
||||||
|
<div class="flex flex-col h-full justify-between p-1">
|
||||||
|
<div :class="{ 'font-bold': row.isMain }">
|
||||||
|
{{ row.pointName }}
|
||||||
|
<span v-if="row.stopTime"> ; {{ row.stopType || 'pt' }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span>{{ row.pointKm }}</span>
|
||||||
|
<span>{{ row.abbrevs.join(', ') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td class="p-0 border border-black dark:border-white relative">
|
||||||
|
<div class="absolute top-0 left-0 w-full h-full">
|
||||||
|
<table class="h-full w-full border-collapse">
|
||||||
|
<tbody>
|
||||||
|
<tr class="text-center align-top h-full">
|
||||||
|
<td class="border-r-[1px] border-r-black dark:border-r-white" :class="{ 'font-bold': row.stopTime > 0 }">
|
||||||
|
{{
|
||||||
|
(row.scheduledArrivalDate?.getTime() || 0) != (row.scheduledDepartureDate?.getTime() || 0)
|
||||||
|
? row.scheduledArrivalDate?.toLocaleTimeString('pl-PL', { hour: '2-digit', minute: '2-digit' })
|
||||||
|
: '|'
|
||||||
|
}}
|
||||||
|
</td>
|
||||||
|
<td width="30">{{ row.driveTime ? Math.floor(row.driveTime / 60000) : '' }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="text-center align-bottom h-full">
|
||||||
|
<td class="border-r-[1px] border-r-black dark:border-r-white" :class="{ 'font-bold': row.stopTime > 0 }">
|
||||||
|
{{ row.scheduledDepartureDate?.toLocaleTimeString('pl-PL', { hour: '2-digit', minute: '2-digit' }) }}
|
||||||
|
</td>
|
||||||
|
<td width="30" class="font-bold">{{ row.stopTime || '' }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td class="p-0 text-center border border-black dark:border-white relative h-24 text-sm" :class="{ 'text-stone-400 ': i > 0 }">
|
||||||
|
<table class="h-full w-full border-collapse">
|
||||||
|
<tbody>
|
||||||
|
<tr class="border-b-[1px] border-b-black dark:border-b-white">
|
||||||
|
<td>{{ row.headUnits[0] }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="border-b-[1px] border-b-black dark:border-b-white">
|
||||||
|
<td>{{ row.headUnits[1] ?? ' ' }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ row.headUnits[2] ?? ' ' }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td class="p-0 text-center border border-black dark:border-white relative" :class="{ 'text-stone-400 ': i > 0 }">
|
||||||
|
<div class="absolute top-0 left-0 w-full h-full">
|
||||||
|
<table class="h-full w-full border-collapse">
|
||||||
|
<tbody>
|
||||||
|
<tr class="border-b-[1px] border-b-black dark:border-b-white">
|
||||||
|
<td>{{ row.stockMass }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ row.stockLength }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td class="text-center border border-black dark:border-white" :class="{ 'text-stone-400 ': i > 0 }">{{ row.stockVmax }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { PropType } from 'vue';
|
||||||
|
import type { StopRow } from '../../types/common.types';
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
computedTimetable: {
|
||||||
|
type: Object as PropType<StopRow[]>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
@media print {
|
||||||
|
table {
|
||||||
|
page-break-after: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr {
|
||||||
|
page-break-inside: avoid;
|
||||||
|
page-break-after: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
thead {
|
||||||
|
display: table-header-group;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr,
|
||||||
|
td {
|
||||||
|
border-color: theme('colors.black');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
<template>
|
|
||||||
<!-- Button closing fullscreen mode, relative to MainContainer -->
|
|
||||||
<button
|
|
||||||
v-if="globalStore.fullscreenMode"
|
|
||||||
class="absolute right-6 top-3 p-1 rounded-md bg-green-600 hover:bg-green-500 print:hidden z-50"
|
|
||||||
@click="() => (globalStore.fullscreenMode = false)"
|
|
||||||
>
|
|
||||||
<Minimize2Icon :size="22" />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- If there is no timetable chosen -->
|
|
||||||
<div
|
|
||||||
class="overflow-auto text-center font-bold text-zinc-400 p-2 min-h-full"
|
|
||||||
v-if="globalStore.currentTimetableData == null"
|
|
||||||
>
|
|
||||||
<component :is="viewModes[globalStore.viewMode]" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="overflow-auto text-center font-bold text-white p-2 min-h-full"
|
|
||||||
v-else-if="apiStore.apiDataStatus == DataStatus.LOADING"
|
|
||||||
>
|
|
||||||
Pobieranie danych...
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="overflow-auto text-center font-bold text-red-500 p-2 min-h-full"
|
|
||||||
v-else-if="apiStore.apiDataStatus == DataStatus.ERROR"
|
|
||||||
>
|
|
||||||
Ups! Coś poszło nie tak przy pobieraniu danych! :/
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Timetable render based on current view mode -->
|
|
||||||
<CurrentTimetableView v-else />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { useGlobalStore } from '../../stores/global.store';
|
|
||||||
import LocalStorageView from '../TimetableViews/LocalStorageView.vue';
|
|
||||||
import JournalStorageView from '../TimetableViews/JournalStorageView.vue';
|
|
||||||
import ActiveDataView from '../TimetableViews/ActiveDataView.vue';
|
|
||||||
import CurrentTimetableView from '../TimetableViews/CurrentTimetableView.vue';
|
|
||||||
import { Minimize2Icon } from 'lucide-vue-next';
|
|
||||||
import { useApiStore } from '../../stores/api.store';
|
|
||||||
import { DataStatus } from '../../types/api.types';
|
|
||||||
|
|
||||||
const globalStore = useGlobalStore();
|
|
||||||
const apiStore = useApiStore();
|
|
||||||
|
|
||||||
const viewModes: Record<typeof globalStore.viewMode, any> = {
|
|
||||||
active: ActiveDataView,
|
|
||||||
storage: LocalStorageView,
|
|
||||||
journal: JournalStorageView
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
@@ -1,756 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<h2 class="p-1 font-bold w-max">
|
|
||||||
{{ globalStore.currentTimetableData!.category }}
|
|
||||||
{{ globalStore.currentTimetableData!.trainNo }} {{ $t('headers.relation') }}
|
|
||||||
{{ globalStore.currentTimetableData!.route.replace('|', ' - ') }}
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<table
|
|
||||||
class="table-fixed mt-2 w-full border-collapse overflow-hidden"
|
|
||||||
v-if="computedTimetableRows.length > 0"
|
|
||||||
>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th width="40" class="border border-black dark:border-white">
|
|
||||||
{{ $t('headers.line_no') }}
|
|
||||||
</th>
|
|
||||||
<th width="100" class="border border-black dark:border-white">
|
|
||||||
{{ $t('headers.line_km') }}
|
|
||||||
</th>
|
|
||||||
<th width="42" class="border border-black dark:border-white">V<sub>P</sub></th>
|
|
||||||
<th width="40" class="border border-black dark:border-white">V<sub>L</sub></th>
|
|
||||||
<th width="200" class="border border-black dark:border-white">
|
|
||||||
{{ $t('headers.station') }}
|
|
||||||
</th>
|
|
||||||
<th width="100" class="border border-black dark:border-white">
|
|
||||||
{{ $t('headers.time') }}
|
|
||||||
</th>
|
|
||||||
<th width="50" class="border border-black dark:border-white text-xs p-0">
|
|
||||||
<table class="h-full w-full border-collapse">
|
|
||||||
<tbody>
|
|
||||||
<tr class="border-b border-b-black dark:border-b-white">
|
|
||||||
<td class="">{{ $t('headers.loco_1') }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr class="border-b border-b-black dark:border-b-white">
|
|
||||||
<td>{{ $t('headers.loco_2') }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>{{ $t('headers.loco_3') }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</th>
|
|
||||||
<th width="55" class="border border-black dark:border-white text-xs relative">
|
|
||||||
<div class="absolute top-0 left-0 w-full h-full">
|
|
||||||
<table class="h-full w-full border-collapse">
|
|
||||||
<tbody>
|
|
||||||
<tr class="border-b border-b-black dark:border-b-white">
|
|
||||||
<td>{{ $t('headers.mass') }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>{{ $t('headers.length') }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</th>
|
|
||||||
<th width="50" class="border border-black dark:border-white">{{ $t('headers.vmax') }}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tbody>
|
|
||||||
<tr v-for="(row, i) in computedTimetableRows">
|
|
||||||
<!-- Line no. -->
|
|
||||||
<td
|
|
||||||
class="text-center align-top border-l border-l-black dark:border-l-white relative"
|
|
||||||
:class="{
|
|
||||||
'border-t border-t-black dark:border-t-white':
|
|
||||||
row.lastRowRef != null && row.lastRowRef.arrivalLineNumber != row.arrivalLineNumber,
|
|
||||||
'border-b border-b-black dark:border-b-white': i == computedTimetableRows.length - 1
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<div class="absolute -top-[0.5px] left-0 w-full h-full">
|
|
||||||
<table class="h-full w-full border-collapse">
|
|
||||||
<tbody>
|
|
||||||
<!-- Arrival Km -->
|
|
||||||
<tr class="align-top">
|
|
||||||
<td>
|
|
||||||
{{
|
|
||||||
row.lastRowRef == null ||
|
|
||||||
row.lastRowRef.arrivalLineNumber != row.arrivalLineNumber
|
|
||||||
? row.arrivalLineNumber
|
|
||||||
: ' '
|
|
||||||
}}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<!-- Departure Km -->
|
|
||||||
<tr class="align-top">
|
|
||||||
<!-- <td>{{ row.departureLineNumber != row.arrivalLineNumber ? row.departureLineNumber : ' ' }}</td> -->
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<!-- Km -->
|
|
||||||
<td
|
|
||||||
class="border border-black dark:border-white border-t-0 border-b-1 relative p-0"
|
|
||||||
:class="{
|
|
||||||
'border-b-0': i != computedTimetableRows.length - 1
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<div class="absolute -top-[0.5px] left-0 w-full h-full">
|
|
||||||
<table class="h-full w-full border-collapse">
|
|
||||||
<tbody>
|
|
||||||
<!-- Arrival Km -->
|
|
||||||
<tr>
|
|
||||||
<td
|
|
||||||
class="align-top border-t text-inherit"
|
|
||||||
:class="{
|
|
||||||
'border-t-0 text-transparent':
|
|
||||||
row.lastRowRef &&
|
|
||||||
row.lastRowRef.departureSpeedL == row.arrivalSpeedL &&
|
|
||||||
row.lastRowRef.departureSpeedP == row.arrivalSpeedP &&
|
|
||||||
row.lastRowRef.departureTracks == row.arrivalTracks &&
|
|
||||||
row.lastRowRef.arrivalLineNumber == row.arrivalLineNumber
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
{{ row.arrivalKm }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<!-- Departure Km -->
|
|
||||||
<tr
|
|
||||||
:class="{
|
|
||||||
'border-black dark:border-white border-t align-top':
|
|
||||||
row.arrivalTracks != row.departureTracks ||
|
|
||||||
row.departureSpeedL != row.arrivalSpeedL ||
|
|
||||||
row.departureSpeedP != row.arrivalSpeedP,
|
|
||||||
hidden:
|
|
||||||
row.arrivalTracks == row.departureTracks &&
|
|
||||||
row.departureSpeedL == row.arrivalSpeedL &&
|
|
||||||
row.departureSpeedP == row.arrivalSpeedP
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<td> {{ row.departureKm }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<!-- Vp, Vl -->
|
|
||||||
<td
|
|
||||||
class="text-center align-top p-0 border-l-black dark:border-l-white relative"
|
|
||||||
:class="{
|
|
||||||
'border-b border-b-black dark:border-b-white': i == computedTimetableRows.length - 1
|
|
||||||
}"
|
|
||||||
colspan="2"
|
|
||||||
>
|
|
||||||
<!-- Direction line arrow -->
|
|
||||||
<div
|
|
||||||
class="absolute h-0 w-0 border-x-transparent border-x-[6px] -left-[4px] border-t-[25px] -bottom-[12px] z-30 border-t-black dark:border-t-white print:border-t-black"
|
|
||||||
v-if="i == computedTimetableRows.length - 1"
|
|
||||||
></div>
|
|
||||||
|
|
||||||
<div class="absolute -top-[0.5px] left-0 w-full h-full">
|
|
||||||
<table class="h-full w-full border-collapse">
|
|
||||||
<tbody>
|
|
||||||
<tr class="align-top">
|
|
||||||
<td
|
|
||||||
class="font-bold border-l-4 border-l-black dark:border-l-white"
|
|
||||||
:class="{
|
|
||||||
'border-t border-t-black dark:border-t-white':
|
|
||||||
row.lastRowRef != null &&
|
|
||||||
row.lastRowRef.departureSpeedP != row.arrivalSpeedP
|
|
||||||
}"
|
|
||||||
:colspan="row.arrivalTracks == 2 ? '1' : '2'"
|
|
||||||
width="35"
|
|
||||||
>
|
|
||||||
{{
|
|
||||||
row.lastRowRef == null ||
|
|
||||||
row.lastRowRef.departureSpeedP != row.arrivalSpeedP ||
|
|
||||||
row.lastRowRef.departureTracks != row.arrivalTracks
|
|
||||||
? row.arrivalSpeedP
|
|
||||||
: ' '
|
|
||||||
}}
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td
|
|
||||||
v-if="row.arrivalTracks == 2"
|
|
||||||
class="border-l border-l-black dark:border-l-white"
|
|
||||||
:class="{
|
|
||||||
'border-t border-t-black dark:border-t-white':
|
|
||||||
row.lastRowRef != null &&
|
|
||||||
row.lastRowRef.departureSpeedL != row.arrivalSpeedL
|
|
||||||
}"
|
|
||||||
width="35"
|
|
||||||
>
|
|
||||||
{{
|
|
||||||
row.lastRowRef == null ||
|
|
||||||
row.lastRowRef.departureSpeedL != row.arrivalSpeedL ||
|
|
||||||
row.lastRowRef.departureTracks != row.arrivalTracks
|
|
||||||
? row.arrivalSpeedL
|
|
||||||
: ' '
|
|
||||||
}}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr
|
|
||||||
class="border-l-4 border-l-black dark:border-l-white"
|
|
||||||
:class="{
|
|
||||||
'border-t border-t-black dark:border-t-white align-top':
|
|
||||||
row.arrivalTracks != row.departureTracks ||
|
|
||||||
row.departureSpeedL != row.arrivalSpeedL ||
|
|
||||||
row.departureSpeedP != row.arrivalSpeedP
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<td
|
|
||||||
:colspan="row.departureTracks == 2 ? '1' : '2'"
|
|
||||||
class="font-bold"
|
|
||||||
width="35"
|
|
||||||
>
|
|
||||||
{{
|
|
||||||
row.departureSpeedP != row.arrivalSpeedP ||
|
|
||||||
row.departureTracks != row.arrivalTracks
|
|
||||||
? row.departureSpeedP
|
|
||||||
: ' '
|
|
||||||
}}
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td
|
|
||||||
v-if="row.departureTracks == 2"
|
|
||||||
class="border-l border-l-black dark:border-l-white"
|
|
||||||
width="35"
|
|
||||||
>
|
|
||||||
{{
|
|
||||||
row.departureSpeedL != row.arrivalSpeedL ||
|
|
||||||
row.departureTracks != row.arrivalTracks
|
|
||||||
? row.departureSpeedL
|
|
||||||
: ' '
|
|
||||||
}}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<!-- Station -->
|
|
||||||
<td class="border border-black dark:border-white relative">
|
|
||||||
<div class="absolute top-0 left-0 w-full h-full">
|
|
||||||
<div class="flex flex-col h-full justify-between p-1">
|
|
||||||
<div :class="{ 'font-bold': row.isMain }">
|
|
||||||
{{ row.pointName }}
|
|
||||||
<span v-if="row.stopTime"> ; {{ row.stopType || 'pt' }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex justify-between">
|
|
||||||
<span>{{ row.pointKm }}</span>
|
|
||||||
<span>{{ row.abbrevs.join(', ') }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<!-- Time -->
|
|
||||||
<td class="p-0 border border-black dark:border-white relative">
|
|
||||||
<div class="absolute top-0 left-0 w-full h-full">
|
|
||||||
<table class="h-full w-full border-collapse">
|
|
||||||
<tbody>
|
|
||||||
<tr class="text-center align-top h-full">
|
|
||||||
<td
|
|
||||||
class="border-r-[1px] border-r-black dark:border-r-white"
|
|
||||||
:class="{ 'font-bold': row.stopTime > 0 }"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
v-if="
|
|
||||||
(row.scheduledArrivalDate?.getTime() || 0) !=
|
|
||||||
(row.scheduledDepartureDate?.getTime() || 0)
|
|
||||||
"
|
|
||||||
>
|
|
||||||
{{
|
|
||||||
row.scheduledArrivalDate?.toLocaleTimeString('pl-PL', {
|
|
||||||
hour: '2-digit',
|
|
||||||
minute: '2-digit'
|
|
||||||
})
|
|
||||||
}}<sup
|
|
||||||
v-if="
|
|
||||||
row.scheduledArrivalDate && row.scheduledArrivalDate.getSeconds() != 0
|
|
||||||
"
|
|
||||||
>{{ Math.floor((row.scheduledArrivalDate.getSeconds() / 60) * 10) }}</sup
|
|
||||||
>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span v-else> | </span>
|
|
||||||
</td>
|
|
||||||
<td width="30">{{ row.driveTime > 0 ? row.driveTime / 60000 : '' }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr class="text-center align-bottom h-full">
|
|
||||||
<td
|
|
||||||
class="border-r-[1px] border-r-black dark:border-r-white"
|
|
||||||
:class="{ 'font-bold': row.stopTime > 0 }"
|
|
||||||
>
|
|
||||||
<span>{{
|
|
||||||
row.scheduledDepartureDate?.toLocaleTimeString('pl-PL', {
|
|
||||||
hour: '2-digit',
|
|
||||||
minute: '2-digit'
|
|
||||||
})
|
|
||||||
}}</span>
|
|
||||||
<sup
|
|
||||||
v-if="
|
|
||||||
row.scheduledDepartureDate && row.scheduledDepartureDate.getSeconds() != 0
|
|
||||||
"
|
|
||||||
>{{ Math.floor((row.scheduledDepartureDate.getSeconds() / 60) * 10) }}</sup
|
|
||||||
>
|
|
||||||
</td>
|
|
||||||
<td width="30" class="font-bold">{{ row.stopTime || '' }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<!-- Locos -->
|
|
||||||
<td
|
|
||||||
class="p-0 text-center border border-black dark:border-white relative h-24 text-sm"
|
|
||||||
:class="{ 'text-stone-400 ': i > 0 }"
|
|
||||||
>
|
|
||||||
<table class="h-full w-full border-collapse">
|
|
||||||
<tbody>
|
|
||||||
<tr class="border-b-[1px] border-b-black dark:border-b-white">
|
|
||||||
<td>{{ row.headUnits[0] }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr class="border-b-[1px] border-b-black dark:border-b-white">
|
|
||||||
<td>{{ row.headUnits[1] ?? ' ' }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>{{ row.headUnits[2] ?? ' ' }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<!-- Load / Length -->
|
|
||||||
<td
|
|
||||||
class="p-0 text-center border border-black dark:border-white relative"
|
|
||||||
:class="{ 'text-stone-400 ': i > 0 }"
|
|
||||||
>
|
|
||||||
<div class="absolute top-0 left-0 w-full h-full">
|
|
||||||
<table class="h-full w-full border-collapse">
|
|
||||||
<tbody>
|
|
||||||
<tr class="border-b-[1px] border-b-black dark:border-b-white">
|
|
||||||
<td>{{ Math.floor(row.stockMass / 1000) }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>{{ row.stockLength }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<!-- Vmax-->
|
|
||||||
<td
|
|
||||||
class="text-center border border-black dark:border-white"
|
|
||||||
:class="{ 'text-stone-400 ': i > 0 }"
|
|
||||||
>
|
|
||||||
{{ row.stockVmax }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<div class="mt-2">
|
|
||||||
<b><u>Kursuje:</u></b>
|
|
||||||
<div>
|
|
||||||
- {{ parseTimetableRunDate(computedTimetableRows[0].scheduledDepartureDate!) }}
|
|
||||||
<span
|
|
||||||
v-if="
|
|
||||||
computedTimetableRows[
|
|
||||||
computedTimetableRows.length - 1
|
|
||||||
].scheduledArrivalDate!.getDate() !=
|
|
||||||
computedTimetableRows[0].scheduledDepartureDate!.getDate()
|
|
||||||
"
|
|
||||||
>
|
|
||||||
-
|
|
||||||
{{
|
|
||||||
parseTimetableRunDate(
|
|
||||||
computedTimetableRows[computedTimetableRows.length - 1].scheduledArrivalDate!
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="timetableWarnings.length != 0">
|
|
||||||
<b><u>Uwagi do rozkładu:</u></b>
|
|
||||||
<div>- {{ timetableWarnings }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { computed } from 'vue';
|
|
||||||
import { useApiStore } from '../../stores/api.store';
|
|
||||||
import { useGlobalStore } from '../../stores/global.store';
|
|
||||||
import type { SceneryRoute, StopRow, TimetablePathData } from '../../types/common.types';
|
|
||||||
import { parseTimetableRunDate } from '../../utils/dateUtils';
|
|
||||||
|
|
||||||
const globalStore = useGlobalStore();
|
|
||||||
const apiStore = useApiStore();
|
|
||||||
|
|
||||||
// Tymczasowa tabelka z posterunkami APO
|
|
||||||
const apoNames = ['Stary Kisielin, pe', 'Czerwony Dwór, pe', 'Szczejkowice, pe'];
|
|
||||||
|
|
||||||
const timetableWarnings = computed(() => {
|
|
||||||
const timetableData = globalStore.currentTimetableData;
|
|
||||||
|
|
||||||
if (!timetableData) return '';
|
|
||||||
|
|
||||||
return timetableData.warningNotes;
|
|
||||||
});
|
|
||||||
|
|
||||||
const computedTimetableRows = computed(() => {
|
|
||||||
const timetableData = globalStore.currentTimetableData;
|
|
||||||
|
|
||||||
if (!timetableData) return [];
|
|
||||||
|
|
||||||
let timeFrom = Date.now();
|
|
||||||
|
|
||||||
const stockVmax = timetableData.trainMaxSpeed,
|
|
||||||
stockMass = timetableData.mass,
|
|
||||||
stockLength = timetableData.length;
|
|
||||||
|
|
||||||
const timetablePath = parseTimetablePath(timetableData.path);
|
|
||||||
|
|
||||||
const stopRows: StopRow[] = [];
|
|
||||||
|
|
||||||
let lastRowRef: StopRow | null = null;
|
|
||||||
|
|
||||||
let currentPathIndex = 0;
|
|
||||||
let currentPath = timetablePath[0];
|
|
||||||
|
|
||||||
let lastDepartureTimestamp = 0;
|
|
||||||
|
|
||||||
let arrivalSpeedL = 0,
|
|
||||||
arrivalSpeedP = 0;
|
|
||||||
|
|
||||||
let departureSpeedL = 0,
|
|
||||||
departureSpeedP = 0;
|
|
||||||
|
|
||||||
let arrivalKm = 0,
|
|
||||||
arrivalTracks = 0,
|
|
||||||
departureTracks = 2,
|
|
||||||
arrivalLineNumber = 0,
|
|
||||||
departureLineNumber = 0,
|
|
||||||
abbrevs = [] as string[];
|
|
||||||
|
|
||||||
if (currentPath.departureLineData) {
|
|
||||||
departureSpeedL = Math.min(currentPath.departureLineData.routeSpeed, stockVmax);
|
|
||||||
departureSpeedP = currentPath.departureLineData.routeSpeedExit
|
|
||||||
? Math.min(currentPath.departureLineData.routeSpeedExit, stockVmax)
|
|
||||||
: departureSpeedL;
|
|
||||||
|
|
||||||
departureTracks = currentPath.departureLineData.routeTracks;
|
|
||||||
|
|
||||||
arrivalSpeedL = departureSpeedL;
|
|
||||||
arrivalSpeedP = departureSpeedP;
|
|
||||||
|
|
||||||
arrivalTracks = departureTracks;
|
|
||||||
|
|
||||||
departureLineNumber = currentPath.departureLineData?.realLineNo ?? 0;
|
|
||||||
arrivalLineNumber = departureLineNumber;
|
|
||||||
|
|
||||||
abbrevs = getAbbrevs(currentPath.departureLineData);
|
|
||||||
}
|
|
||||||
|
|
||||||
// console.debug('=========== ' + timetableData.trainNo + ' ===========');
|
|
||||||
|
|
||||||
const stopList = parseStopListString(timetableData.stopListString);
|
|
||||||
|
|
||||||
for (const stop of stopList) {
|
|
||||||
if (stop.arrivalLine && stop.arrivalLine == currentPath.arrivalLine) {
|
|
||||||
if (arrivalKm >= stop.stopDistance)
|
|
||||||
arrivalKm =
|
|
||||||
(Number(stopRows[stopRows.length - 1].departureKm ?? '0') + stop.stopDistance) / 2;
|
|
||||||
|
|
||||||
if (currentPath.arrivalLineData) {
|
|
||||||
arrivalSpeedP = Math.min(currentPath.arrivalLineData.routeSpeed, stockVmax);
|
|
||||||
arrivalSpeedL = currentPath.arrivalLineData.routeSpeedExit
|
|
||||||
? Math.min(currentPath.arrivalLineData.routeSpeedExit, stockVmax)
|
|
||||||
: arrivalSpeedP;
|
|
||||||
|
|
||||||
arrivalTracks = currentPath.arrivalLineData.routeTracks;
|
|
||||||
arrivalLineNumber = currentPath.arrivalLineData.realLineNo ?? 0;
|
|
||||||
abbrevs = getAbbrevs(currentPath.arrivalLineData);
|
|
||||||
}
|
|
||||||
|
|
||||||
departureSpeedL = arrivalSpeedL;
|
|
||||||
departureSpeedP = arrivalSpeedP;
|
|
||||||
departureLineNumber = arrivalLineNumber;
|
|
||||||
|
|
||||||
departureTracks = arrivalTracks;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
stop.mainStop ||
|
|
||||||
(/^podg|po|pe$/.test(stop.stopNameRAW) && !/^sbl/i.test(stop.stopNameRAW))
|
|
||||||
) {
|
|
||||||
let correctedDepartureSpeedL = 0,
|
|
||||||
correctedDepartureSpeedP = 0,
|
|
||||||
correctedDepartureTracks = 0;
|
|
||||||
|
|
||||||
const internalRouteInfo = stop.departureLine
|
|
||||||
? currentPath.sceneryData?.routesInfo.find(
|
|
||||||
(route) => route.isInternal && route.routeName == stop.departureLine
|
|
||||||
)
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
if (internalRouteInfo) {
|
|
||||||
correctedDepartureSpeedL = Math.min(internalRouteInfo.routeSpeed, stockVmax);
|
|
||||||
correctedDepartureSpeedP = internalRouteInfo.routeSpeedExit
|
|
||||||
? Math.min(internalRouteInfo.routeSpeedExit, stockVmax)
|
|
||||||
: correctedDepartureSpeedL;
|
|
||||||
|
|
||||||
departureSpeedL = correctedDepartureSpeedL;
|
|
||||||
departureSpeedP = correctedDepartureSpeedP;
|
|
||||||
|
|
||||||
arrivalLineNumber = internalRouteInfo.realLineNo ?? arrivalLineNumber;
|
|
||||||
departureLineNumber = internalRouteInfo.realLineNo ?? departureLineNumber;
|
|
||||||
|
|
||||||
abbrevs = getAbbrevs(internalRouteInfo);
|
|
||||||
|
|
||||||
correctedDepartureTracks = internalRouteInfo.routeTracks;
|
|
||||||
departureTracks = internalRouteInfo.routeTracks;
|
|
||||||
|
|
||||||
if (stopRows.length == 0) {
|
|
||||||
arrivalSpeedL = departureSpeedL;
|
|
||||||
arrivalSpeedP = departureSpeedP;
|
|
||||||
arrivalTracks = departureTracks;
|
|
||||||
arrivalLineNumber = departureLineNumber;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let pointAbbrevs = [];
|
|
||||||
if (apoNames.includes(stop.stopNameRAW))
|
|
||||||
pointAbbrevs.unshift(`APO ${currentPath.sceneryData?.abbr}`);
|
|
||||||
|
|
||||||
let rowData: StopRow = {
|
|
||||||
isMain: stop.mainStop,
|
|
||||||
pointKm: stop.stopDistance.toFixed(3),
|
|
||||||
pointName: stop.stopNameRAW,
|
|
||||||
scheduledArrivalDate: stop.arrivalTimestamp ? new Date(stop.arrivalTimestamp) : null,
|
|
||||||
scheduledDepartureDate: stop.departureTimestamp ? new Date(stop.departureTimestamp) : null,
|
|
||||||
stopTime: stop.stopTime ? (stop.departureTimestamp - stop.arrivalTimestamp) / 60000 : 0,
|
|
||||||
stopType: stop.stopType,
|
|
||||||
sceneryName: currentPath.sceneryName,
|
|
||||||
arrivalLineNumber: arrivalLineNumber == 0 ? '' : arrivalLineNumber.toString(),
|
|
||||||
departureLineNumber: departureLineNumber == 0 ? '' : departureLineNumber.toString(),
|
|
||||||
driveTime: lastDepartureTimestamp ? stop.arrivalTimestamp - lastDepartureTimestamp : 0,
|
|
||||||
|
|
||||||
abbrevs: [...pointAbbrevs, ...abbrevs],
|
|
||||||
|
|
||||||
arrivalKm: arrivalKm.toFixed(3),
|
|
||||||
departureKm: stop.stopDistance.toFixed(3),
|
|
||||||
|
|
||||||
arrivalSpeedL,
|
|
||||||
arrivalSpeedP,
|
|
||||||
|
|
||||||
arrivalTracks: arrivalTracks,
|
|
||||||
|
|
||||||
departureSpeedL,
|
|
||||||
departureSpeedP,
|
|
||||||
|
|
||||||
departureTracks: departureTracks,
|
|
||||||
|
|
||||||
headUnits: timetableData.headUnits,
|
|
||||||
stockVmax,
|
|
||||||
stockLength,
|
|
||||||
stockMass,
|
|
||||||
|
|
||||||
lastRowRef
|
|
||||||
};
|
|
||||||
|
|
||||||
// console.debug(stop.stopNameRAW, stop.departureLine);
|
|
||||||
|
|
||||||
arrivalKm = stop.stopDistance;
|
|
||||||
|
|
||||||
arrivalSpeedL = correctedDepartureSpeedL || arrivalSpeedL;
|
|
||||||
arrivalSpeedP = correctedDepartureSpeedP || arrivalSpeedP;
|
|
||||||
|
|
||||||
arrivalTracks = correctedDepartureTracks || arrivalTracks;
|
|
||||||
|
|
||||||
if (stop.departureTimestamp) lastDepartureTimestamp = stop.departureTimestamp;
|
|
||||||
lastRowRef = rowData;
|
|
||||||
|
|
||||||
stopRows.push(rowData);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stop.departureLine && stop.departureLine == currentPath.departureLine) {
|
|
||||||
arrivalKm = stop.stopDistance;
|
|
||||||
|
|
||||||
// Reverse search for last scenery checkpoint
|
|
||||||
if (currentPath.departureLineData) {
|
|
||||||
if (
|
|
||||||
currentPath.departureLineData.routeLength != 0 &&
|
|
||||||
!currentPath.departureLineData.isRouteSBL
|
|
||||||
)
|
|
||||||
arrivalKm = stop.stopDistance + currentPath.departureLineData.routeLength / 1000;
|
|
||||||
|
|
||||||
if (
|
|
||||||
stopRows[stopRows.length - 1].isMain &&
|
|
||||||
currentPath.departureLineData.isRouteSBL &&
|
|
||||||
stop.departureLine == currentPath.departureLine
|
|
||||||
)
|
|
||||||
arrivalKm = stop.stopDistance + currentPath.departureLineData.routeLength / 1000;
|
|
||||||
|
|
||||||
for (let i = stopRows.length - 1; i >= 0; i--) {
|
|
||||||
stopRows[i].departureTracks = currentPath.departureLineData.routeTracks;
|
|
||||||
|
|
||||||
stopRows[i].departureSpeedL = Math.min(
|
|
||||||
currentPath.departureLineData.routeSpeed,
|
|
||||||
stockVmax
|
|
||||||
);
|
|
||||||
|
|
||||||
stopRows[i].departureSpeedP = currentPath.departureLineData.routeSpeedExit
|
|
||||||
? Math.min(currentPath.departureLineData.routeSpeedExit, stockVmax)
|
|
||||||
: stopRows[i].departureSpeedL;
|
|
||||||
|
|
||||||
stopRows[i].arrivalLineNumber =
|
|
||||||
currentPath.departureLineData.realLineNo?.toString() ?? '';
|
|
||||||
|
|
||||||
if (stopRows[i].isMain || stopRows[i].pointName.endsWith(', podg')) {
|
|
||||||
stopRows[i].departureSpeedL = Math.min(
|
|
||||||
currentPath.departureLineData.routeSpeed,
|
|
||||||
stockVmax
|
|
||||||
);
|
|
||||||
|
|
||||||
stopRows[i].departureSpeedP = currentPath.departureLineData.routeSpeedExit
|
|
||||||
? Math.min(currentPath.departureLineData.routeSpeedExit, stockVmax)
|
|
||||||
: stopRows[i].departureSpeedL;
|
|
||||||
|
|
||||||
stopRows[i].departureTracks = currentPath.departureLineData.routeTracks;
|
|
||||||
|
|
||||||
// console.log(
|
|
||||||
// stop.departureLine,
|
|
||||||
// currentPath.sceneryName,
|
|
||||||
// stop.stopDistance,
|
|
||||||
// currentPath.departureLineData.routeLength,
|
|
||||||
// currentPath.departureLineData.isRouteSBL
|
|
||||||
// );
|
|
||||||
|
|
||||||
abbrevs = getAbbrevs(currentPath.departureLineData);
|
|
||||||
stopRows[i].abbrevs = abbrevs;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
stopRows[i].arrivalSpeedP = Math.min(currentPath.departureLineData.routeSpeed, stockVmax);
|
|
||||||
stopRows[i].arrivalSpeedL = currentPath.departureLineData.routeSpeedExit
|
|
||||||
? Math.min(currentPath.departureLineData.routeSpeedExit, stockVmax)
|
|
||||||
: stopRows[i].arrivalSpeedP;
|
|
||||||
stopRows[i].arrivalTracks = currentPath.departureLineData.routeTracks;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
currentPath = timetablePath[++currentPathIndex];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let timeTo = Date.now();
|
|
||||||
|
|
||||||
globalStore.generatedMs = timeTo - timeFrom;
|
|
||||||
|
|
||||||
return stopRows;
|
|
||||||
});
|
|
||||||
|
|
||||||
function parseTimetablePath(path: string): TimetablePathData[] {
|
|
||||||
return path.split(';').map((pathEl) => {
|
|
||||||
const [arrivalLine, scenery, departureLine] = pathEl.split(',');
|
|
||||||
const sceneryName = scenery.split(' ').slice(0, -1).join(' ');
|
|
||||||
|
|
||||||
const sceneryData = apiStore.sceneryData?.find((sc) => sc.name == sceneryName) ?? null;
|
|
||||||
const arrivalLineData = arrivalLine
|
|
||||||
? (sceneryData?.routesInfo.find((rt) => rt.routeName == arrivalLine) ?? null)
|
|
||||||
: null;
|
|
||||||
const departureLineData = departureLine
|
|
||||||
? (sceneryData?.routesInfo.find((rt) => rt.routeName == departureLine) ?? null)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
return {
|
|
||||||
sceneryName,
|
|
||||||
sceneryData: sceneryData ?? null,
|
|
||||||
arrivalLine: arrivalLine ?? '',
|
|
||||||
departureLine: departureLine ?? '',
|
|
||||||
arrivalLineData,
|
|
||||||
departureLineData
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseStopListString(stopsString: string) {
|
|
||||||
//${stop.arrivalLine ?? ''};${stop.arrivalTimestamp};${stop.stopNameRAW};${stop.stopTime ? stop.stopTime + '_' + stop.stopType : ''};${stop.mainStop};${stop.stopDistance};${stop.departureTimestamp};${stop.departureLine ?? ''}
|
|
||||||
return stopsString.split('~~').map((stop) => {
|
|
||||||
const [
|
|
||||||
arrivalLine,
|
|
||||||
arrivalTimestamp,
|
|
||||||
stopNameRAW,
|
|
||||||
stopDetails,
|
|
||||||
isMainStop,
|
|
||||||
stopDistance,
|
|
||||||
departureTimestamp,
|
|
||||||
departureLine
|
|
||||||
] = stop.split(';');
|
|
||||||
const [stopTime, stopType] = stopDetails.split('_');
|
|
||||||
|
|
||||||
return {
|
|
||||||
arrivalLine,
|
|
||||||
arrivalTimestamp: parseInt(arrivalTimestamp),
|
|
||||||
stopNameRAW,
|
|
||||||
stopTime: stopTime ?? 0,
|
|
||||||
stopType: stopType ?? null,
|
|
||||||
mainStop: isMainStop == 'true',
|
|
||||||
stopDistance: parseFloat(stopDistance),
|
|
||||||
departureTimestamp: parseInt(departureTimestamp),
|
|
||||||
departureLine
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAbbrevs(routeData: SceneryRoute) {
|
|
||||||
const abbrevs = [];
|
|
||||||
|
|
||||||
if (routeData.isRouteSBL == true)
|
|
||||||
abbrevs.push(
|
|
||||||
`${routeData.routeSpeed > 130 ? '4' : ''}S${routeData.routeTracks == 2 ? 'S' : ''}`
|
|
||||||
);
|
|
||||||
else if (routeData.routeTracks == 2) abbrevs.push('PP');
|
|
||||||
|
|
||||||
return abbrevs;
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
@media print {
|
|
||||||
th,
|
|
||||||
tr,
|
|
||||||
td {
|
|
||||||
border-color: theme('colors.black');
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
page-break-after: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
tr {
|
|
||||||
page-break-inside: avoid;
|
|
||||||
page-break-after: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
thead {
|
|
||||||
display: table-header-group;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,394 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<h2 class="font-semibold text-center text-3xl">
|
|
||||||
{{ globalStore.currentTimetableData!.category }}
|
|
||||||
{{ globalStore.currentTimetableData!.trainNo }}
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<h3 class="font-medium text-center">
|
|
||||||
{{ globalStore.currentTimetableData!.route.replace('|', ' - ') }}
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<p class="mt-2 text-center">
|
|
||||||
Kursuje: {{ timetableDate.toLocaleDateString('pl-PL', { day: '2-digit' }) }}.{{
|
|
||||||
romanMonthDigits[timetableDate.getMonth()]
|
|
||||||
}}.{{ timetableDate.toLocaleDateString('pl-PL', { year: 'numeric' }) }}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p class="mt-2">
|
|
||||||
Lokomotywa elektryczna {{ globalStore.currentTimetableData!.headUnits[0] }}, waga:
|
|
||||||
{{ (globalStore.currentTimetableData!.mass / 1000).toFixed(1) }} t, długość:
|
|
||||||
{{ globalStore.currentTimetableData!.length }} m
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p></p>
|
|
||||||
|
|
||||||
<table class="table-fixed w-full border-collapse h-full">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<!-- Name -->
|
|
||||||
<th
|
|
||||||
width="250"
|
|
||||||
class="font-normal border border-black dark:border-white border-l-transparent"
|
|
||||||
>
|
|
||||||
<MapPinIcon :size="20" class="mx-auto" />
|
|
||||||
</th>
|
|
||||||
|
|
||||||
<!-- Info -->
|
|
||||||
<th width="50" class="font-normal border border-black dark:border-white">
|
|
||||||
<CircleAlertIcon :size="20" class="mx-auto" />
|
|
||||||
</th>
|
|
||||||
|
|
||||||
<!-- Drive time -->
|
|
||||||
<th width="30" class="font-normal border border-black dark:border-white">
|
|
||||||
<TimerIcon :size="20" class="mx-auto" />
|
|
||||||
</th>
|
|
||||||
|
|
||||||
<!-- Arrival -->
|
|
||||||
<th width="70" class="font-normal border border-black dark:border-white">
|
|
||||||
<CalendarArrowUpIcon :size="20" class="mx-auto" />
|
|
||||||
</th>
|
|
||||||
|
|
||||||
<!-- Stop time -->
|
|
||||||
<th width="40" class="font-normal border border-black dark:border-white">
|
|
||||||
<HandIcon :size="20" class="mx-auto" />
|
|
||||||
</th>
|
|
||||||
|
|
||||||
<!-- Departure -->
|
|
||||||
<th width="70" class="font-normal border border-black dark:border-white">
|
|
||||||
<CalendarArrowDownIcon :size="20" class="mx-auto" />
|
|
||||||
</th>
|
|
||||||
|
|
||||||
<!-- vMax -->
|
|
||||||
<th
|
|
||||||
width="80"
|
|
||||||
class="font-normal border border-black dark:border-white border-r-transparent"
|
|
||||||
>
|
|
||||||
<CircleGaugeIcon :size="20" class="mx-auto" />
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tbody>
|
|
||||||
<tr
|
|
||||||
v-for="(row, i) in computedTimetableRows"
|
|
||||||
:class="{ 'bg-slate-100 dark:bg-zinc-900 print:bg-gray-300': i % 2 == 0 }"
|
|
||||||
class="leading-none"
|
|
||||||
>
|
|
||||||
<td class="px-2 font-thin text-nowrap overflow-hidden overflow-ellipsis">
|
|
||||||
<span :class="{ 'font-semibold': row.isMain, 'font-normal': !row.isMain }">
|
|
||||||
{{ row.pointName }}</span
|
|
||||||
><span
|
|
||||||
>.............................................................................................
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td
|
|
||||||
class="border border-black dark:border-white border-t-transparent border-b-transparent"
|
|
||||||
></td>
|
|
||||||
|
|
||||||
<td
|
|
||||||
class="border border-black dark:border-white border-t-transparent border-b-transparent text-center font-bold"
|
|
||||||
>
|
|
||||||
{{ row.driveTime ? Math.floor(row.driveTime / 60000) : '' }}
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td
|
|
||||||
class="border border-black dark:border-white border-t-transparent border-b-transparent text-right font-bold px-2"
|
|
||||||
>
|
|
||||||
<span v-if="row.stopType == 'pt'">+ </span>
|
|
||||||
<span> {{ row.arrivalDateStr }} </span>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td
|
|
||||||
class="border border-black dark:border-white border-t-transparent border-b-transparent text-center font-semibold"
|
|
||||||
>
|
|
||||||
{{ row.stopTime || '' }}
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td
|
|
||||||
class="border border-black dark:border-white border-t-transparent border-b-transparent text-right font-bold px-2 relative"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="absolute right-[-3px] border-r-[5px] border-black"
|
|
||||||
:class="{
|
|
||||||
'top-0 h-[calc(100%+1px)]':
|
|
||||||
row.arrivalTracks == row.departureTracks && row.arrivalTracks == 2,
|
|
||||||
'top-0 h-[calc(50%+1px)]': row.arrivalTracks > row.departureTracks,
|
|
||||||
'top-1/2 h-[calc(50%+1px)]': row.arrivalTracks < row.departureTracks
|
|
||||||
}"
|
|
||||||
></span>
|
|
||||||
{{ row.departureDateStr }}
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<!-- v-if="
|
|
||||||
i == 0 || (i > 0 && computedTimetableRows[i - 1].departureSpeed != row.arrivalSpeed)
|
|
||||||
" -->
|
|
||||||
<td class="text-center font-bold">
|
|
||||||
<span v-if="i == 0 || computedTimetableRows[i - 1].departureSpeed != row.arrivalSpeed">
|
|
||||||
{{ i == 0 ? row.departureSpeed : row.arrivalSpeed }}
|
|
||||||
<span v-if="row.arrivalSpeed != row.departureSpeed">
|
|
||||||
/
|
|
||||||
{{ row.departureSpeed }}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { computed, ref } from 'vue';
|
|
||||||
import { useGlobalStore } from '../../stores/global.store';
|
|
||||||
import type { StopRowCZ, TimetablePathData } from '../../types/common.types';
|
|
||||||
import { useApiStore } from '../../stores/api.store';
|
|
||||||
import {
|
|
||||||
CalendarArrowDownIcon,
|
|
||||||
CalendarArrowUpIcon,
|
|
||||||
CircleAlertIcon,
|
|
||||||
CircleGaugeIcon,
|
|
||||||
HandIcon,
|
|
||||||
MapPinIcon,
|
|
||||||
TimerIcon
|
|
||||||
} from 'lucide-vue-next';
|
|
||||||
|
|
||||||
const globalStore = useGlobalStore();
|
|
||||||
const apiStore = useApiStore();
|
|
||||||
|
|
||||||
const timetableDate = ref(new Date());
|
|
||||||
|
|
||||||
const romanMonthDigits = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X', 'XI', 'XII'];
|
|
||||||
|
|
||||||
const computedTimetableRows = computed(() => {
|
|
||||||
const timetableData = globalStore.currentTimetableData;
|
|
||||||
|
|
||||||
if (!timetableData) return [];
|
|
||||||
|
|
||||||
let timeFrom = Date.now();
|
|
||||||
|
|
||||||
const stockVmax = timetableData.trainMaxSpeed,
|
|
||||||
stockMass = Math.floor(timetableData.mass / 1000),
|
|
||||||
stockLength = timetableData.length;
|
|
||||||
|
|
||||||
const timetablePath = parseTimetablePath(timetableData.path);
|
|
||||||
|
|
||||||
const stopRows: StopRowCZ[] = [];
|
|
||||||
|
|
||||||
let currentPathIndex = 0;
|
|
||||||
let currentPath = timetablePath[0];
|
|
||||||
|
|
||||||
let lastDepartureTimestamp = 0;
|
|
||||||
|
|
||||||
let arrivalSpeed = 0,
|
|
||||||
departureSpeed = 0,
|
|
||||||
arrivalTracks = 0,
|
|
||||||
departureTracks = 2;
|
|
||||||
|
|
||||||
if (currentPath.departureLineData) {
|
|
||||||
departureSpeed = Math.min(currentPath.departureLineData.routeSpeed, stockVmax);
|
|
||||||
arrivalSpeed = Math.min(currentPath.departureLineData.routeSpeed, stockVmax);
|
|
||||||
|
|
||||||
departureTracks = currentPath.departureLineData.routeTracks;
|
|
||||||
arrivalTracks = currentPath.departureLineData.routeTracks;
|
|
||||||
}
|
|
||||||
|
|
||||||
const stopList = parseStopListString(timetableData.stopListString);
|
|
||||||
|
|
||||||
timetableDate.value = new Date(stopList[0].departureTimestamp);
|
|
||||||
|
|
||||||
stopList.forEach((stop) => {
|
|
||||||
if (stop.arrivalLine && stop.arrivalLine == currentPath.arrivalLine) {
|
|
||||||
if (currentPath.arrivalLineData) {
|
|
||||||
arrivalSpeed = Math.min(currentPath.arrivalLineData.routeSpeed, stockVmax);
|
|
||||||
arrivalTracks = currentPath.arrivalLineData.routeTracks;
|
|
||||||
}
|
|
||||||
|
|
||||||
departureSpeed = arrivalSpeed;
|
|
||||||
departureTracks = arrivalTracks;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
stop.mainStop ||
|
|
||||||
(/^podg|po|pe$/.test(stop.stopNameRAW) && !/^sbl/i.test(stop.stopNameRAW))
|
|
||||||
) {
|
|
||||||
let correctedDepartureSpeed = 0,
|
|
||||||
correctedDepartureTracks = 0;
|
|
||||||
|
|
||||||
const internalRouteInfo = stop.departureLine
|
|
||||||
? currentPath.sceneryData?.routesInfo.find(
|
|
||||||
(route) => route.isInternal && route.routeName == stop.departureLine
|
|
||||||
)
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
if (internalRouteInfo) {
|
|
||||||
correctedDepartureSpeed = Math.min(internalRouteInfo.routeSpeed, stockVmax);
|
|
||||||
departureSpeed = Math.min(internalRouteInfo.routeSpeed, stockVmax);
|
|
||||||
correctedDepartureTracks = internalRouteInfo.routeTracks;
|
|
||||||
departureTracks = internalRouteInfo.routeTracks;
|
|
||||||
|
|
||||||
if (stopRows.length == 0) {
|
|
||||||
arrivalSpeed = departureSpeed;
|
|
||||||
arrivalTracks = departureTracks;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const scheduledArrivalDate = stop.arrivalTimestamp ? new Date(stop.arrivalTimestamp) : null;
|
|
||||||
|
|
||||||
const scheduledDepartureDate = stop.departureTimestamp
|
|
||||||
? new Date(stop.departureTimestamp)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
let arrivalDateStr =
|
|
||||||
scheduledArrivalDate?.toLocaleTimeString('pl-PL', {
|
|
||||||
hour: 'numeric',
|
|
||||||
minute: '2-digit'
|
|
||||||
}) ?? '';
|
|
||||||
|
|
||||||
let departureDateStr =
|
|
||||||
scheduledDepartureDate?.toLocaleTimeString('pl-PL', {
|
|
||||||
hour: 'numeric',
|
|
||||||
minute: '2-digit'
|
|
||||||
}) ?? '';
|
|
||||||
|
|
||||||
if (
|
|
||||||
stopRows.length > 0 &&
|
|
||||||
stopRows[stopRows.length - 1]?.scheduledArrivalDate?.getHours() ==
|
|
||||||
scheduledArrivalDate?.getHours()
|
|
||||||
) {
|
|
||||||
arrivalDateStr = arrivalDateStr.split(':').slice(1).join(' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
stopRows[stopRows.length - 1]?.scheduledDepartureDate?.getHours() ==
|
|
||||||
scheduledDepartureDate?.getHours()
|
|
||||||
) {
|
|
||||||
departureDateStr = departureDateStr.split(':').slice(1).join(' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
let rowData: StopRowCZ = {
|
|
||||||
isMain: stop.mainStop,
|
|
||||||
pointKm: stop.stopDistance.toFixed(3),
|
|
||||||
pointName: stop.stopNameRAW,
|
|
||||||
scheduledArrivalDate,
|
|
||||||
scheduledDepartureDate,
|
|
||||||
stopTime: stop.stopTime ? (stop.departureTimestamp - stop.arrivalTimestamp) / 60000 : 0,
|
|
||||||
stopType: stop.stopType,
|
|
||||||
sceneryName: currentPath.sceneryName,
|
|
||||||
driveTime: lastDepartureTimestamp ? stop.arrivalTimestamp - lastDepartureTimestamp : 0,
|
|
||||||
|
|
||||||
arrivalSpeed: arrivalSpeed,
|
|
||||||
departureSpeed: departureSpeed,
|
|
||||||
|
|
||||||
arrivalTracks,
|
|
||||||
departureTracks,
|
|
||||||
|
|
||||||
headUnits: timetableData.headUnits,
|
|
||||||
stockVmax,
|
|
||||||
stockLength,
|
|
||||||
stockMass,
|
|
||||||
|
|
||||||
arrivalDateStr,
|
|
||||||
departureDateStr
|
|
||||||
};
|
|
||||||
|
|
||||||
arrivalSpeed = correctedDepartureSpeed || arrivalSpeed;
|
|
||||||
arrivalTracks = correctedDepartureTracks || arrivalTracks;
|
|
||||||
|
|
||||||
if (stop.departureTimestamp) lastDepartureTimestamp = stop.departureTimestamp;
|
|
||||||
|
|
||||||
stopRows.push(rowData);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stop.departureLine && stop.departureLine == currentPath.departureLine) {
|
|
||||||
// Reverse search for last scenery checkpoint
|
|
||||||
if (currentPath.departureLineData) {
|
|
||||||
for (let i = stopRows.length - 1; i >= 0; i--) {
|
|
||||||
stopRows[i].departureTracks = currentPath.departureLineData.routeTracks;
|
|
||||||
|
|
||||||
stopRows[i].departureSpeed = Math.min(
|
|
||||||
currentPath.departureLineData.routeSpeed,
|
|
||||||
stockVmax
|
|
||||||
);
|
|
||||||
|
|
||||||
if (stopRows[i].isMain || stopRows[i].pointName.endsWith(', podg')) {
|
|
||||||
stopRows[i].departureSpeed = Math.min(
|
|
||||||
currentPath.departureLineData.routeSpeed,
|
|
||||||
stockVmax
|
|
||||||
);
|
|
||||||
|
|
||||||
stopRows[i].departureTracks = currentPath.departureLineData.routeTracks;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
stopRows[i].arrivalSpeed = Math.min(currentPath.departureLineData.routeSpeed, stockVmax);
|
|
||||||
stopRows[i].arrivalTracks = currentPath.departureLineData.routeTracks;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
currentPath = timetablePath[++currentPathIndex];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let timeTo = Date.now();
|
|
||||||
|
|
||||||
globalStore.generatedMs = timeTo - timeFrom;
|
|
||||||
|
|
||||||
return stopRows;
|
|
||||||
});
|
|
||||||
|
|
||||||
function parseTimetablePath(path: string): TimetablePathData[] {
|
|
||||||
return path.split(';').map((pathEl) => {
|
|
||||||
const [arrivalLine, scenery, departureLine] = pathEl.split(',');
|
|
||||||
const sceneryName = scenery.split(' ').slice(0, -1).join(' ');
|
|
||||||
|
|
||||||
const sceneryData = apiStore.sceneryData?.find((sc) => sc.name == sceneryName) ?? null;
|
|
||||||
const arrivalLineData = arrivalLine
|
|
||||||
? sceneryData?.routesInfo.find((rt) => rt.routeName == arrivalLine) ?? null
|
|
||||||
: null;
|
|
||||||
const departureLineData = departureLine
|
|
||||||
? sceneryData?.routesInfo.find((rt) => rt.routeName == departureLine) ?? null
|
|
||||||
: null;
|
|
||||||
|
|
||||||
return {
|
|
||||||
sceneryName,
|
|
||||||
sceneryData: sceneryData ?? null,
|
|
||||||
arrivalLine: arrivalLine ?? '',
|
|
||||||
departureLine: departureLine ?? '',
|
|
||||||
arrivalLineData,
|
|
||||||
departureLineData
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseStopListString(stopsString: string) {
|
|
||||||
//${stop.arrivalLine ?? ''};${stop.arrivalTimestamp};${stop.stopNameRAW};${stop.stopTime ? stop.stopTime + '_' + stop.stopType : ''};${stop.mainStop};${stop.stopDistance};${stop.departureTimestamp};${stop.departureLine ?? ''}
|
|
||||||
return stopsString.split('~~').map((stop) => {
|
|
||||||
const [
|
|
||||||
arrivalLine,
|
|
||||||
arrivalTimestamp,
|
|
||||||
stopNameRAW,
|
|
||||||
stopDetails,
|
|
||||||
isMainStop,
|
|
||||||
stopDistance,
|
|
||||||
departureTimestamp,
|
|
||||||
departureLine
|
|
||||||
] = stop.split(';');
|
|
||||||
const [stopTime, stopType] = stopDetails.split('_');
|
|
||||||
|
|
||||||
return {
|
|
||||||
arrivalLine,
|
|
||||||
arrivalTimestamp: parseInt(arrivalTimestamp),
|
|
||||||
stopNameRAW,
|
|
||||||
stopTime: Number(stopTime ?? 0),
|
|
||||||
stopType: stopType ?? null,
|
|
||||||
mainStop: isMainStop == 'true',
|
|
||||||
stopDistance: parseFloat(stopDistance),
|
|
||||||
departureTimestamp: parseInt(departureTimestamp),
|
|
||||||
departureLine
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<template>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th width="40" class="border border-black dark:border-white">{{ $t('headers.line_no') }}</th>
|
||||||
|
<th width="100" class="border border-black dark:border-white">{{ $t('headers.line_km') }}</th>
|
||||||
|
<th width="35" class="border border-black dark:border-white">V<sub>P</sub></th>
|
||||||
|
<th width="35" class="border border-black dark:border-white">V<sub>L</sub></th>
|
||||||
|
<th width="200" class="border border-black dark:border-white">{{ $t('headers.station') }}</th>
|
||||||
|
<th width="100" class="border border-black dark:border-white">{{ $t('headers.time') }}</th>
|
||||||
|
<th width="50" class="border border-black dark:border-white text-xs p-0">
|
||||||
|
<table class="h-full w-full border-collapse">
|
||||||
|
<tbody>
|
||||||
|
<tr class="border-b border-b-black dark:border-b-white">
|
||||||
|
<td class="">{{ $t('headers.loco_1') }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="border-b border-b-black dark:border-b-white">
|
||||||
|
<td>{{ $t('headers.loco_2') }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ $t('headers.loco_3') }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</th>
|
||||||
|
<th width="55" class="border border-black dark:border-white text-xs relative">
|
||||||
|
<div class="absolute top-0 left-0 w-full h-full">
|
||||||
|
<table class="h-full w-full border-collapse">
|
||||||
|
<tbody>
|
||||||
|
<tr class="border-b border-b-black dark:border-b-white">
|
||||||
|
<td>{{ $t('headers.mass') }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ $t('headers.length') }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th width="50" class="border border-black dark:border-white">{{ $t('headers.vmax') }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
@media print {
|
||||||
|
th, tr {
|
||||||
|
border-color: theme('colors.black');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
<template>
|
||||||
|
<div class="text-white">
|
||||||
|
<!-- <div v-if="apiStore.journalTimetables == null">
|
||||||
|
<div class="font-bold text-xl">{{ $t('storage-empty-header') }}</div>
|
||||||
|
<div>{{ $t('storage-empty-info') }}</div>
|
||||||
|
</div> -->
|
||||||
|
|
||||||
|
<div class="font-bold text-2xl p-2 bg-zinc-800">DZIENNIK SRJP</div>
|
||||||
|
|
||||||
|
<div v-if="apiStore.journalTimetables == null" class="text-md mt-2 p-2 bg-zinc-900">
|
||||||
|
Wyszukaj gracza w polu powyżej, aby pokazać jego najnowsze SRJP.
|
||||||
|
<!-- <div class="text-sm text-red-400 mt-2 flex flex-wrap md:flex-nowrap justify-center items-center gap-1">
|
||||||
|
<ExclamationCircleIcon class="size-8 min-w-8" />
|
||||||
|
<span>
|
||||||
|
Rozkłady z danymi do wygenerowania SRJP są dostępne tylko dla wspierających twórczość
|
||||||
|
<a class="underline hover:text-red-200 transition-colors" href="https://td2.info.pl/profile/?u=20777" target="_blank">@Spythere</a> dla
|
||||||
|
symulatora TD2!
|
||||||
|
</span>
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- <div class="font-bold text-xl p-2 bg-zinc-700 mb-3">{{ $t('storage-preview-title') }}</div> -->
|
||||||
|
<!-- <div class="font-bold p-2 bg-zinc-800 mb-3" v-if="filteredTimetables.length == 0">{{ $t('storage-preview-empty') }}</div> -->
|
||||||
|
|
||||||
|
<li v-for="timetable in apiStore.journalTimetables" class="flex gap-1 w-full my-2">
|
||||||
|
<button class="bg-zinc-900 p-2 w-full cursor-pointer hover:bg-zinc-800 text-left" @click="selectTimetable(timetable.id)">
|
||||||
|
<div class="text-zinc-300">#{{ timetable.id }} • {{ new Date(timetable.createdAt).toLocaleString() }}</div>
|
||||||
|
<b>{{ timetable.driverName }} | {{ timetable.trainCategoryCode }} {{ timetable.trainNo }}</b> {{ timetable.route.replace('|', ' > ') }}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- <button class="bg-zinc-900 p-2 hover:bg-zinc-800" @click="removeTimetable(timetable.timetableId)">
|
||||||
|
<TrashIcon class="size-5 text-white" />
|
||||||
|
</button> -->
|
||||||
|
</li>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { useGlobalStore } from '../../stores/global.store';
|
||||||
|
import { useApiStore } from '../../stores/api.store';
|
||||||
|
import { onMounted } from 'vue';
|
||||||
|
import { ExclamationCircleIcon } from '@heroicons/vue/16/solid';
|
||||||
|
|
||||||
|
const globalStore = useGlobalStore();
|
||||||
|
const apiStore = useApiStore();
|
||||||
|
const i18n = useI18n();
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// apiStore.fetchTimetableHistoryList();
|
||||||
|
});
|
||||||
|
|
||||||
|
function selectTimetable(timetableId: number) {}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
@@ -0,0 +1,247 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex gap-2 mb-2">
|
||||||
|
<div class="relative" @focusin="isMenuOpen = true" @keydown.esc="isMenuOpen = false">
|
||||||
|
<button
|
||||||
|
class="p-1 rounded-md flex gap-2"
|
||||||
|
:class="{
|
||||||
|
'bg-zinc-800 hover:bg-zinc-700': isMenuOpen == false,
|
||||||
|
'bg-green-600 hover:bg-green-500': isMenuOpen == true,
|
||||||
|
}"
|
||||||
|
@click="isMenuOpen = !isMenuOpen"
|
||||||
|
>
|
||||||
|
<Bars3Icon class="size-6" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="fixed z-20 left-0 top-0 w-screen h-screen" v-if="isMenuOpen" @click="isMenuOpen = false"></div>
|
||||||
|
|
||||||
|
<div class="absolute z-30 top-full left-0 w-36 p-1 mt-2 flex flex-col gap-1 bg-zinc-600 rounded-md" v-if="isMenuOpen">
|
||||||
|
<button
|
||||||
|
class="p-1 rounded-md flex gap-2"
|
||||||
|
:class="{
|
||||||
|
'bg-zinc-950 hover:bg-zinc-800': globalStore.viewMode != 'active',
|
||||||
|
'bg-green-600 hover:bg-green-500': globalStore.viewMode == 'active',
|
||||||
|
}"
|
||||||
|
@click="toggleViewMode('active')"
|
||||||
|
>
|
||||||
|
<WifiIcon class="size-6" /> <span> Aktywne RJ</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="p-1 rounded-md flex gap-2"
|
||||||
|
:class="{
|
||||||
|
'bg-zinc-950 hover:bg-zinc-800': globalStore.viewMode != 'storage',
|
||||||
|
'bg-green-600 hover:bg-green-500': globalStore.viewMode == 'storage',
|
||||||
|
}"
|
||||||
|
@click="toggleViewMode('storage')"
|
||||||
|
>
|
||||||
|
<ArchiveBoxArrowDownIcon class="size-6" /> Zapisane RJ
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="p-1 rounded-md flex gap-2"
|
||||||
|
:class="{
|
||||||
|
'bg-zinc-950 hover:bg-zinc-800': globalStore.viewMode != 'journal',
|
||||||
|
'bg-green-600 hover:bg-green-500': globalStore.viewMode == 'journal',
|
||||||
|
}"
|
||||||
|
@click="toggleViewMode('journal')"
|
||||||
|
>
|
||||||
|
<CloudArrowDownIcon class="size-6" /> Dziennik RJ
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- <button class="m-0 p-0" @focus="isMenuOpen = false"></button> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<select
|
||||||
|
name="trains"
|
||||||
|
id="trains-select"
|
||||||
|
class="bg-zinc-800 p-1 rounded-md print:hidden w-full"
|
||||||
|
:disabled="apiStore.activeDataStatus != DataStatus.SUCCESS"
|
||||||
|
v-model="globalStore.selectedTrainId"
|
||||||
|
v-if="globalStore.viewMode == 'active'"
|
||||||
|
@change="selectTrain"
|
||||||
|
>
|
||||||
|
<option :value="null" disabled>
|
||||||
|
{{ apiStore.activeDataStatus == DataStatus.LOADING ? $t('data-loading-text') : $t('train-select-placeholder') }}
|
||||||
|
</option>
|
||||||
|
<option :value="train.id" v-for="train in globalStore.activeTimetableTrains">
|
||||||
|
{{ train.driverName }} | {{ train.timetable?.category }} {{ train.trainNo }} [{{ getRegionNameById(train.region) }}]
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
v-if="globalStore.viewMode == 'storage'"
|
||||||
|
v-model="globalStore.storageTimetableSearch"
|
||||||
|
class="bg-zinc-800 p-1 rounded-md print:hidden w-full"
|
||||||
|
:placeholder="$t('train-search-placeholder')"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div v-if="globalStore.viewMode == 'journal'" class="w-full relative">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
v-model="globalStore.journalTimetableSearch"
|
||||||
|
class="bg-zinc-800 p-1 rounded-md print:hidden w-full"
|
||||||
|
:placeholder="$t('journal-search-placeholder')"
|
||||||
|
@keydown.enter="fetchJournalTrain"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="absolute top-0 right-0">
|
||||||
|
<button class="bg-zinc-800 p-1 rounded-md hover:bg-zinc-700">
|
||||||
|
<MagnifyingGlassIcon class="text-white size-6" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button class="bg-zinc-800 p-1 rounded-md hover:bg-zinc-700">
|
||||||
|
<XMarkIcon class="text-white size-6" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="bg-zinc-800 p-1 rounded-md hover:bg-zinc-700" @click="toggleDarkMode">
|
||||||
|
<MoonIcon v-if="globalStore.darkMode" class="text-white size-6" />
|
||||||
|
<SunIcon v-else class="text-white size-6" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="bg-zinc-800 p-1 rounded-md hover:bg-zinc-700 disabled:opacity-60 disabled:hover:bg-zinc-800"
|
||||||
|
:disabled="globalStore.currentTimetableData == null"
|
||||||
|
@click="openPrintingWindow"
|
||||||
|
>
|
||||||
|
<PrinterIcon class="text-white size-6" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="p-1 rounded-md disabled:opacity-60 disabled:hover:bg-zinc-800"
|
||||||
|
:disabled="globalStore.currentTimetableData == null"
|
||||||
|
:class="{
|
||||||
|
'bg-green-600 hover:bg-green-700': isTimetableSaved,
|
||||||
|
'bg-zinc-800 hover:bg-zinc-700': !isTimetableSaved,
|
||||||
|
}"
|
||||||
|
@click="saveToStorage"
|
||||||
|
>
|
||||||
|
<ArrowDownTrayIcon class="text-white size-6" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
import { useApiStore } from '../../stores/api.store';
|
||||||
|
import { DataStatus } from '../../types/api.types';
|
||||||
|
import { useGlobalStore } from '../../stores/global.store';
|
||||||
|
import {
|
||||||
|
PrinterIcon,
|
||||||
|
MoonIcon,
|
||||||
|
SunIcon,
|
||||||
|
ArchiveBoxArrowDownIcon,
|
||||||
|
ArrowDownTrayIcon,
|
||||||
|
CloudArrowDownIcon,
|
||||||
|
WifiIcon,
|
||||||
|
XMarkIcon,
|
||||||
|
MagnifyingGlassIcon,
|
||||||
|
Bars3Icon,
|
||||||
|
} from '@heroicons/vue/16/solid';
|
||||||
|
import { getRegionNameById } from '../../utils/trainUtils';
|
||||||
|
import type { TimetableData, ViewMode } from '../../types/common.types';
|
||||||
|
|
||||||
|
// Stores
|
||||||
|
const apiStore = useApiStore();
|
||||||
|
const globalStore = useGlobalStore();
|
||||||
|
|
||||||
|
const isMenuOpen = ref(false);
|
||||||
|
|
||||||
|
// Computed
|
||||||
|
const isTimetableSaved = computed(() => {
|
||||||
|
if (!globalStore.currentTimetableData) return false;
|
||||||
|
|
||||||
|
return Object.keys(globalStore.storageTimetables).includes(`${globalStore.currentTimetableData.timetableId}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Methods
|
||||||
|
function selectTrain() {
|
||||||
|
if (!apiStore.activeData) return;
|
||||||
|
|
||||||
|
globalStore.selectedActiveTrain = globalStore.activeTimetableTrains.find((train) => train.id == globalStore.selectedTrainId) ?? null;
|
||||||
|
|
||||||
|
if (globalStore.selectedActiveTrain != null) {
|
||||||
|
globalStore.generatedDate = new Date();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchJournalTrain(e: Event) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
console.log(globalStore.journalTimetableSearch);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleViewMode(viewMode: ViewMode) {
|
||||||
|
if (viewMode == globalStore.viewMode) {
|
||||||
|
switch (viewMode) {
|
||||||
|
case 'active':
|
||||||
|
globalStore.selectedActiveTrain = null;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'storage':
|
||||||
|
globalStore.selectedStorageTimetable = null;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'journal':
|
||||||
|
globalStore.selectedJournalTimetable = null;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isMenuOpen.value = false;
|
||||||
|
globalStore.viewMode = viewMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleDarkMode() {
|
||||||
|
globalStore.darkMode = !globalStore.darkMode;
|
||||||
|
|
||||||
|
window.localStorage.setItem('currentTheme', globalStore.darkMode ? 'dark' : 'light');
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveToStorage() {
|
||||||
|
if (globalStore.currentTimetableData == null) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const savedTimetablesStorage = localStorage.getItem('savedTimetables');
|
||||||
|
let savedTimetablesJSON: Record<number, TimetableData> = savedTimetablesStorage ? JSON.parse(savedTimetablesStorage) : {};
|
||||||
|
|
||||||
|
if (savedTimetablesJSON[globalStore.currentTimetableData.timetableId] !== undefined) {
|
||||||
|
globalStore.selectedStorageTimetable = savedTimetablesJSON[globalStore.currentTimetableData.timetableId];
|
||||||
|
globalStore.viewMode = 'storage';
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
savedTimetablesJSON[globalStore.currentTimetableData.timetableId] = { ...globalStore.currentTimetableData, savedTimestamp: Date.now() };
|
||||||
|
|
||||||
|
localStorage.setItem('savedTimetables', JSON.stringify(savedTimetablesJSON));
|
||||||
|
globalStore.storageTimetables = savedTimetablesJSON;
|
||||||
|
} catch (error) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openPrintingWindow() {
|
||||||
|
if (globalStore.selectedActiveTrain != null) {
|
||||||
|
const date = `${globalStore.generatedDate!.toLocaleDateString('pl-PL').replace(/\./g, '-')}--${globalStore
|
||||||
|
.generatedDate!.toLocaleTimeString('pl-PL')
|
||||||
|
.replace(/:/g, '-')}`;
|
||||||
|
|
||||||
|
document.title = `${globalStore.selectedActiveTrain.driverName} ; ${globalStore.selectedActiveTrain.timetable!.category} ${
|
||||||
|
globalStore.selectedActiveTrain.trainNo
|
||||||
|
}
|
||||||
|
${globalStore.selectedActiveTrain.timetable?.route.replace('|', ' - ')} ; ${date}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.print();
|
||||||
|
}
|
||||||
|
|
||||||
|
// function refreshData() {
|
||||||
|
// apiStore.fetchActiveData();
|
||||||
|
// selectTrain();
|
||||||
|
// }
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
<template>
|
||||||
|
<div class="text-white">
|
||||||
|
<div v-if="globalStore.selectedStorageTimetable == null && Object.keys(globalStore.storageTimetables).length == 0">
|
||||||
|
<div class="font-bold text-2xl p-1 bg-zinc-800">{{ $t('storage-empty-header') }}</div>
|
||||||
|
<div class="text-md mt-2 p-2 bg-zinc-900">{{ $t('storage-empty-info') }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else>
|
||||||
|
<div class="font-bold text-2xl p-2 bg-zinc-800 mb-3">{{ $t('storage-preview-title') }}</div>
|
||||||
|
<div class="font-bold p-2 bg-zinc-800 mb-3" v-if="filteredTimetables.length == 0">{{ $t('storage-preview-empty') }}</div>
|
||||||
|
|
||||||
|
<li v-for="timetable in filteredTimetables" class="flex gap-1 w-full my-2">
|
||||||
|
<button class="bg-zinc-900 p-2 w-full cursor-pointer hover:bg-zinc-800 text-left" @click="selectTimetable(timetable)">
|
||||||
|
<div class="text-zinc-300">#{{ timetable.timetableId }} • {{ new Date(timetable.savedTimestamp!).toLocaleString() }}</div>
|
||||||
|
<b>{{ timetable.driverName }} | {{ timetable.category }} {{ timetable.trainNo }}</b> {{ timetable.route.replace('|', ' > ') }}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button class="bg-zinc-900 p-2 hover:bg-zinc-800" @click="removeTimetable(timetable.timetableId)">
|
||||||
|
<TrashIcon class="size-5 text-white" />
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { TrashIcon } from '@heroicons/vue/16/solid';
|
||||||
|
import { useGlobalStore } from '../../stores/global.store';
|
||||||
|
import type { TimetableData } from '../../types/common.types';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
const globalStore = useGlobalStore();
|
||||||
|
const i18n = useI18n();
|
||||||
|
|
||||||
|
const filteredTimetables = computed(() => {
|
||||||
|
let timetables = Object.values(globalStore.storageTimetables);
|
||||||
|
|
||||||
|
if (globalStore.storageTimetableSearch.length != 0)
|
||||||
|
timetables = timetables.filter((st) =>
|
||||||
|
`${st.timetableId} ${st.driverName} ${st.route} ${st.category} ${st.trainNo}`
|
||||||
|
.toLocaleLowerCase()
|
||||||
|
.includes(globalStore.storageTimetableSearch.toLocaleLowerCase())
|
||||||
|
);
|
||||||
|
|
||||||
|
timetables.sort((a, b) => {
|
||||||
|
return (b.savedTimestamp ?? 0) - (a.savedTimestamp ?? 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
return timetables;
|
||||||
|
});
|
||||||
|
|
||||||
|
function selectTimetable(timetable: TimetableData) {
|
||||||
|
globalStore.selectedStorageTimetable = timetable;
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeTimetable(timetableId: number) {
|
||||||
|
const isConfirmed = confirm(i18n.t('delete-timetable-confirm'));
|
||||||
|
|
||||||
|
if (!isConfirmed) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const savedTimetablesStorage = localStorage.getItem('savedTimetables');
|
||||||
|
let savedTimetablesJSON: Record<number, TimetableData> = savedTimetablesStorage ? JSON.parse(savedTimetablesStorage) : {};
|
||||||
|
delete savedTimetablesJSON[timetableId];
|
||||||
|
|
||||||
|
localStorage.setItem('savedTimetables', JSON.stringify(savedTimetablesJSON));
|
||||||
|
globalStore.storageTimetables = savedTimetablesJSON;
|
||||||
|
} catch (error) {}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<!-- Local -->
|
<div class="my-2 print:hidden" v-if="globalStore.currentTimetableData?.savedTimestamp">
|
||||||
<div class="print:hidden" v-if="globalStore.currentTimetableData?.savedTimestamp">
|
<div class="flex gap-2">
|
||||||
<div class="flex gap-2 mt-1">
|
|
||||||
<div class="flex items-center gap-2 bg-zinc-900 p-1 w-full">
|
<div class="flex items-center gap-2 bg-zinc-900 p-1 w-full">
|
||||||
<div>
|
<div>
|
||||||
<InfoIcon :size="20" />
|
<InformationCircleIcon class="size-5" />
|
||||||
</div>
|
</div>
|
||||||
<i18n-t keypath="storage-preview-info" tag="span">
|
<i18n-t keypath="storage-preview-info" tag="span">
|
||||||
<template v-slot:id>
|
<template v-slot:id>
|
||||||
@@ -14,70 +13,27 @@
|
|||||||
<b>{{ globalStore.currentTimetableData.driverName }}</b>
|
<b>{{ globalStore.currentTimetableData.driverName }}</b>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:date>
|
<template v-slot:date>
|
||||||
<b>{{
|
<b>{{ new Date(globalStore.currentTimetableData.savedTimestamp).toLocaleString() }}</b>
|
||||||
new Date(
|
|
||||||
globalStore.currentTimetableData?.journalCreatedAt ??
|
|
||||||
globalStore.currentTimetableData.savedTimestamp
|
|
||||||
).toLocaleString()
|
|
||||||
}}</b>
|
|
||||||
</template>
|
</template>
|
||||||
</i18n-t>
|
</i18n-t>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button class="font-bold bg-zinc-900 p-1 hover:bg-zinc-800" @click="removeTimetable(globalStore.currentTimetableData.timetableId)">
|
||||||
class="font-bold bg-zinc-900 p-1 hover:bg-zinc-800 rounded-md"
|
<TrashIcon class="text-white size-6" />
|
||||||
@click="removeTimetable(globalStore.currentTimetableData.timetableId)"
|
|
||||||
>
|
|
||||||
<Trash2Icon />
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button class="font-bold bg-zinc-900 p-1 hover:bg-zinc-800" @click="globalStore.selectedStorageTimetable = null">
|
||||||
class="font-bold bg-zinc-900 p-1 hover:bg-zinc-800 rounded-md"
|
<ArrowUturnLeftIcon class="text-white size-6" />
|
||||||
@click="globalStore.selectedStorageTimetable = null"
|
|
||||||
>
|
|
||||||
<Undo2Icon />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Journal -->
|
|
||||||
<div class="print:hidden" v-else-if="globalStore.currentTimetableData?.journalCreatedAt">
|
|
||||||
<div class="flex gap-2 mt-1">
|
|
||||||
<div class="flex items-center gap-2 bg-zinc-900 p-1 w-full">
|
|
||||||
<div>
|
|
||||||
<InfoIcon :size="20" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<i18n-t keypath="journal-preview-info" tag="span">
|
|
||||||
<template v-slot:id>
|
|
||||||
<b>#{{ globalStore.currentTimetableData.timetableId }}</b>
|
|
||||||
</template>
|
|
||||||
<template v-slot:driverName>
|
|
||||||
<b>{{ globalStore.currentTimetableData.driverName }}</b>
|
|
||||||
</template>
|
|
||||||
<template v-slot:date>
|
|
||||||
<b>{{
|
|
||||||
new Date(globalStore.currentTimetableData.journalCreatedAt).toLocaleString()
|
|
||||||
}}</b>
|
|
||||||
</template>
|
|
||||||
</i18n-t>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
class="font-bold bg-zinc-900 p-1 hover:bg-zinc-800 rounded-md"
|
|
||||||
@click="globalStore.selectedJournalTimetable = null"
|
|
||||||
>
|
|
||||||
<Undo2Icon />
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { ArrowUturnLeftIcon, InformationCircleIcon, TrashIcon } from '@heroicons/vue/16/solid';
|
||||||
import { useGlobalStore } from '../../stores/global.store';
|
import { useGlobalStore } from '../../stores/global.store';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import type { TimetableData } from '../../types/common.types';
|
import type { TimetableData } from '../../types/common.types';
|
||||||
import { InfoIcon, Trash2Icon, Undo2Icon } from 'lucide-vue-next';
|
|
||||||
|
|
||||||
const globalStore = useGlobalStore();
|
const globalStore = useGlobalStore();
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
@@ -89,14 +45,12 @@ function removeTimetable(timetableId: number) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const savedTimetablesStorage = localStorage.getItem('savedTimetables');
|
const savedTimetablesStorage = localStorage.getItem('savedTimetables');
|
||||||
let savedTimetablesJSON: Record<number, TimetableData> = savedTimetablesStorage
|
let savedTimetablesJSON: Record<number, TimetableData> = savedTimetablesStorage ? JSON.parse(savedTimetablesStorage) : {};
|
||||||
? JSON.parse(savedTimetablesStorage)
|
|
||||||
: {};
|
|
||||||
delete savedTimetablesJSON[timetableId];
|
delete savedTimetablesJSON[timetableId];
|
||||||
|
|
||||||
localStorage.setItem('savedTimetables', JSON.stringify(savedTimetablesJSON));
|
localStorage.setItem('savedTimetables', JSON.stringify(savedTimetablesJSON));
|
||||||
globalStore.storageTimetables = savedTimetablesJSON;
|
globalStore.storageTimetables = savedTimetablesJSON;
|
||||||
|
|
||||||
globalStore.selectedStorageTimetable = null;
|
globalStore.selectedStorageTimetable = null;
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,252 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
:class="{ dark: globalStore.darkMode }"
|
||||||
|
v-if="globalStore.currentTimetableData != null"
|
||||||
|
class="overflow-auto p-1 bg-white print:bg-white dark:bg-zinc-950 print:text-black text-black dark:text-white min-h-full"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div class="p-1 font-bold w-max">
|
||||||
|
{{ globalStore.currentTimetableData.category }} {{ globalStore.currentTimetableData.trainNo }} {{ $t('headers.relation') }}
|
||||||
|
{{ globalStore.currentTimetableData.route.replace('|', ' - ') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="table-fixed mt-2 w-full border-collapse" v-if="computedTimetableRows.length > 0">
|
||||||
|
<TimetableHeader />
|
||||||
|
<TimetableBody :computed-timetable="computedTimetableRows" />
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="overflow-auto text-center font-bold text-zinc-400 p-1 min-h-full" v-else>
|
||||||
|
<div v-if="globalStore.viewMode == 'active'">
|
||||||
|
<div class="text-xl p-2 bg-zinc-900">{{ $t('train-select-info') }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<TimetableStorage v-else-if="globalStore.viewMode == 'storage'" />
|
||||||
|
<TimetableJournal v-else-if="globalStore.viewMode == 'journal'" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
|
``;
|
||||||
|
import { useApiStore } from '../../stores/api.store';
|
||||||
|
import { useGlobalStore } from '../../stores/global.store';
|
||||||
|
import TimetableBody from './TimetableBody.vue';
|
||||||
|
import TimetableHeader from './TimetableHeader.vue';
|
||||||
|
import type { SceneryRoute, StopRow, TimetablePathData } from '../../types/common.types';
|
||||||
|
import TimetableStorage from './TimetableStorage.vue';
|
||||||
|
import TimetableJournal from './TimetableJournal.vue';
|
||||||
|
|
||||||
|
const globalStore = useGlobalStore();
|
||||||
|
const apiStore = useApiStore();
|
||||||
|
|
||||||
|
// Tymczasowa tabelka z posterunkami APO
|
||||||
|
const apoNames = ['Stary Kisielin, pe', 'Czerwony Dwór, pe', 'Szczejkowice, pe'];
|
||||||
|
|
||||||
|
const computedTimetableRows = computed(() => {
|
||||||
|
const timetableData = globalStore.currentTimetableData;
|
||||||
|
|
||||||
|
if (!timetableData) return [];
|
||||||
|
|
||||||
|
let timeFrom = Date.now();
|
||||||
|
|
||||||
|
const stockVmax = timetableData.trainMaxSpeed,
|
||||||
|
stockMass = Math.floor(timetableData.mass / 1000),
|
||||||
|
stockLength = timetableData.length;
|
||||||
|
|
||||||
|
const timetablePath = parseTimetablePath(timetableData.path);
|
||||||
|
|
||||||
|
const stopRows: StopRow[] = [];
|
||||||
|
|
||||||
|
let currentPathIndex = 0;
|
||||||
|
let currentPath = timetablePath[0];
|
||||||
|
|
||||||
|
let lastDepartureTimestamp = 0;
|
||||||
|
|
||||||
|
let arrivalKm = 0,
|
||||||
|
arrivalSpeed = 0,
|
||||||
|
arrivalTracks = 0,
|
||||||
|
departureSpeed = 0,
|
||||||
|
departureTracks = 2,
|
||||||
|
realLineNo = 0,
|
||||||
|
abbrevs = [] as string[];
|
||||||
|
|
||||||
|
if (currentPath.departureLineData) {
|
||||||
|
departureSpeed = currentPath.departureLineData.routeSpeed;
|
||||||
|
departureTracks = currentPath.departureLineData.routeTracks;
|
||||||
|
|
||||||
|
arrivalSpeed = currentPath.departureLineData.routeSpeed;
|
||||||
|
arrivalTracks = currentPath.departureLineData.routeTracks;
|
||||||
|
|
||||||
|
realLineNo = currentPath.departureLineData?.realLineNo ?? 0;
|
||||||
|
abbrevs = getAbbrevs(currentPath.departureLineData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.debug('=========== ' + timetableData.trainNo + ' ===========');
|
||||||
|
|
||||||
|
const stopList = parseStopListString(timetableData.stopListString);
|
||||||
|
|
||||||
|
for (const stop of stopList) {
|
||||||
|
if (stop.arrivalLine && stop.arrivalLine == currentPath.arrivalLine) {
|
||||||
|
arrivalKm = stop.stopDistance;
|
||||||
|
|
||||||
|
if (currentPath.arrivalLineData) {
|
||||||
|
arrivalSpeed = currentPath.arrivalLineData.routeSpeed;
|
||||||
|
arrivalTracks = currentPath.arrivalLineData.routeTracks;
|
||||||
|
realLineNo = currentPath.arrivalLineData.realLineNo ?? 0;
|
||||||
|
abbrevs = getAbbrevs(currentPath.arrivalLineData);
|
||||||
|
}
|
||||||
|
|
||||||
|
departureSpeed = arrivalSpeed;
|
||||||
|
departureTracks = arrivalTracks;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stop.mainStop || (/^podg|po|pe$/.test(stop.stopNameRAW) && !/^sbl/i.test(stop.stopNameRAW))) {
|
||||||
|
let correctedDepartureSpeed = 0,
|
||||||
|
correctedDepartureTracks = 0;
|
||||||
|
|
||||||
|
const internalRouteInfo = stop.departureLine
|
||||||
|
? currentPath.sceneryData?.routesInfo.find((route) => route.isInternal && route.routeName == stop.departureLine)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
if (internalRouteInfo) {
|
||||||
|
correctedDepartureSpeed = internalRouteInfo.routeSpeed;
|
||||||
|
departureSpeed = internalRouteInfo.routeSpeed;
|
||||||
|
realLineNo = internalRouteInfo.realLineNo ?? realLineNo;
|
||||||
|
abbrevs = getAbbrevs(internalRouteInfo);
|
||||||
|
|
||||||
|
correctedDepartureTracks = internalRouteInfo.routeTracks;
|
||||||
|
departureTracks = internalRouteInfo.routeTracks;
|
||||||
|
|
||||||
|
if (stopRows.length == 0) {
|
||||||
|
arrivalSpeed = departureSpeed;
|
||||||
|
arrivalTracks = departureTracks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let pointAbbrevs = [];
|
||||||
|
if (apoNames.includes(stop.stopNameRAW)) pointAbbrevs.unshift(`APO ${currentPath.sceneryData?.abbr}`);
|
||||||
|
|
||||||
|
let rowData: StopRow = {
|
||||||
|
isMain: stop.mainStop,
|
||||||
|
pointKm: stop.stopDistance.toFixed(3),
|
||||||
|
pointName: stop.stopNameRAW,
|
||||||
|
scheduledArrivalDate: stop.arrivalTimestamp ? new Date(stop.arrivalTimestamp) : null,
|
||||||
|
scheduledDepartureDate: stop.departureTimestamp ? new Date(stop.departureTimestamp) : null,
|
||||||
|
stopTime: stop.stopTime ? (stop.departureTimestamp - stop.arrivalTimestamp) / 60000 : 0,
|
||||||
|
stopType: stop.stopType,
|
||||||
|
sceneryName: currentPath.sceneryName,
|
||||||
|
realLine: realLineNo == 0 ? '' : realLineNo.toString(),
|
||||||
|
driveTime: lastDepartureTimestamp ? stop.arrivalTimestamp - lastDepartureTimestamp : 0,
|
||||||
|
|
||||||
|
abbrevs: [...pointAbbrevs, ...abbrevs],
|
||||||
|
|
||||||
|
arrivalKm: arrivalKm.toFixed(3),
|
||||||
|
departureKm: stop.stopDistance.toFixed(3),
|
||||||
|
|
||||||
|
arrivalSpeed: arrivalSpeed,
|
||||||
|
arrivalTracks: arrivalTracks,
|
||||||
|
|
||||||
|
departureSpeed: departureSpeed,
|
||||||
|
departureTracks: departureTracks,
|
||||||
|
|
||||||
|
headUnits: timetableData.headUnits,
|
||||||
|
stockVmax,
|
||||||
|
stockLength,
|
||||||
|
stockMass,
|
||||||
|
};
|
||||||
|
|
||||||
|
// console.debug(stop.stopNameRAW, stop.departureLine);
|
||||||
|
|
||||||
|
arrivalKm = stop.stopDistance;
|
||||||
|
arrivalSpeed = correctedDepartureSpeed || arrivalSpeed;
|
||||||
|
arrivalTracks = correctedDepartureTracks || arrivalTracks;
|
||||||
|
|
||||||
|
if (stop.departureTimestamp) lastDepartureTimestamp = stop.departureTimestamp;
|
||||||
|
|
||||||
|
stopRows.push(rowData);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stop.departureLine && stop.departureLine == currentPath.departureLine) {
|
||||||
|
// Reverse search for last scenery checkpoint
|
||||||
|
for (let i = stopRows.length - 1; i > 0; i--) {
|
||||||
|
if (currentPath.departureLineData) {
|
||||||
|
stopRows[i].departureTracks = currentPath.departureLineData.routeTracks;
|
||||||
|
stopRows[i].departureSpeed = currentPath.departureLineData.routeSpeed;
|
||||||
|
stopRows[i].realLine = currentPath.departureLineData.realLineNo?.toString() ?? '';
|
||||||
|
|
||||||
|
if (stopRows[i].isMain || stopRows[i].pointName.endsWith(', podg')) {
|
||||||
|
stopRows[i].departureSpeed = currentPath.departureLineData.routeSpeed;
|
||||||
|
stopRows[i].departureTracks = currentPath.departureLineData.routeTracks;
|
||||||
|
|
||||||
|
abbrevs = getAbbrevs(currentPath.departureLineData);
|
||||||
|
stopRows[i].abbrevs = abbrevs;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
stopRows[i].arrivalSpeed = currentPath.departureLineData.routeSpeed;
|
||||||
|
stopRows[i].arrivalTracks = currentPath.departureLineData.routeTracks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentPath = timetablePath[++currentPathIndex];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let timeTo = Date.now();
|
||||||
|
|
||||||
|
globalStore.generatedMs = timeTo - timeFrom;
|
||||||
|
|
||||||
|
return stopRows;
|
||||||
|
});
|
||||||
|
|
||||||
|
function parseTimetablePath(path: string): TimetablePathData[] {
|
||||||
|
return path.split(';').map((pathEl) => {
|
||||||
|
const [arrivalLine, scenery, departureLine] = pathEl.split(',');
|
||||||
|
const sceneryName = scenery.split(' ').slice(0, -1).join(' ');
|
||||||
|
|
||||||
|
const sceneryData = apiStore.sceneryData?.find((sc) => sc.name == sceneryName) ?? null;
|
||||||
|
const arrivalLineData = arrivalLine ? sceneryData?.routesInfo.find((rt) => rt.routeName == arrivalLine) ?? null : null;
|
||||||
|
const departureLineData = departureLine ? sceneryData?.routesInfo.find((rt) => rt.routeName == departureLine) ?? null : null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
sceneryName,
|
||||||
|
sceneryData: sceneryData ?? null,
|
||||||
|
arrivalLine: arrivalLine ?? '',
|
||||||
|
departureLine: departureLine ?? '',
|
||||||
|
arrivalLineData,
|
||||||
|
departureLineData,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseStopListString(stopsString: string) {
|
||||||
|
//${stop.arrivalLine ?? ''};${stop.arrivalTimestamp};${stop.stopNameRAW};${stop.stopTime ? stop.stopTime + '_' + stop.stopType : ''};${stop.mainStop};${stop.stopDistance};${stop.departureTimestamp};${stop.departureLine ?? ''}
|
||||||
|
return stopsString.split('~~').map((stop) => {
|
||||||
|
const [arrivalLine, arrivalTimestamp, stopNameRAW, stopDetails, isMainStop, stopDistance, departureTimestamp, departureLine] = stop.split(';');
|
||||||
|
const [stopTime, stopType] = stopDetails.split('_');
|
||||||
|
|
||||||
|
return {
|
||||||
|
arrivalLine,
|
||||||
|
arrivalTimestamp: parseInt(arrivalTimestamp),
|
||||||
|
stopNameRAW,
|
||||||
|
stopTime: stopTime ?? 0,
|
||||||
|
stopType: stopType ?? null,
|
||||||
|
mainStop: isMainStop == 'true',
|
||||||
|
stopDistance: parseFloat(stopDistance),
|
||||||
|
departureTimestamp: parseInt(departureTimestamp),
|
||||||
|
departureLine,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAbbrevs(routeData: SceneryRoute) {
|
||||||
|
const abbrevs = [];
|
||||||
|
|
||||||
|
if (routeData.isRouteSBL == true) abbrevs.push(`${routeData.routeSpeed > 130 ? '4' : ''}S${routeData.routeTracks == 2 ? 'S' : ''}`);
|
||||||
|
else if (routeData.routeTracks == 2) abbrevs.push('PP');
|
||||||
|
|
||||||
|
return abbrevs;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
<template>
|
|
||||||
<select
|
|
||||||
v-model="globalStore.selectedTrainId"
|
|
||||||
@change="selectTrain"
|
|
||||||
name="trains"
|
|
||||||
id="trains-select"
|
|
||||||
class="bg-zinc-800 p-1 rounded-md print:hidden w-full"
|
|
||||||
:disabled="apiStore.apiDataStatus != DataStatus.SUCCESS"
|
|
||||||
aria-label="Active train select"
|
|
||||||
>
|
|
||||||
<option :value="null" disabled>
|
|
||||||
{{
|
|
||||||
apiStore.apiDataStatus == DataStatus.LOADING
|
|
||||||
? $t('data-loading-text')
|
|
||||||
: $t('train-select-placeholder')
|
|
||||||
}}
|
|
||||||
</option>
|
|
||||||
<option :value="train.id" v-for="train in globalStore.activeTimetableTrains">
|
|
||||||
{{ train.driverName }} | {{ train.timetable?.category }} {{ train.trainNo }} [{{
|
|
||||||
getRegionNameById(train.region)
|
|
||||||
}}]
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { useApiStore } from '../../stores/api.store';
|
|
||||||
import { useGlobalStore } from '../../stores/global.store';
|
|
||||||
import { DataStatus } from '../../types/api.types';
|
|
||||||
import { getRegionNameById } from '../../utils/trainUtils';
|
|
||||||
|
|
||||||
const apiStore = useApiStore();
|
|
||||||
const globalStore = useGlobalStore();
|
|
||||||
|
|
||||||
function selectTrain() {
|
|
||||||
if (!apiStore.activeData) return;
|
|
||||||
|
|
||||||
globalStore.selectedActiveTrain =
|
|
||||||
globalStore.activeTimetableTrains.find((train) => train.id == globalStore.selectedTrainId) ??
|
|
||||||
null;
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="flex gap-2 items-center" v-if="globalStore.currentTimetableData == null">
|
|
||||||
<div class="flex gap-2 w-full">
|
|
||||||
<input
|
|
||||||
v-model="globalStore.journalTimetableSearch.driverName"
|
|
||||||
type="text"
|
|
||||||
@keydown.enter="fetchJournalTimetables"
|
|
||||||
:class="`bg-zinc-800 p-1 rounded-md print:hidden w-full ${
|
|
||||||
apiStore.connectionMode == 'offline' ? 'opacity-35' : ''
|
|
||||||
}`"
|
|
||||||
:disabled="
|
|
||||||
apiStore.journalDataStatus == DataStatus.LOADING || apiStore.connectionMode == 'offline'
|
|
||||||
"
|
|
||||||
:placeholder="$t('journal-driver-search-placeholder')"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<input
|
|
||||||
v-model="globalStore.journalTimetableSearch.route"
|
|
||||||
type="text"
|
|
||||||
@keydown.enter="fetchJournalTimetables"
|
|
||||||
:class="`bg-zinc-800 p-1 rounded-md print:hidden w-full ${
|
|
||||||
apiStore.connectionMode == 'offline' ? 'opacity-35' : ''
|
|
||||||
}`"
|
|
||||||
:disabled="
|
|
||||||
apiStore.journalDataStatus == DataStatus.LOADING || apiStore.connectionMode == 'offline'
|
|
||||||
"
|
|
||||||
:placeholder="$t('journal-route-search-placeholder')"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<input
|
|
||||||
v-model="globalStore.journalTimetableSearch.date"
|
|
||||||
type="date"
|
|
||||||
@keydown.enter="fetchJournalTimetables"
|
|
||||||
:class="`bg-zinc-800 p-1 rounded-md print:hidden w-full ${
|
|
||||||
apiStore.connectionMode == 'offline' ? 'opacity-35' : ''
|
|
||||||
}`"
|
|
||||||
:disabled="
|
|
||||||
apiStore.journalDataStatus == DataStatus.LOADING || apiStore.connectionMode == 'offline'
|
|
||||||
"
|
|
||||||
:placeholder="$t('journal-date-search-placeholder')"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
class="bg-zinc-800 hover:bg-zinc-700 p-1 rounded-md"
|
|
||||||
v-if="globalStore.viewMode == 'journal'"
|
|
||||||
@click="clearSearch"
|
|
||||||
>
|
|
||||||
<Trash2Icon />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
class="bg-zinc-800 hover:bg-zinc-700 p-1 rounded-md"
|
|
||||||
v-if="globalStore.viewMode == 'journal'"
|
|
||||||
@click="fetchJournalTimetables"
|
|
||||||
>
|
|
||||||
<SearchIcon />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { useApiStore } from '../../stores/api.store';
|
|
||||||
import { useGlobalStore } from '../../stores/global.store';
|
|
||||||
import { DataStatus, type JournalTimetablesShortResponse } from '../../types/api.types';
|
|
||||||
import { SearchIcon, Trash2Icon } from 'lucide-vue-next';
|
|
||||||
|
|
||||||
const globalStore = useGlobalStore();
|
|
||||||
const apiStore = useApiStore();
|
|
||||||
|
|
||||||
async function fetchJournalTimetables() {
|
|
||||||
const searchValues = globalStore.journalTimetableSearch;
|
|
||||||
|
|
||||||
let fetchParams: Record<string, any> = {};
|
|
||||||
|
|
||||||
if (searchValues['driverName']) {
|
|
||||||
fetchParams['driverName'] = searchValues['driverName'].trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (searchValues['date']) {
|
|
||||||
let dateFromStr = new Date(searchValues['date']).toISOString();
|
|
||||||
|
|
||||||
let dateTo = new Date(dateFromStr);
|
|
||||||
dateTo.setDate(dateTo.getDate() + 1);
|
|
||||||
|
|
||||||
fetchParams['dateFrom'] = dateFromStr;
|
|
||||||
fetchParams['dateTo'] = dateTo.toISOString();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (searchValues['route']) {
|
|
||||||
const [routeFrom, routeTo] = searchValues['route'].split('-');
|
|
||||||
|
|
||||||
if (routeFrom) {
|
|
||||||
fetchParams['issuedFrom'] = routeFrom.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (routeTo) {
|
|
||||||
fetchParams['terminatingAt'] = routeTo.trim();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchParams['hasStopsDetails'] = 1;
|
|
||||||
fetchParams['returnType'] = 'short';
|
|
||||||
|
|
||||||
try {
|
|
||||||
apiStore.journalDataStatus = DataStatus.LOADING;
|
|
||||||
|
|
||||||
const response = await apiStore.client.get<JournalTimetablesShortResponse>(
|
|
||||||
'api/getTimetables',
|
|
||||||
fetchParams
|
|
||||||
);
|
|
||||||
|
|
||||||
apiStore.journalDataStatus = DataStatus.SUCCESS;
|
|
||||||
apiStore.journalTimetablesData = response;
|
|
||||||
} catch (error) {
|
|
||||||
apiStore.journalDataStatus = DataStatus.ERROR;
|
|
||||||
apiStore.journalTimetablesData = null;
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearSearch() {
|
|
||||||
Object.keys(globalStore.journalTimetableSearch).forEach(
|
|
||||||
(k) => ((globalStore.journalTimetableSearch as any)[k] = '')
|
|
||||||
);
|
|
||||||
|
|
||||||
apiStore.journalTimetablesData = null;
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="flex gap-2" v-if="globalStore.currentTimetableData == null">
|
|
||||||
<div class="w-full">
|
|
||||||
<input
|
|
||||||
v-model="globalStore.localTimetableSearch"
|
|
||||||
type="text"
|
|
||||||
class="bg-zinc-800 p-1 rounded-md print:hidden w-full"
|
|
||||||
:placeholder="$t('train-search-placeholder')"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<button
|
|
||||||
class="bg-zinc-800 hover:bg-zinc-700 p-1 rounded-md"
|
|
||||||
v-if="globalStore.viewMode == 'storage'"
|
|
||||||
@click="clearSearch"
|
|
||||||
>
|
|
||||||
<Trash2Icon />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { Trash2Icon } from 'lucide-vue-next';
|
|
||||||
import { useGlobalStore } from '../../stores/global.store';
|
|
||||||
|
|
||||||
const globalStore = useGlobalStore();
|
|
||||||
|
|
||||||
function clearSearch() {
|
|
||||||
globalStore.localTimetableSearch = '';
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="flex gap-2 flex-col print:hidden">
|
|
||||||
<!-- Top Actions & Modes -->
|
|
||||||
<SearchModeActions />
|
|
||||||
|
|
||||||
<!-- Active Data Search -->
|
|
||||||
<ActiveSearchInput v-if="globalStore.viewMode == 'active'" />
|
|
||||||
|
|
||||||
<!-- Local Storage Search -->
|
|
||||||
<LocalSearchInput v-else-if="globalStore.viewMode == 'storage'" />
|
|
||||||
|
|
||||||
<!-- Journal Serach -->
|
|
||||||
<JournalSearchInput v-else />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { useGlobalStore } from '../../stores/global.store';
|
|
||||||
|
|
||||||
import ActiveSearchInput from './ActiveSearchInput.vue';
|
|
||||||
import JournalSearchInput from './JournalSearchInput.vue';
|
|
||||||
import LocalSearchInput from './LocalSearchInput.vue';
|
|
||||||
import SearchModeActions from './SearchModeActions.vue';
|
|
||||||
|
|
||||||
const globalStore = useGlobalStore();
|
|
||||||
</script>
|
|
||||||
@@ -1,179 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="flex justify-between gap-2">
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<button
|
|
||||||
:class="`p-1 rounded-md ${
|
|
||||||
globalStore.viewMode == 'active'
|
|
||||||
? 'bg-green-600 hover:bg-green-500'
|
|
||||||
: 'bg-zinc-800 hover:bg-zinc-700'
|
|
||||||
}`"
|
|
||||||
@click="toggleViewMode('active')"
|
|
||||||
aria-label="Active data view mode"
|
|
||||||
>
|
|
||||||
<WifiIcon />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
:class="`p-1 rounded-md ${
|
|
||||||
globalStore.viewMode == 'storage'
|
|
||||||
? 'bg-green-600 hover:bg-green-500'
|
|
||||||
: 'bg-zinc-800 hover:bg-zinc-700'
|
|
||||||
}`"
|
|
||||||
@click="toggleViewMode('storage')"
|
|
||||||
aria-label="Storage view mode"
|
|
||||||
>
|
|
||||||
<ArchiveIcon />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
:class="`p-1 rounded-md ${
|
|
||||||
globalStore.viewMode == 'journal'
|
|
||||||
? 'bg-green-600 hover:bg-green-500'
|
|
||||||
: 'bg-zinc-800 hover:bg-zinc-700'
|
|
||||||
}`"
|
|
||||||
@click="toggleViewMode('journal')"
|
|
||||||
aria-label="Journal view mode"
|
|
||||||
>
|
|
||||||
<HistoryIcon />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<button
|
|
||||||
class="bg-zinc-800 p-1 rounded-md hover:bg-zinc-700"
|
|
||||||
@click="toggleDarkMode"
|
|
||||||
aria-label="Dark mode toggle"
|
|
||||||
>
|
|
||||||
<MoonIcon v-if="globalStore.darkMode" />
|
|
||||||
<SunIcon v-else />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
class="bg-zinc-800 p-1 rounded-md hover:bg-zinc-700 disabled:opacity-60 disabled:hover:bg-zinc-800"
|
|
||||||
@click="toggleFullscreenMode()"
|
|
||||||
:disabled="globalStore.currentTimetableData == null"
|
|
||||||
aria-label="Full screen toggle"
|
|
||||||
>
|
|
||||||
<FullscreenIcon :size="24" />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
class="bg-zinc-800 p-1 rounded-md hover:bg-zinc-700 disabled:opacity-60 disabled:hover:bg-zinc-800"
|
|
||||||
:disabled="globalStore.currentTimetableData == null"
|
|
||||||
@click="openPrintingWindow"
|
|
||||||
aria-label="Print mode"
|
|
||||||
>
|
|
||||||
<PrinterIcon />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
class="p-1 rounded-md disabled:opacity-60 disabled:hover:bg-zinc-800"
|
|
||||||
:disabled="globalStore.currentTimetableData == null"
|
|
||||||
:class="{
|
|
||||||
'bg-green-600 hover:bg-green-700': isTimetableSaved,
|
|
||||||
'bg-zinc-800 hover:bg-zinc-700': !isTimetableSaved
|
|
||||||
}"
|
|
||||||
@click="saveToStorage"
|
|
||||||
aria-label="Save timetable to storage"
|
|
||||||
>
|
|
||||||
<FolderDownIcon />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { computed, watch } from 'vue';
|
|
||||||
import { useGlobalStore } from '../../stores/global.store';
|
|
||||||
import type { ViewMode, TimetableData } from '../../types/common.types';
|
|
||||||
import {
|
|
||||||
ArchiveIcon,
|
|
||||||
FolderDownIcon,
|
|
||||||
FullscreenIcon,
|
|
||||||
HistoryIcon,
|
|
||||||
MoonIcon,
|
|
||||||
PrinterIcon,
|
|
||||||
SunIcon,
|
|
||||||
WifiIcon
|
|
||||||
} from 'lucide-vue-next';
|
|
||||||
|
|
||||||
const globalStore = useGlobalStore();
|
|
||||||
|
|
||||||
// Computed
|
|
||||||
const isTimetableSaved = computed(() => {
|
|
||||||
if (!globalStore.currentTimetableData) return false;
|
|
||||||
|
|
||||||
return Object.keys(globalStore.storageTimetables).includes(
|
|
||||||
`${globalStore.currentTimetableData.timetableId}`
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Watchers
|
|
||||||
watch(
|
|
||||||
() => globalStore.selectedActiveTrain,
|
|
||||||
(curr) => {
|
|
||||||
if (curr != null) {
|
|
||||||
globalStore.generatedDate = new Date();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Methods
|
|
||||||
function toggleViewMode(viewMode: ViewMode) {
|
|
||||||
globalStore.viewMode = viewMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleDarkMode() {
|
|
||||||
globalStore.darkMode = !globalStore.darkMode;
|
|
||||||
|
|
||||||
window.localStorage.setItem('currentTheme', globalStore.darkMode ? 'dark' : 'light');
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveToStorage() {
|
|
||||||
if (globalStore.currentTimetableData == null) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const savedTimetablesStorage = localStorage.getItem('savedTimetables');
|
|
||||||
let savedTimetablesJSON: Record<number, TimetableData> = savedTimetablesStorage
|
|
||||||
? JSON.parse(savedTimetablesStorage)
|
|
||||||
: {};
|
|
||||||
|
|
||||||
if (savedTimetablesJSON[globalStore.currentTimetableData.timetableId] !== undefined) {
|
|
||||||
globalStore.selectedStorageTimetable =
|
|
||||||
savedTimetablesJSON[globalStore.currentTimetableData.timetableId];
|
|
||||||
globalStore.viewMode = 'storage';
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
savedTimetablesJSON[globalStore.currentTimetableData.timetableId] = {
|
|
||||||
...globalStore.currentTimetableData,
|
|
||||||
savedTimestamp: Date.now()
|
|
||||||
};
|
|
||||||
|
|
||||||
localStorage.setItem('savedTimetables', JSON.stringify(savedTimetablesJSON));
|
|
||||||
globalStore.storageTimetables = savedTimetablesJSON;
|
|
||||||
} catch (error) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
function openPrintingWindow() {
|
|
||||||
if (globalStore.selectedActiveTrain != null) {
|
|
||||||
const date = `${globalStore
|
|
||||||
.generatedDate!.toLocaleDateString('pl-PL')
|
|
||||||
.replace(/\./g, '-')}--${globalStore
|
|
||||||
.generatedDate!.toLocaleTimeString('pl-PL')
|
|
||||||
.replace(/:/g, '-')}`;
|
|
||||||
|
|
||||||
document.title = `${globalStore.selectedActiveTrain.driverName} ; ${
|
|
||||||
globalStore.selectedActiveTrain.timetable!.category
|
|
||||||
} ${globalStore.selectedActiveTrain.trainNo}
|
|
||||||
${globalStore.selectedActiveTrain.timetable?.route.replace('|', ' - ')} ; ${date}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.print();
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleFullscreenMode() {
|
|
||||||
globalStore.fullscreenMode = !globalStore.fullscreenMode;
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<div v-if="apiStore.connectionMode == 'online'">{{ $t('train-select-info') }}</div>
|
|
||||||
<div v-else class="bg-red-500 text-white p-2">{{ $t('data-offline-mode') }}</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { useApiStore } from '../../stores/api.store';
|
|
||||||
|
|
||||||
const apiStore = useApiStore();
|
|
||||||
</script>
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div
|
|
||||||
class="overflow-auto p-1 bg-white print:bg-white dark:bg-zinc-950 print:text-black text-black dark:text-white min-h-full relative"
|
|
||||||
:class="{ dark: globalStore.darkMode }"
|
|
||||||
>
|
|
||||||
<!-- <button
|
|
||||||
v-if="globalStore.fullscreenMode"
|
|
||||||
class="fixed right-0 top-0 bg-green-600 p-1 m-1 rounded-md hover:bg-green-500 print:hidden"
|
|
||||||
@click="() => (globalStore.fullscreenMode = false)"
|
|
||||||
>
|
|
||||||
<Minimize2Icon :size="22" />
|
|
||||||
</button> -->
|
|
||||||
|
|
||||||
<TimetableContent />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { useGlobalStore } from '../../stores/global.store';
|
|
||||||
import TimetableContent from '../Timetable/TimetableContent.vue';
|
|
||||||
|
|
||||||
const globalStore = useGlobalStore();
|
|
||||||
</script>
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="text-white">
|
|
||||||
<h2
|
|
||||||
class="font-bold p-2 bg-zinc-700 mb-3 text-2xl flex items-center gap-2 justify-center flex-wrap"
|
|
||||||
>
|
|
||||||
<HistoryIcon :size="25" />
|
|
||||||
{{ $t('journal-preview-title') }}
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<div v-if="apiStore.connectionMode == 'offline'" class="bg-red-500 p-2">
|
|
||||||
{{ $t('data-offline-mode') }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else-if="apiStore.journalDataStatus == DataStatus.LOADING" class="bg-zinc-900 p-2">
|
|
||||||
{{ $t('data-loading-text') }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else-if="apiStore.journalDataStatus == DataStatus.ERROR" class="bg-red-500 p-2">
|
|
||||||
{{ $t('data-loading-error-text') }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-else-if="!apiStore.journalTimetablesData"
|
|
||||||
class="text-zinc-400 mt-2"
|
|
||||||
v-html="$t('journal-empty-info')"
|
|
||||||
></div>
|
|
||||||
|
|
||||||
<div v-else-if="apiStore.journalTimetablesData.length == 0">
|
|
||||||
<p class="text-zinc-300 mb-2">
|
|
||||||
{{ $t('journal-no-data') }}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<b class="text-red-300">
|
|
||||||
{{ $t('journal-reminder-text') }}
|
|
||||||
</b>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else>
|
|
||||||
<ul>
|
|
||||||
<li
|
|
||||||
v-for="timetable in apiStore.journalTimetablesData"
|
|
||||||
class="flex gap-1 w-full my-2"
|
|
||||||
@click="fetchTimetableDetails(timetable.id)"
|
|
||||||
>
|
|
||||||
<button class="bg-zinc-900 p-2 w-full cursor-pointer hover:bg-zinc-800 text-left">
|
|
||||||
<div class="text-zinc-300">
|
|
||||||
#{{ timetable.id }} •
|
|
||||||
{{ new Date(timetable.createdAt!).toLocaleString() }}
|
|
||||||
</div>
|
|
||||||
<b>
|
|
||||||
{{ timetable.driverName }} | {{ timetable.trainCategoryCode }}
|
|
||||||
{{ timetable.trainNo }}
|
|
||||||
</b>
|
|
||||||
{{ timetable.route.replace('|', ' > ') }}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div v-if="apiStore.journalTimetablesData.length > 0">
|
|
||||||
<hr class="border-t-2 border-t-gray-500" />
|
|
||||||
|
|
||||||
<p class="text-zinc-400 text-sm">
|
|
||||||
{{ $t('journal-footer-text') }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { HistoryIcon } from 'lucide-vue-next';
|
|
||||||
import { useApiStore } from '../../stores/api.store';
|
|
||||||
import { useGlobalStore } from '../../stores/global.store';
|
|
||||||
import { DataStatus } from '../../types/api.types';
|
|
||||||
import type { JournalTimetableDetailed } from '../../types/common.types';
|
|
||||||
|
|
||||||
const apiStore = useApiStore();
|
|
||||||
const globalStore = useGlobalStore();
|
|
||||||
|
|
||||||
async function fetchTimetableDetails(id: number) {
|
|
||||||
try {
|
|
||||||
const response = await apiStore.client.get<JournalTimetableDetailed[]>('api/getTimetables', {
|
|
||||||
timetableId: id,
|
|
||||||
returnType: 'detailed'
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.length > 0) globalStore.selectedJournalTimetable = response[0];
|
|
||||||
} catch (error) {
|
|
||||||
globalStore.selectedJournalTimetable = null;
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="text-white">
|
|
||||||
<div class="font-bold p-2 bg-zinc-700 mb-3">
|
|
||||||
<div class="text-2xl flex items-center gap-2 justify-center flex-wrap">
|
|
||||||
<ArchiveIcon :size="25" />
|
|
||||||
<span>{{ $t('storage-preview-title') }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-if="
|
|
||||||
globalStore.selectedStorageTimetable == null &&
|
|
||||||
Object.keys(globalStore.storageTimetables).length == 0
|
|
||||||
"
|
|
||||||
class="text-zinc-400"
|
|
||||||
>
|
|
||||||
{{ $t('storage-empty-info') }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="font-bold p-2 bg-zinc-800 mb-3" v-else-if="filteredTimetables.length == 0">
|
|
||||||
{{ $t('storage-preview-empty') }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul v-else>
|
|
||||||
<li v-for="timetable in filteredTimetables" class="flex gap-1 w-full my-2">
|
|
||||||
<button
|
|
||||||
class="bg-zinc-900 p-2 w-full cursor-pointer hover:bg-zinc-800 text-left"
|
|
||||||
@click="selectTimetable(timetable)"
|
|
||||||
>
|
|
||||||
<div class="text-zinc-300 flex gap-x-2 items-center flex-wrap">
|
|
||||||
<span>#{{ timetable.timetableId }}</span>
|
|
||||||
|
|
||||||
<i class="flex items-center gap-1"><ArchiveIcon :size="18" :stroke-width="3" /> {{ new Date(timetable.savedTimestamp!).toLocaleString() }}</i>
|
|
||||||
|
|
||||||
<i
|
|
||||||
v-if="timetable.journalCreatedAt"
|
|
||||||
class="flex items-center gap-0.5"
|
|
||||||
:title="
|
|
||||||
$t('storage-journal-timetable-placeholder', {
|
|
||||||
date: new Date(timetable.journalCreatedAt).toLocaleDateString('pl-PL')
|
|
||||||
})
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<HistoryIcon :size="18" :stroke-width="3" />
|
|
||||||
{{ new Date(timetable.journalCreatedAt).toLocaleDateString('pl-PL') }}
|
|
||||||
</i>
|
|
||||||
</div>
|
|
||||||
<b>{{ timetable.driverName }} | {{ timetable.category }} {{ timetable.trainNo }}</b>
|
|
||||||
{{ timetable.route.replace('|', ' > ') }}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
class="bg-zinc-900 p-2 hover:bg-zinc-800"
|
|
||||||
@click="removeTimetable(timetable.timetableId)"
|
|
||||||
>
|
|
||||||
<Trash2Icon />
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { computed } from 'vue';
|
|
||||||
import { useI18n } from 'vue-i18n';
|
|
||||||
import { useGlobalStore } from '../../stores/global.store';
|
|
||||||
import type { TimetableData } from '../../types/common.types';
|
|
||||||
import { ArchiveIcon, HistoryIcon, Trash2Icon } from 'lucide-vue-next';
|
|
||||||
|
|
||||||
const globalStore = useGlobalStore();
|
|
||||||
const i18n = useI18n();
|
|
||||||
|
|
||||||
const filteredTimetables = computed(() => {
|
|
||||||
let timetables = Object.values(globalStore.storageTimetables);
|
|
||||||
|
|
||||||
if (globalStore.localTimetableSearch.length != 0)
|
|
||||||
timetables = timetables.filter((st) =>
|
|
||||||
`${st.timetableId} ${st.driverName} ${st.route} ${st.category} ${st.trainNo}`
|
|
||||||
.toLocaleLowerCase()
|
|
||||||
.includes(globalStore.localTimetableSearch.toLocaleLowerCase())
|
|
||||||
);
|
|
||||||
|
|
||||||
timetables.sort((a, b) => {
|
|
||||||
return (b.savedTimestamp ?? 0) - (a.savedTimestamp ?? 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
return timetables;
|
|
||||||
});
|
|
||||||
|
|
||||||
function selectTimetable(timetable: TimetableData) {
|
|
||||||
globalStore.selectedStorageTimetable = timetable;
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeTimetable(timetableId: number) {
|
|
||||||
const isConfirmed = confirm(i18n.t('delete-timetable-confirm'));
|
|
||||||
|
|
||||||
if (!isConfirmed) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const savedTimetablesStorage = localStorage.getItem('savedTimetables');
|
|
||||||
let savedTimetablesJSON: Record<number, TimetableData> = savedTimetablesStorage
|
|
||||||
? JSON.parse(savedTimetablesStorage)
|
|
||||||
: {};
|
|
||||||
delete savedTimetablesJSON[timetableId];
|
|
||||||
|
|
||||||
localStorage.setItem('savedTimetables', JSON.stringify(savedTimetablesJSON));
|
|
||||||
|
|
||||||
globalStore.storageTimetables = savedTimetablesJSON;
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
-23
@@ -1,23 +0,0 @@
|
|||||||
export class HttpClient {
|
|
||||||
constructor(private readonly baseURL: string) {}
|
|
||||||
|
|
||||||
async get<T>(url: string, params?: Record<string, any>): Promise<T> {
|
|
||||||
const absoluteURL = new URL(this.baseURL + '/' + url);
|
|
||||||
|
|
||||||
if (params) {
|
|
||||||
Object.keys(params).forEach((key) => {
|
|
||||||
if (params[key] === undefined) return;
|
|
||||||
|
|
||||||
absoluteURL.searchParams.append(key, params[key]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await fetch(absoluteURL);
|
|
||||||
|
|
||||||
if (!data.ok) {
|
|
||||||
throw new Error(`Cannot fetch ${absoluteURL}: ${data.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return data.json();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+26
-52
@@ -1,53 +1,27 @@
|
|||||||
{
|
{
|
||||||
"data-loading-text": "Loading data...",
|
"data-loading-text": "Loading data...",
|
||||||
"data-loading-error-text": "Oops! An error occurent while loading data from the server!",
|
"train-select-placeholder": "Choose active train from the list",
|
||||||
"train-select-placeholder": "Choose active train from the list",
|
"train-select-info": "Choose active train to generate SRJP timetable",
|
||||||
"train-select-info": "Choose active train to generate SRJP timetable",
|
"train-search-placeholder": "Enter TT details (number, route, user)",
|
||||||
"train-search-placeholder": "Enter TT details (number, route, user)",
|
"headers": {
|
||||||
|
"line_no": "Line\nno.",
|
||||||
"update-prompt": {
|
"line_km": "Km",
|
||||||
"line1": "New version of SRJP is available!",
|
"station": "Station",
|
||||||
"line2": "Click here to update the app!"
|
"time": "Time",
|
||||||
},
|
"loco_1": "Loco I",
|
||||||
|
"loco_2": "Loco II",
|
||||||
"data-offline-mode": "You're currently using the offline mode of the SRJP app - server data is unavailable!",
|
"loco_3": "Loco III",
|
||||||
|
"mass": "Loco load",
|
||||||
"headers": {
|
"length": "Train len.",
|
||||||
"line_no": "Line\nno.",
|
"vmax": "Vmax",
|
||||||
"line_km": "Km",
|
"relation": "Route"
|
||||||
"station": "Station",
|
},
|
||||||
"time": "Time",
|
"storage-empty-header": "ARCHIVED TIMETABLES SEARCH MODE",
|
||||||
"loco_1": "Loco I",
|
"storage-empty-info": "Timetables will be shown here after their archiving.",
|
||||||
"loco_2": "Loco II",
|
"storage-preview-title": "ARCHIVED TIMETABLES",
|
||||||
"loco_3": "Loco III",
|
"storage-preview-empty": "No entries found for given parameters",
|
||||||
"mass": "Loco load",
|
"storage-preview-info": "Archived timetable {id} for user {driverName} from: {date}",
|
||||||
"length": "Train len.",
|
"storage-preview-button-text": "Return",
|
||||||
"vmax": "Vmax",
|
"delete-timetable-confirm": "Are you sure that you want to delete this timetable?",
|
||||||
"relation": "Route"
|
"journal-search-placeholder": "Driver nickname"
|
||||||
},
|
}
|
||||||
|
|
||||||
"migrate-info": {
|
|
||||||
"line-1": "Rozkładownik is being moved to a new domain - {0}! You can still use the current website, but it will no longer be updated and will be shut down in the nearest future!",
|
|
||||||
"accept-btn": "ROGER THAT!"
|
|
||||||
},
|
|
||||||
|
|
||||||
"storage-empty-header": "ARCHIVED TIMETABLES SEARCH MODE",
|
|
||||||
"storage-empty-info": "Timetables will be shown here after their archiving.",
|
|
||||||
"storage-preview-title": "ARCHIVED TIMETABLES",
|
|
||||||
"storage-preview-empty": "No entries found for given parameters",
|
|
||||||
"storage-preview-info": "Archived timetable {id} for user {driverName} from: {date}",
|
|
||||||
"storage-preview-button-text": "Return",
|
|
||||||
"storage-journal-timetable-placeholder": "Saved historical timetable from day {date}",
|
|
||||||
"delete-timetable-confirm": "Are you sure that you want to delete this timetable?",
|
|
||||||
|
|
||||||
"journal-preview-title": "TIMETABLES JOURNAL",
|
|
||||||
"journal-empty-info": "Enter timetable details in the text fields above (use at least one field).<br>Up to 15 newest timetables will be shown.",
|
|
||||||
"journal-driver-search-placeholder": "Driver",
|
|
||||||
"journal-date-search-placeholder": "Date",
|
|
||||||
"journal-route-search-placeholder": "Route",
|
|
||||||
"journal-preview-info": "Historical timetable {id} for user {driverName} from: {date}",
|
|
||||||
|
|
||||||
"journal-no-data": "No data for the current search! Check if the data you entered is correct.",
|
|
||||||
"journal-reminder-text": "Warning: detailed timetables data for SRJP purpose are collected since 1st February 2025 and only for users who support Stacjownik project!",
|
|
||||||
"journal-footer-text": "Detailed timetables data for SRJP purpose are collected since 1st February 2025 and only for users who support Stacjownik project!"
|
|
||||||
}
|
|
||||||
+26
-52
@@ -1,53 +1,27 @@
|
|||||||
{
|
{
|
||||||
"data-loading-text": "Ładowanie danych...",
|
"data-loading-text": "Ładowanie danych...",
|
||||||
"data-loading-error-text": "Ups! Wystąpił błąd podczas pobierania danych z serwera!",
|
"train-select-placeholder": "Wybierz pociąg z listy",
|
||||||
"train-select-placeholder": "Wybierz pociąg z listy",
|
"train-select-info": "Wybierz aktywny pociąg, aby wygenerować SRJP",
|
||||||
"train-select-info": "Wybierz aktywny pociąg, aby wygenerować SRJP",
|
"train-search-placeholder": "Wpisz szczegóły RJ (nr, relacja, gracz)",
|
||||||
"train-search-placeholder": "Wpisz szczegóły RJ (nr, relacja, gracz)",
|
"headers": {
|
||||||
|
"line_no": "Nr\nlinii",
|
||||||
"update-prompt": {
|
"line_km": "Km",
|
||||||
"line1": "Nowa wersja SRJP jest dostępna!",
|
"station": "Stacja",
|
||||||
"line2": "Kliknij, aby zaktualizować aplikację!"
|
"time": "Godzina",
|
||||||
},
|
"loco_1": "Lok I",
|
||||||
|
"loco_2": "Lok II",
|
||||||
"data-offline-mode": "Korzystasz z trybu offline aplikacji SRJP - dane serwerowe są niedostępne!",
|
"loco_3": "Lok III",
|
||||||
|
"mass": "Obc. lok.",
|
||||||
"headers": {
|
"length": "Dł. poc.",
|
||||||
"line_no": "Nr\nlinii",
|
"vmax": "Vmax",
|
||||||
"line_km": "Km",
|
"relation": "Relacja"
|
||||||
"station": "Stacja",
|
},
|
||||||
"time": "Godzina",
|
"storage-empty-header": "TRYB WYSZUKIWANA ZAPISANYCH ROZKŁADÓW JAZDY",
|
||||||
"loco_1": "Lok I",
|
"storage-empty-info": "Użyj funkcji zapisu rozkładu jazdy, aby go tutaj wyświetlić.",
|
||||||
"loco_2": "Lok II",
|
"storage-preview-title": "ZAPISANE ROZKŁADY JAZDY",
|
||||||
"loco_3": "Lok III",
|
"storage-preview-empty": "Nie znaleziono żadnych wpisów dla podanych parametrów",
|
||||||
"mass": "Obc. lok.",
|
"storage-preview-info": "Rozkład archiwalny {id} maszynisty {driverName} z dnia {date}",
|
||||||
"length": "Dł. poc.",
|
"storage-preview-button-text": "Powróć",
|
||||||
"vmax": "Vmax",
|
"delete-timetable-confirm": "Czy na pewno chcesz usunąć ten rozkład jazdy z archiwum?",
|
||||||
"relation": "Relacja"
|
"journal-search-placeholder": "Nick maszynisty"
|
||||||
},
|
}
|
||||||
|
|
||||||
"migrate-info": {
|
|
||||||
"line-1": "Rozkładownik zostaje przeniesiony na nową domenę - {0}! Możesz korzystać z obecnej strony, jednak nie będzie ona otrzymywać już aktualizacji i w przyszłości zostanie wyłączona!",
|
|
||||||
"accept-btn": "PRZYJĄŁEM!"
|
|
||||||
},
|
|
||||||
|
|
||||||
"storage-empty-header": "TRYB WYSZUKIWANA ZAPISANYCH ROZKŁADÓW JAZDY",
|
|
||||||
"storage-empty-info": "Użyj funkcji zapisu rozkładu jazdy, aby go tutaj wyświetlić.",
|
|
||||||
"storage-preview-title": "ZAPISANE ROZKŁADY JAZDY",
|
|
||||||
"storage-preview-empty": "Nie znaleziono żadnych wpisów dla podanych parametrów",
|
|
||||||
"storage-preview-info": "Rozkład archiwalny {id} maszynisty {driverName} z dnia {date}",
|
|
||||||
"storage-preview-button-text": "Powróć",
|
|
||||||
"storage-journal-timetable-placeholder": "Zapisany historyczny rozkład jazdy z dnia {date}",
|
|
||||||
"delete-timetable-confirm": "Czy na pewno chcesz usunąć ten rozkład jazdy z archiwum?",
|
|
||||||
|
|
||||||
"journal-preview-title": "DZIENNIK ROZKŁADÓW JAZDY",
|
|
||||||
"journal-empty-info": "Wpisz dane rozkładu korzystając z pól tekstowych powyżej (co najmniej jednego).<br>W przypadku wielu rozkładów jazdy wyświetli się maks. 15 najnowszych.",
|
|
||||||
"journal-driver-search-placeholder": "Maszynista",
|
|
||||||
"journal-date-search-placeholder": "Data",
|
|
||||||
"journal-route-search-placeholder": "Relacja",
|
|
||||||
"journal-preview-info": "Rozkład historyczny {id} maszynisty {driverName} z dnia {date}",
|
|
||||||
|
|
||||||
"journal-no-data": "Brak wyników dla obecnego wyszukiwania! Sprawdź czy wpisałeś poprawnie dane.",
|
|
||||||
"journal-reminder-text": "Uwaga: szczegółowe rozkłady jazdy są zapisywane od 1 lutego 2025r. wyłącznie dla osób wspierających projekt Stacjownika!",
|
|
||||||
"journal-footer-text": "Szczegółowe dane o rozkładach jazdy do wygenerowania SRJP są zbierane od 1 lutego 2025r. wyłącznie dla maszynistów wspierających projekt Stacjownika!"
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import { useApiStore } from '../stores/api.store';
|
|
||||||
|
|
||||||
export function useVehicleMixin() {
|
|
||||||
const apiStore = useApiStore();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets loco load (obc. lok.) in tons - effectively train mass without locomotive or lone locomotive / unit mass
|
|
||||||
*/
|
|
||||||
function getLocoLoad(trainMass: number, stockString: string) {
|
|
||||||
if (!apiStore.vehiclesData) return trainMass;
|
|
||||||
|
|
||||||
const stockArray = stockString.split(';');
|
|
||||||
const headUnitsNames = stockArray.slice(0, 3).filter((v) => /-\d{3,}$/.test(v));
|
|
||||||
|
|
||||||
const headVehicleData = apiStore.vehiclesData.find((v) => v.name == headUnitsNames[0]);
|
|
||||||
|
|
||||||
if (!headVehicleData) return trainMass;
|
|
||||||
|
|
||||||
// 0t load for loco only
|
|
||||||
if (headVehicleData.type.startsWith("loco") && stockArray.length == 1) return 0;
|
|
||||||
else if (headVehicleData.type.startsWith("unit")) return trainMass;
|
|
||||||
|
|
||||||
return Math.min(trainMass, trainMass - headVehicleData.group.weight);
|
|
||||||
}
|
|
||||||
|
|
||||||
return { getLocoLoad };
|
|
||||||
}
|
|
||||||
+48
-63
@@ -1,83 +1,61 @@
|
|||||||
|
import type { AxiosInstance } from 'axios';
|
||||||
|
import axios from 'axios';
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import {
|
import { DataStatus, type ActiveDataResponse, type JournalTimetableShortResponse, type SceneriesDataResponse } from '../types/api.types';
|
||||||
DataStatus,
|
import type { ActiveData, JournalTimetableShort, SceneryData } from '../types/common.types';
|
||||||
type ActiveDataResponse,
|
|
||||||
type SceneriesDataResponse,
|
|
||||||
type VehiclesDataResponse
|
|
||||||
} from '../types/api.types';
|
|
||||||
import type {
|
|
||||||
ActiveData,
|
|
||||||
JournalTimetableShort,
|
|
||||||
SceneryData,
|
|
||||||
VehicleData
|
|
||||||
} from '../types/common.types';
|
|
||||||
import { HttpClient } from '../http';
|
|
||||||
|
|
||||||
let activeDataInterval = -1;
|
|
||||||
|
|
||||||
// Base API URL
|
|
||||||
let baseURL = 'https://stacjownik.spythere.eu';
|
|
||||||
|
|
||||||
switch (import.meta.env.VITE_API_MODE) {
|
|
||||||
case 'development':
|
|
||||||
baseURL = 'http://localhost:3001';
|
|
||||||
break;
|
|
||||||
case 'mocking':
|
|
||||||
baseURL = 'http://localhost:3123';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useApiStore = defineStore('api', {
|
export const useApiStore = defineStore('api', {
|
||||||
state() {
|
state() {
|
||||||
return {
|
return {
|
||||||
client: new HttpClient(baseURL),
|
client: null as AxiosInstance | null,
|
||||||
|
|
||||||
activeData: null as ActiveData | null,
|
activeData: null as ActiveData | null,
|
||||||
sceneryData: null as SceneryData[] | null,
|
sceneryData: null as SceneryData[] | null,
|
||||||
vehiclesData: null as VehicleData[] | null,
|
|
||||||
journalTimetablesData: null as JournalTimetableShort[] | null,
|
journalTimetables: null as JournalTimetableShort[] | null,
|
||||||
|
|
||||||
outdatedTimerId: -1,
|
outdatedTimerId: -1,
|
||||||
isActiveDataOutdated: false,
|
isActiveDataOutdated: false,
|
||||||
|
|
||||||
apiDataStatus: DataStatus.LOADING,
|
activeDataStatus: DataStatus.LOADING,
|
||||||
journalDataStatus: DataStatus.SUCCESS,
|
|
||||||
|
|
||||||
connectionMode: 'online' as 'online' | 'offline'
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
async setupAPIData() {
|
async setupAPIData() {
|
||||||
clearInterval(activeDataInterval);
|
if (this.client != null) return;
|
||||||
|
|
||||||
try {
|
let baseURL = 'https://stacjownik.spythere.eu';
|
||||||
this.apiDataStatus = DataStatus.LOADING;
|
|
||||||
|
|
||||||
await Promise.all([
|
switch (import.meta.env.VITE_API_MODE) {
|
||||||
this.fetchSceneriesData(),
|
case 'development':
|
||||||
this.fetchVehiclesData(),
|
baseURL = 'http://localhost:3001';
|
||||||
this.fetchActiveData()
|
break;
|
||||||
]);
|
case 'mocking':
|
||||||
|
baseURL = 'http://localhost:3123';
|
||||||
this.apiDataStatus = DataStatus.SUCCESS;
|
break;
|
||||||
} catch (error) {
|
default:
|
||||||
this.apiDataStatus = DataStatus.ERROR;
|
break;
|
||||||
console.log('Data fetching error: ', error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
activeDataInterval = setInterval(() => {
|
this.client = axios.create({
|
||||||
|
baseURL,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.fetchSceneriesData();
|
||||||
|
await this.fetchActiveData();
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
this.fetchActiveData();
|
this.fetchActiveData();
|
||||||
}, 25000);
|
}, 25000);
|
||||||
},
|
},
|
||||||
|
|
||||||
async fetchActiveData() {
|
async fetchActiveData() {
|
||||||
try {
|
try {
|
||||||
const response = await this.client.get<ActiveDataResponse>('api/getActiveData');
|
const response = (await this.client!.get<ActiveDataResponse>('/api/getActiveData')).data;
|
||||||
|
|
||||||
this.activeData = response;
|
this.activeData = response;
|
||||||
|
this.activeDataStatus = DataStatus.SUCCESS;
|
||||||
this.isActiveDataOutdated = false;
|
this.isActiveDataOutdated = false;
|
||||||
|
|
||||||
if (this.outdatedTimerId != -1) clearTimeout(this.outdatedTimerId);
|
if (this.outdatedTimerId != -1) clearTimeout(this.outdatedTimerId);
|
||||||
@@ -86,31 +64,38 @@ export const useApiStore = defineStore('api', {
|
|||||||
this.isActiveDataOutdated = true;
|
this.isActiveDataOutdated = true;
|
||||||
}, 60000);
|
}, 60000);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
console.error(error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async fetchSceneriesData() {
|
async fetchSceneriesData() {
|
||||||
try {
|
try {
|
||||||
const response = await this.client.get<SceneriesDataResponse>('api/getSceneries');
|
const response = (await this.client!.get<SceneriesDataResponse>('/api/getSceneries')).data;
|
||||||
|
|
||||||
this.sceneryData = response;
|
this.sceneryData = response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
console.error(error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async fetchVehiclesData() {
|
async fetchTimetableHistoryList() {
|
||||||
try {
|
try {
|
||||||
const response = await this.client.get<VehiclesDataResponse>('api/getVehiclesData');
|
const response = (
|
||||||
|
await this.client!.get<JournalTimetableShortResponse>('/api/getTimetables', {
|
||||||
|
params: {
|
||||||
|
driverName: 'Spythere',
|
||||||
|
returnType: 'short',
|
||||||
|
hasStopsDetails: 1
|
||||||
|
},
|
||||||
|
})
|
||||||
|
).data;
|
||||||
|
|
||||||
this.vehiclesData = response.vehicles.map((v) => ({
|
this.journalTimetables = response;
|
||||||
...v,
|
|
||||||
group: response.vehicleGroups.find((g) => g.id == v.vehicleGroupsId)!
|
// this.sceneryData = response;
|
||||||
}));
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
console.error(error);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
+24
-60
@@ -1,24 +1,17 @@
|
|||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { useApiStore } from './api.store';
|
import { useApiStore } from './api.store';
|
||||||
import type {
|
import type { ActiveTrain, TimetableData, ViewMode } from '../types/common.types';
|
||||||
ActiveTrain,
|
import { unitNameCorrections } from '../utils/trainUtils';
|
||||||
JournalTimetableDetailed,
|
|
||||||
TimetableData,
|
|
||||||
ViewMode
|
|
||||||
} from '../types/common.types';
|
|
||||||
import { getHeadUnits } from '../utils/trainUtils';
|
|
||||||
import { useVehicleMixin } from '../mixins/useVehicleMixin';
|
|
||||||
|
|
||||||
export const useGlobalStore = defineStore('global', {
|
export const useGlobalStore = defineStore('global', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
darkMode: false,
|
darkMode: false,
|
||||||
fullscreenMode: false,
|
|
||||||
viewMode: 'active' as ViewMode,
|
viewMode: 'active' as ViewMode,
|
||||||
|
|
||||||
selectedTrainId: null as string | null,
|
selectedTrainId: null as string | null,
|
||||||
selectedActiveTrain: null as ActiveTrain | null,
|
selectedActiveTrain: null as ActiveTrain | null,
|
||||||
selectedStorageTimetable: null as TimetableData | null,
|
selectedStorageTimetable: null as TimetableData | null,
|
||||||
selectedJournalTimetable: null as JournalTimetableDetailed | null,
|
selectedJournalTimetable: null,
|
||||||
|
|
||||||
storageTimetables: {} as Record<number, TimetableData>,
|
storageTimetables: {} as Record<number, TimetableData>,
|
||||||
|
|
||||||
@@ -27,16 +20,10 @@ export const useGlobalStore = defineStore('global', {
|
|||||||
generatedDate: null as Date | null,
|
generatedDate: null as Date | null,
|
||||||
generatedMs: 0,
|
generatedMs: 0,
|
||||||
|
|
||||||
localTimetableSearch: '',
|
storageTimetableSearch: '',
|
||||||
|
journalTimetableSearch: '',
|
||||||
journalTimetableSearch: {
|
|
||||||
driverName: '',
|
|
||||||
date: '',
|
|
||||||
route: ''
|
|
||||||
},
|
|
||||||
|
|
||||||
showSettings: false,
|
showSettings: false,
|
||||||
isMigrationInfoOpen: false
|
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
activeTimetableTrains() {
|
activeTimetableTrains() {
|
||||||
@@ -44,14 +31,10 @@ export const useGlobalStore = defineStore('global', {
|
|||||||
|
|
||||||
if (!apiStore.activeData) return [];
|
if (!apiStore.activeData) return [];
|
||||||
|
|
||||||
return apiStore.activeData.trains
|
return apiStore.activeData.trains.filter((train) => train.timetable).sort((t1, t2) => t1.driverName.localeCompare(t2.driverName, 'pl-PL'));
|
||||||
.filter((train) => train.timetable)
|
|
||||||
.sort((t1, t2) => t1.driverName.localeCompare(t2.driverName, 'pl-PL'));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
currentTimetableData(): TimetableData | null {
|
currentTimetableData(): TimetableData | null {
|
||||||
const vehicleUtils = useVehicleMixin();
|
|
||||||
|
|
||||||
if (this.viewMode == 'active') {
|
if (this.viewMode == 'active') {
|
||||||
const selectedTrain = this.selectedActiveTrain;
|
const selectedTrain = this.selectedActiveTrain;
|
||||||
|
|
||||||
@@ -59,7 +42,7 @@ export const useGlobalStore = defineStore('global', {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
trainNo: selectedTrain.trainNo,
|
trainNo: selectedTrain.trainNo,
|
||||||
mass: vehicleUtils.getLocoLoad(selectedTrain.mass, selectedTrain.stockString),
|
mass: selectedTrain.mass,
|
||||||
length: selectedTrain.length,
|
length: selectedTrain.length,
|
||||||
driverId: selectedTrain.driverId,
|
driverId: selectedTrain.driverId,
|
||||||
driverName: selectedTrain.driverName,
|
driverName: selectedTrain.driverName,
|
||||||
@@ -75,45 +58,26 @@ export const useGlobalStore = defineStore('global', {
|
|||||||
.filter((stop) => stop.mainStop || /^podg|po|pe$/.test(stop.stopNameRAW))
|
.filter((stop) => stop.mainStop || /^podg|po|pe$/.test(stop.stopNameRAW))
|
||||||
.map(
|
.map(
|
||||||
(stop) =>
|
(stop) =>
|
||||||
`${stop.arrivalLine ?? ''};${stop.arrivalTimestamp};${stop.stopNameRAW};${
|
`${stop.arrivalLine ?? ''};${stop.arrivalTimestamp};${stop.stopNameRAW};${stop.stopTime ? stop.stopTime + '_' + stop.stopType : ''};${
|
||||||
stop.stopTime ? stop.stopTime + '_' + stop.stopType : ''
|
stop.mainStop
|
||||||
};${stop.mainStop};${stop.stopDistance};${stop.departureTimestamp};${
|
};${stop.stopDistance};${stop.departureTimestamp};${stop.departureLine ?? ''}`
|
||||||
stop.departureLine ?? ''
|
|
||||||
}`
|
|
||||||
)
|
)
|
||||||
.join('~~'),
|
.join('~~'),
|
||||||
headUnits: getHeadUnits(selectedTrain.stockString)
|
headUnits: selectedTrain.stockString
|
||||||
};
|
.split(';')
|
||||||
} else if (this.viewMode == 'journal') {
|
.slice(0, 3)
|
||||||
const selectedTimetable = this.selectedJournalTimetable;
|
.filter((s, i) => i == 0 || /-\d+$/.test(s))
|
||||||
|
.map((s) => {
|
||||||
|
const unitName = s.slice(0, s.indexOf('-'));
|
||||||
|
|
||||||
if (!selectedTimetable || !selectedTimetable.stopListString) return null;
|
return unitNameCorrections[unitName] ?? unitName;
|
||||||
|
}),
|
||||||
return {
|
|
||||||
journalCreatedAt: new Date(selectedTimetable.createdAt).getTime(),
|
|
||||||
trainNo: selectedTimetable.trainNo,
|
|
||||||
mass: vehicleUtils.getLocoLoad(
|
|
||||||
selectedTimetable.stockMass,
|
|
||||||
selectedTimetable.stockString
|
|
||||||
),
|
|
||||||
length: selectedTimetable.stockLength,
|
|
||||||
driverId: selectedTimetable.driverId,
|
|
||||||
driverName: selectedTimetable.driverName,
|
|
||||||
category: selectedTimetable.trainCategoryCode,
|
|
||||||
hasDangerousCargo: selectedTimetable.hasDangerousCargo,
|
|
||||||
hasExtraDeliveries: selectedTimetable.hasExtraDeliveries,
|
|
||||||
warningNotes: selectedTimetable.warningNotes,
|
|
||||||
path: selectedTimetable.path,
|
|
||||||
route: selectedTimetable.route,
|
|
||||||
trainMaxSpeed: selectedTimetable.trainMaxSpeed,
|
|
||||||
timetableId: selectedTimetable.id,
|
|
||||||
stopListString: selectedTimetable.stopListString,
|
|
||||||
headUnits: getHeadUnits(selectedTimetable.stockString)
|
|
||||||
};
|
};
|
||||||
} else {
|
} else if (this.viewMode == 'storage') {
|
||||||
return this.selectedStorageTimetable;
|
const selectedStorageTimetable = this.selectedStorageTimetable;
|
||||||
}
|
return selectedStorageTimetable;
|
||||||
}
|
} else return null;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
actions: {}
|
actions: {},
|
||||||
});
|
});
|
||||||
|
|||||||
+1
-12
@@ -32,6 +32,7 @@ body {
|
|||||||
::-webkit-scrollbar-corner {
|
::-webkit-scrollbar-corner {
|
||||||
background: theme('colors.stone.900');
|
background: theme('colors.stone.900');
|
||||||
border-radius: 0 0 theme('borderRadius.md') 0;
|
border-radius: 0 0 theme('borderRadius.md') 0;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tooltips */
|
/* Tooltips */
|
||||||
@@ -85,15 +86,3 @@ body {
|
|||||||
color-scheme: light;
|
color-scheme: light;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Animations */
|
|
||||||
.slide-anim-enter-active,
|
|
||||||
.slide-anim-leave-active {
|
|
||||||
transition: all 250ms ease-in-out;
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-anim-enter-from,
|
|
||||||
.slide-anim-leave-to {
|
|
||||||
transform: translateY(100%);
|
|
||||||
}
|
|
||||||
|
|||||||
+7
-50
@@ -1,57 +1,14 @@
|
|||||||
import type {
|
import type { ActiveData, JournalTimetableDetailed, JournalTimetableShort, SceneryData } from './common.types';
|
||||||
ActiveData,
|
|
||||||
JournalTimetableShort,
|
|
||||||
SceneryData,
|
|
||||||
VehicleGroup,
|
|
||||||
VehicleRestrictions
|
|
||||||
} from './common.types';
|
|
||||||
|
|
||||||
/***
|
|
||||||
* API Data Status
|
|
||||||
* */
|
|
||||||
|
|
||||||
|
|
||||||
export enum DataStatus {
|
|
||||||
'INIT' = -1,
|
|
||||||
'LOADING' = 0,
|
|
||||||
'SUCCESS' = 1,
|
|
||||||
'ERROR' = 2
|
|
||||||
}
|
|
||||||
|
|
||||||
/***
|
|
||||||
* Active Data API
|
|
||||||
* */
|
|
||||||
|
|
||||||
export type ActiveDataResponse = ActiveData;
|
export type ActiveDataResponse = ActiveData;
|
||||||
|
|
||||||
/***
|
|
||||||
* Sceneries API
|
|
||||||
* */
|
|
||||||
|
|
||||||
export type SceneriesDataResponse = SceneryData[];
|
export type SceneriesDataResponse = SceneryData[];
|
||||||
|
|
||||||
/***
|
export type JournalTimetableShortResponse = JournalTimetableShort[];
|
||||||
* Journal API
|
export type JournalTimetableDetailedResponse = JournalTimetableDetailed[];
|
||||||
* */
|
|
||||||
|
|
||||||
export type JournalTimetablesShortResponse = JournalTimetableShort[];
|
export enum DataStatus {
|
||||||
|
'LOADING' = 0,
|
||||||
/***
|
'SUCCESS' = 1,
|
||||||
* Vehicles API
|
'ERROR' = 2,
|
||||||
* */
|
|
||||||
|
|
||||||
export interface VehiclesDataResponse {
|
|
||||||
vehicles: VehicleDataAPI[];
|
|
||||||
vehicleGroups: VehicleGroupAPI[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface VehicleDataAPI {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
type: string;
|
|
||||||
cabinName?: string;
|
|
||||||
restrictions?: VehicleRestrictions;
|
|
||||||
vehicleGroupsId: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface VehicleGroupAPI extends VehicleGroup {}
|
|
||||||
|
|||||||
@@ -119,7 +119,6 @@ export interface SceneryRoute {
|
|||||||
isInternal: boolean;
|
isInternal: boolean;
|
||||||
isRouteSBL: boolean;
|
isRouteSBL: boolean;
|
||||||
routeSpeed: number;
|
routeSpeed: number;
|
||||||
routeSpeedExit?: number;
|
|
||||||
routeLength: number;
|
routeLength: number;
|
||||||
routeTracks: number;
|
routeTracks: number;
|
||||||
hidden?: boolean;
|
hidden?: boolean;
|
||||||
@@ -134,48 +133,20 @@ export interface StopRow {
|
|||||||
stopType: string;
|
stopType: string;
|
||||||
scheduledArrivalDate: Date | null;
|
scheduledArrivalDate: Date | null;
|
||||||
scheduledDepartureDate: Date | null;
|
scheduledDepartureDate: Date | null;
|
||||||
arrivalLineNumber: string;
|
realLine: string;
|
||||||
departureLineNumber: string;
|
|
||||||
driveTime: number;
|
driveTime: number;
|
||||||
abbrevs: string[];
|
abbrevs: string[];
|
||||||
sceneryName: string;
|
sceneryName: string;
|
||||||
arrivalKm: string;
|
arrivalKm: string;
|
||||||
arrivalSpeedL: number;
|
arrivalSpeed: number;
|
||||||
arrivalSpeedP: number;
|
|
||||||
arrivalTracks: number;
|
arrivalTracks: number;
|
||||||
departureKm: string;
|
departureKm: string;
|
||||||
departureSpeedL: number;
|
|
||||||
departureSpeedP: number;
|
|
||||||
departureTracks: number;
|
|
||||||
headUnits: string[];
|
|
||||||
stockVmax: number;
|
|
||||||
stockLength: number;
|
|
||||||
stockMass: number;
|
|
||||||
|
|
||||||
lastRowRef: StopRow | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface StopRowCZ {
|
|
||||||
pointName: string;
|
|
||||||
pointKm: string;
|
|
||||||
isMain: boolean;
|
|
||||||
stopTime: number;
|
|
||||||
stopType: string;
|
|
||||||
scheduledArrivalDate: Date | null;
|
|
||||||
scheduledDepartureDate: Date | null;
|
|
||||||
driveTime: number;
|
|
||||||
sceneryName: string;
|
|
||||||
arrivalSpeed: number;
|
|
||||||
departureSpeed: number;
|
departureSpeed: number;
|
||||||
arrivalTracks: number;
|
|
||||||
departureTracks: number;
|
departureTracks: number;
|
||||||
headUnits: string[];
|
headUnits: string[];
|
||||||
stockVmax: number;
|
stockVmax: number;
|
||||||
stockLength: number;
|
stockLength: number;
|
||||||
stockMass: number;
|
stockMass: number;
|
||||||
|
|
||||||
arrivalDateStr: string;
|
|
||||||
departureDateStr: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TimetablePathData {
|
export interface TimetablePathData {
|
||||||
@@ -252,6 +223,7 @@ export interface JournalTimetableDetailed extends JournalTimetableShort {
|
|||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
stockHistory: string[];
|
stockHistory: string[];
|
||||||
|
hidden: boolean;
|
||||||
routeSceneries: string;
|
routeSceneries: string;
|
||||||
checkpointArrivals: any[];
|
checkpointArrivals: any[];
|
||||||
checkpointDepartures: any[];
|
checkpointDepartures: any[];
|
||||||
@@ -288,48 +260,4 @@ export interface TimetableData {
|
|||||||
stopListString: string;
|
stopListString: string;
|
||||||
headUnits: string[];
|
headUnits: string[];
|
||||||
savedTimestamp?: number;
|
savedTimestamp?: number;
|
||||||
journalCreatedAt?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface VehicleData {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
type: string;
|
|
||||||
group: VehicleGroup;
|
|
||||||
cabinName?: string;
|
|
||||||
restrictions?: VehicleRestrictions;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface VehicleRestrictions {
|
|
||||||
sponsorOnly?: number;
|
|
||||||
teamOnly?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface VehicleGroup {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
speed: number;
|
|
||||||
speedLoaded?: number;
|
|
||||||
speedLoco?: number;
|
|
||||||
length: number;
|
|
||||||
weight: number;
|
|
||||||
cargoTypes?: VehicleCargoType[];
|
|
||||||
locoProps?: VehicleLocoProps;
|
|
||||||
massSpeeds?: VehicleMassSpeeds;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface VehicleCargoType {
|
|
||||||
id: string;
|
|
||||||
weight: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface VehicleLocoProps {
|
|
||||||
coldStart: boolean;
|
|
||||||
doubleManned: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface VehicleMassSpeeds {
|
|
||||||
none: number;
|
|
||||||
cargo?: Record<string, number>;
|
|
||||||
passenger?: Record<string, number>;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
const romanMonthDigits = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X', 'XI', 'XII'];
|
|
||||||
|
|
||||||
export function parseTimetableRunDate(date: Date) {
|
|
||||||
return `${date.getDate()}.${romanMonthDigits[date.getMonth()]}.${date
|
|
||||||
.getFullYear()
|
|
||||||
.toString()
|
|
||||||
.slice(2)}`;
|
|
||||||
}
|
|
||||||
+7
-23
@@ -1,11 +1,3 @@
|
|||||||
const unitNameCorrections: Record<string, string[]> = {
|
|
||||||
'2EN57': ['EN57', 'EN57'],
|
|
||||||
'201E': ['ET22'],
|
|
||||||
'4E': ['EU07'],
|
|
||||||
M62: ['ST44'],
|
|
||||||
CTLR4C: ['ST44']
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getRegionNameById = (id: string) => {
|
export const getRegionNameById = (id: string) => {
|
||||||
switch (id) {
|
switch (id) {
|
||||||
case 'eu':
|
case 'eu':
|
||||||
@@ -28,18 +20,10 @@ export const getRegionNameById = (id: string) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getHeadUnits(stockString: string) {
|
export const unitNameCorrections: Record<string, string> = {
|
||||||
const stockList = stockString.split(';').slice(0, 3);
|
'2EN57': 'EN57',
|
||||||
|
'201E': 'ET22',
|
||||||
return stockList.reduce((acc, unitType, i) => {
|
'4E': 'EU07',
|
||||||
if (i != 0 && !/-\d{3,}$/.test(unitType)) return acc;
|
M62: 'ST44',
|
||||||
|
CTLR4C: 'ST44',
|
||||||
const unitName = unitType.slice(0, unitType.indexOf('-'));
|
};
|
||||||
|
|
||||||
const correctedNames = unitNameCorrections[unitName] ?? [unitName];
|
|
||||||
|
|
||||||
acc.push(...correctedNames);
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, [] as string[]);
|
|
||||||
}
|
|
||||||
+1
-1
@@ -6,6 +6,6 @@ export default {
|
|||||||
},
|
},
|
||||||
darkMode: 'selector',
|
darkMode: 'selector',
|
||||||
plugins: [],
|
plugins: [],
|
||||||
content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
|
purge: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-2
@@ -1,13 +1,14 @@
|
|||||||
{
|
{
|
||||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
|
|
||||||
/* Linting */
|
/* Linting */
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedSideEffectImports": true
|
||||||
"types": ["vite/client", "vite-plugin-pwa/client"]
|
|
||||||
},
|
},
|
||||||
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
|
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
{"root":["./src/http.ts","./src/i18n.ts","./src/main.ts","./src/vite-env.d.ts","./src/mixins/useVehicleMixin.ts","./src/stores/api.store.ts","./src/stores/global.store.ts","./src/types/api.types.ts","./src/types/common.types.ts","./src/utils/dateUtils.ts","./src/utils/trainUtils.ts","./src/App.vue","./src/components/App/MainBottom.vue","./src/components/App/MainContainer.vue","./src/components/App/MigrateInfo.vue","./src/components/App/Navbar.vue","./src/components/App/SettingsCard.vue","./src/components/App/UpdatePrompt.vue","./src/components/Timetable/TimetableContainer.vue","./src/components/Timetable/TimetableContent.vue","./src/components/Timetable/TimetableContentCZ.vue","./src/components/Timetable/TimetableWarnings.vue","./src/components/TimetableSearch/ActiveSearchInput.vue","./src/components/TimetableSearch/JournalSearchInput.vue","./src/components/TimetableSearch/LocalSearchInput.vue","./src/components/TimetableSearch/SearchContainer.vue","./src/components/TimetableSearch/SearchModeActions.vue","./src/components/TimetableViews/ActiveDataView.vue","./src/components/TimetableViews/CurrentTimetableView.vue","./src/components/TimetableViews/JournalStorageView.vue","./src/components/TimetableViews/LocalStorageView.vue"],"version":"5.9.3"}
|
|
||||||
+3
-1
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||||
"target": "ES2022",
|
"target": "ES2022",
|
||||||
"lib": ["ES2023"],
|
"lib": ["ES2023"],
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
@@ -16,7 +17,8 @@
|
|||||||
"strict": true,
|
"strict": true,
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedSideEffectImports": true
|
||||||
},
|
},
|
||||||
"include": ["vite.config.ts"]
|
"include": ["vite.config.ts"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
{"root":["./vite.config.ts"],"version":"5.9.3"}
|
|
||||||
+4
-29
@@ -1,35 +1,10 @@
|
|||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite'
|
||||||
import vue from '@vitejs/plugin-vue';
|
import vue from '@vitejs/plugin-vue'
|
||||||
import { VitePWA } from 'vite-plugin-pwa';
|
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [vue()],
|
||||||
vue(),
|
|
||||||
VitePWA({
|
|
||||||
registerType: 'prompt',
|
|
||||||
workbox: {
|
|
||||||
disableDevLogs: true,
|
|
||||||
globPatterns: ['**/*.{js,css,html,png,svg,jpg,ico}'],
|
|
||||||
cleanupOutdatedCaches: true,
|
|
||||||
runtimeCaching: [
|
|
||||||
{
|
|
||||||
urlPattern: /^https:\/\/stacjownik.spythere.eu\/api\/(getSceneries|getVehiclesData)/i,
|
|
||||||
handler: 'StaleWhileRevalidate',
|
|
||||||
options: {
|
|
||||||
expiration: {
|
|
||||||
maxAgeSeconds: 3600
|
|
||||||
},
|
|
||||||
cacheName: 'stacjownik-api-cache',
|
|
||||||
cacheableResponse: { statuses: [0, 200] }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
devOptions: { enabled: false, suppressWarnings: true }
|
|
||||||
})
|
|
||||||
],
|
|
||||||
server: {
|
server: {
|
||||||
port: 5345
|
port: 5345
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user