mirror of
https://github.com/Spythere/srjp-td2.git
synced 2026-05-03 05:28:12 +00:00
feat: offline mode; PWA
This commit is contained in:
@@ -0,0 +1,93 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2018 Google Inc. All Rights Reserved.
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// If the loader is already loaded, just stop.
|
||||||
|
if (!self.define) {
|
||||||
|
let registry = {};
|
||||||
|
|
||||||
|
// Used for `eval` and `importScripts` where we can't get script URL by other means.
|
||||||
|
// In both cases, it's safe to use a global var because those functions are synchronous.
|
||||||
|
let nextDefineUri;
|
||||||
|
|
||||||
|
const singleRequire = (uri, parentUri) => {
|
||||||
|
uri = new URL(uri + ".js", parentUri).href;
|
||||||
|
return registry[uri] || (
|
||||||
|
|
||||||
|
new Promise(resolve => {
|
||||||
|
if ("document" in self) {
|
||||||
|
const script = document.createElement("script");
|
||||||
|
script.src = uri;
|
||||||
|
script.onload = resolve;
|
||||||
|
document.head.appendChild(script);
|
||||||
|
} else {
|
||||||
|
nextDefineUri = uri;
|
||||||
|
importScripts(uri);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
.then(() => {
|
||||||
|
let promise = registry[uri];
|
||||||
|
if (!promise) {
|
||||||
|
throw new Error(`Module ${uri} didn’t register its module`);
|
||||||
|
}
|
||||||
|
return promise;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
self.define = (depsNames, factory) => {
|
||||||
|
const uri = nextDefineUri || ("document" in self ? document.currentScript.src : "") || location.href;
|
||||||
|
if (registry[uri]) {
|
||||||
|
// Module is already loading or loaded.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let exports = {};
|
||||||
|
const require = depUri => singleRequire(depUri, uri);
|
||||||
|
const specialDeps = {
|
||||||
|
module: { uri },
|
||||||
|
exports,
|
||||||
|
require
|
||||||
|
};
|
||||||
|
registry[uri] = Promise.all(depsNames.map(
|
||||||
|
depName => specialDeps[depName] || require(depName)
|
||||||
|
)).then(deps => {
|
||||||
|
factory(...deps);
|
||||||
|
return exports;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
define(['./workbox-99d8380f'], (function (workbox) { 'use strict';
|
||||||
|
|
||||||
|
self.addEventListener('message', event => {
|
||||||
|
if (event.data && event.data.type === 'SKIP_WAITING') {
|
||||||
|
self.skipWaiting();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The precacheAndRoute() method efficiently caches and responds to
|
||||||
|
* requests for URLs in the manifest.
|
||||||
|
* See https://goo.gl/S9QRab
|
||||||
|
*/
|
||||||
|
workbox.precacheAndRoute([{
|
||||||
|
"url": "index.html",
|
||||||
|
"revision": "0.3s1lnfb7iao"
|
||||||
|
}], {});
|
||||||
|
workbox.cleanupOutdatedCaches();
|
||||||
|
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {
|
||||||
|
allowlist: [/^\/$/]
|
||||||
|
}));
|
||||||
|
self.__WB_DISABLE_DEV_LOGS = true;
|
||||||
|
|
||||||
|
}));
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -25,6 +25,7 @@
|
|||||||
"tailwindcss": "^3.4.17",
|
"tailwindcss": "^3.4.17",
|
||||||
"typescript": "~5.6.2",
|
"typescript": "~5.6.2",
|
||||||
"vite": "^6.0.5",
|
"vite": "^6.0.5",
|
||||||
|
"vite-plugin-pwa": "^1.0.0",
|
||||||
"vue-tsc": "^2.2.0"
|
"vue-tsc": "^2.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+52
-13
@@ -1,5 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="text-white min-h-screen bg-zinc-950">
|
<div class="text-white min-h-screen bg-zinc-950">
|
||||||
|
<!-- PWA update prompt -->
|
||||||
|
<transition name="slide-anim">
|
||||||
|
<UpdatePrompt v-if="needRefresh" @onUpdateClick="updateApp()" />
|
||||||
|
</transition>
|
||||||
|
|
||||||
|
<!-- Content -->
|
||||||
<Navbar />
|
<Navbar />
|
||||||
<MainContainer />
|
<MainContainer />
|
||||||
</div>
|
</div>
|
||||||
@@ -8,10 +14,14 @@
|
|||||||
<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';
|
||||||
|
|
||||||
const originalDocumentTitle = document.title;
|
const originalDocumentTitle = document.title;
|
||||||
|
|
||||||
@@ -19,28 +29,24 @@ 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();
|
||||||
|
setupOfflineMode();
|
||||||
loadStorageTimetables();
|
loadStorageTimetables();
|
||||||
setupAfterPrintClose();
|
setupAfterPrintClose();
|
||||||
|
|
||||||
await apiStore.setupAPIData();
|
await apiStore.setupAPIData();
|
||||||
|
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 updateApp() {
|
||||||
|
updateServiceWorker(true);
|
||||||
|
needRefresh.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
function loadStorageTimetables() {
|
function loadStorageTimetables() {
|
||||||
if (!window.localStorage.getItem('savedTimetables')) return;
|
if (!window.localStorage.getItem('savedTimetables')) return;
|
||||||
|
|
||||||
@@ -73,4 +79,37 @@ 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<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" 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>
|
||||||
@@ -99,9 +99,14 @@
|
|||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
v-else-if="globalStore.viewMode == 'journal'"
|
v-else-if="globalStore.viewMode == 'journal'"
|
||||||
v-model="globalStore.journalTimetableSearch"
|
|
||||||
@change="fetchJournalTimetables"
|
@change="fetchJournalTimetables"
|
||||||
class="bg-zinc-800 p-1 rounded-md print:hidden w-full"
|
v-model="globalStore.journalTimetableSearch"
|
||||||
|
: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-search-placeholder')"
|
:placeholder="$t('journal-search-placeholder')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -18,7 +18,8 @@
|
|||||||
|
|
||||||
<div class="overflow-auto text-center font-bold text-zinc-400 p-1 min-h-full" v-else>
|
<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 v-if="globalStore.viewMode == 'active'">
|
||||||
<div>{{ $t('train-select-info') }}</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>
|
</div>
|
||||||
|
|
||||||
<LocalStorageView v-else-if="globalStore.viewMode == 'storage'" />
|
<LocalStorageView v-else-if="globalStore.viewMode == 'storage'" />
|
||||||
|
|||||||
@@ -4,7 +4,11 @@
|
|||||||
{{ $t('journal-preview-title') }}
|
{{ $t('journal-preview-title') }}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div v-if="apiStore.journalDataStatus == DataStatus.LOADING" class="bg-zinc-900 p-2">
|
<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') }}
|
{{ $t('data-loading-text') }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,14 @@
|
|||||||
"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)",
|
||||||
|
|
||||||
|
"update-prompt": {
|
||||||
|
"line1": "New version of SRJP is available!",
|
||||||
|
"line2": "Click here to update the app!"
|
||||||
|
},
|
||||||
|
|
||||||
|
"data-offline-mode": "You're currently using the offline mode of the SRJP app - server data is unavailable!",
|
||||||
|
|
||||||
"headers": {
|
"headers": {
|
||||||
"line_no": "Line\nno.",
|
"line_no": "Line\nno.",
|
||||||
"line_km": "Km",
|
"line_km": "Km",
|
||||||
@@ -17,6 +25,7 @@
|
|||||||
"vmax": "Vmax",
|
"vmax": "Vmax",
|
||||||
"relation": "Route"
|
"relation": "Route"
|
||||||
},
|
},
|
||||||
|
|
||||||
"storage-empty-header": "ARCHIVED TIMETABLES SEARCH MODE",
|
"storage-empty-header": "ARCHIVED TIMETABLES SEARCH MODE",
|
||||||
"storage-empty-info": "Timetables will be shown here after their archiving.",
|
"storage-empty-info": "Timetables will be shown here after their archiving.",
|
||||||
"storage-preview-title": "ARCHIVED TIMETABLES",
|
"storage-preview-title": "ARCHIVED TIMETABLES",
|
||||||
|
|||||||
@@ -4,6 +4,14 @@
|
|||||||
"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)",
|
||||||
|
|
||||||
|
"update-prompt": {
|
||||||
|
"line1": "Nowa wersja SRJP jest dostępna!",
|
||||||
|
"line2": "Kliknij, aby zaktualizować aplikację!"
|
||||||
|
},
|
||||||
|
|
||||||
|
"data-offline-mode": "Korzystasz z trybu offline aplikacji SRJP - dane serwerowe są niedostępne!",
|
||||||
|
|
||||||
"headers": {
|
"headers": {
|
||||||
"line_no": "Nr\nlinii",
|
"line_no": "Nr\nlinii",
|
||||||
"line_km": "Km",
|
"line_km": "Km",
|
||||||
@@ -17,6 +25,7 @@
|
|||||||
"vmax": "Vmax",
|
"vmax": "Vmax",
|
||||||
"relation": "Relacja"
|
"relation": "Relacja"
|
||||||
},
|
},
|
||||||
|
|
||||||
"storage-empty-header": "TRYB WYSZUKIWANA ZAPISANYCH ROZKŁADÓW JAZDY",
|
"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-empty-info": "Użyj funkcji zapisu rozkładu jazdy, aby go tutaj wyświetlić.",
|
||||||
"storage-preview-title": "ZAPISANE ROZKŁADY JAZDY",
|
"storage-preview-title": "ZAPISANE ROZKŁADY JAZDY",
|
||||||
|
|||||||
+25
-19
@@ -15,6 +15,8 @@ import type {
|
|||||||
} from '../types/common.types';
|
} from '../types/common.types';
|
||||||
import { useGlobalStore } from './global.store';
|
import { useGlobalStore } from './global.store';
|
||||||
|
|
||||||
|
let activeDataInterval = -1;
|
||||||
|
|
||||||
export const useApiStore = defineStore('api', {
|
export const useApiStore = defineStore('api', {
|
||||||
state() {
|
state() {
|
||||||
return {
|
return {
|
||||||
@@ -28,37 +30,41 @@ export const useApiStore = defineStore('api', {
|
|||||||
isActiveDataOutdated: false,
|
isActiveDataOutdated: false,
|
||||||
|
|
||||||
activeDataStatus: DataStatus.LOADING,
|
activeDataStatus: DataStatus.LOADING,
|
||||||
journalDataStatus: DataStatus.SUCCESS
|
journalDataStatus: DataStatus.SUCCESS,
|
||||||
|
|
||||||
|
connectionMode: 'online' as 'online' | 'offline'
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
async setupAPIData() {
|
async setupAPIData() {
|
||||||
if (this.client != null) return;
|
if (this.client == null) {
|
||||||
|
let baseURL = 'https://stacjownik.spythere.eu';
|
||||||
|
|
||||||
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;
|
||||||
|
}
|
||||||
|
|
||||||
switch (import.meta.env.VITE_API_MODE) {
|
this.client = axios.create({
|
||||||
case 'development':
|
baseURL
|
||||||
baseURL = 'http://localhost:3001';
|
});
|
||||||
break;
|
|
||||||
case 'mocking':
|
|
||||||
baseURL = 'http://localhost:3123';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.client = axios.create({
|
clearInterval(activeDataInterval);
|
||||||
baseURL
|
|
||||||
});
|
activeDataInterval = setInterval(() => {
|
||||||
|
this.fetchActiveData();
|
||||||
|
}, 25000);
|
||||||
|
|
||||||
this.fetchSceneriesData();
|
this.fetchSceneriesData();
|
||||||
await this.fetchActiveData();
|
await this.fetchActiveData();
|
||||||
|
|
||||||
setInterval(() => {
|
|
||||||
this.fetchActiveData();
|
|
||||||
}, 25000);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async fetchActiveData() {
|
async fetchActiveData() {
|
||||||
|
|||||||
+12
-1
@@ -32,7 +32,6 @@ 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 */
|
||||||
@@ -86,3 +85,15 @@ 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%);
|
||||||
|
}
|
||||||
|
|||||||
+2
-3
@@ -1,14 +1,13 @@
|
|||||||
{
|
{
|
||||||
"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"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
{"root":["./src/i18n.ts","./src/main.ts","./src/vite-env.d.ts","./src/stores/api.store.ts","./src/stores/global.store.ts","./src/types/api.types.ts","./src/types/common.types.ts","./src/utils/trainUtils.ts","./src/App.vue","./src/components/App/MainBottom.vue","./src/components/App/MainContainer.vue","./src/components/App/Navbar.vue","./src/components/App/SettingsCard.vue","./src/components/App/UpdatePrompt.vue","./src/components/Timetable/TimetableBody.vue","./src/components/Timetable/TimetableHeader.vue","./src/components/Timetable/TimetableSelect.vue","./src/components/Timetable/TimetableWarnings.vue","./src/components/Timetable/TrainTimetable.vue","./src/components/TimetableViews/JournalStorageView.vue","./src/components/TimetableViews/LocalStorageView.vue"],"version":"5.6.3"}
|
||||||
+1
-3
@@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
|
||||||
"target": "ES2022",
|
"target": "ES2022",
|
||||||
"lib": ["ES2023"],
|
"lib": ["ES2023"],
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
@@ -17,8 +16,7 @@
|
|||||||
"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"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
{"root":["./vite.config.ts"],"version":"5.6.3"}
|
||||||
+16
-4
@@ -1,10 +1,22 @@
|
|||||||
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: [vue()],
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
VitePWA({
|
||||||
|
registerType: 'prompt',
|
||||||
|
workbox: {
|
||||||
|
disableDevLogs: true,
|
||||||
|
globPatterns: ['**/*.{js,css,html,png,svg,jpg,ico}'],
|
||||||
|
cleanupOutdatedCaches: true
|
||||||
|
},
|
||||||
|
devOptions: { enabled: true }
|
||||||
|
})
|
||||||
|
],
|
||||||
server: {
|
server: {
|
||||||
port: 5345
|
port: 5345
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user