lokalne fonty; poprawki offline i cachingu pwa

This commit is contained in:
2023-12-21 19:27:27 +01:00
parent 0c6b55146f
commit 2027b85450
23 changed files with 168 additions and 82 deletions
-5
View File
@@ -50,11 +50,6 @@
name="twitter:image" name="twitter:image"
content="https://raw.githubusercontent.com/Spythere/api/main/thumbnails/stacjownik.jpg" content="https://raw.githubusercontent.com/Spythere/api/main/thumbnails/stacjownik.jpg"
/> />
<link
href="https://fonts.googleapis.com/css2?family=Quicksand:wght@500;700&display=swap"
rel="stylesheet"
/>
</head> </head>
<body> <body>
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+34 -34
View File
@@ -37,7 +37,6 @@ import { defineComponent, watch } from 'vue';
import Clock from './components/App/Clock.vue'; import Clock from './components/App/Clock.vue';
import packageInfo from '.././package.json'; import packageInfo from '.././package.json';
import { regions } from './data/options.json';
import { useMainStore } from './store/mainStore'; import { useMainStore } from './store/mainStore';
import StatusIndicator from './components/App/StatusIndicator.vue'; import StatusIndicator from './components/App/StatusIndicator.vue';
@@ -46,6 +45,7 @@ import AppHeader from './components/App/AppHeader.vue';
import axios from 'axios'; import axios from 'axios';
import StorageManager from './managers/storageManager'; import StorageManager from './managers/storageManager';
import { useApiStore } from './store/apiStore'; import { useApiStore } from './store/apiStore';
import { Status } from './typings/common';
export default defineComponent({ export default defineComponent({
components: { components: {
@@ -66,26 +66,10 @@ export default defineComponent({
}), }),
created() { created() {
this.loadLang(); this.init();
this.apiStore.setupAPI();
this.store.isOffline = !window.navigator.onLine;
window.addEventListener('offline', () => {
this.store.isOffline = true;
this.apiStore.activeData = undefined;
this.apiStore.setDataStatuses();
});
window.addEventListener('online', () => {
this.store.isOffline = false;
});
}, },
async mounted() { async mounted() {
this.setReleaseURL();
watch( watch(
() => this.store.blockScroll, () => this.store.blockScroll,
(value) => { (value) => {
@@ -95,23 +79,39 @@ export default defineComponent({
); );
}, },
watch: {
'$route.query.region': {
immediate: true,
handler(regionQuery: string) {
if (regionQuery) {
this.store.region.id =
regions.find(
(reg) =>
reg.id == regionQuery.toLocaleLowerCase() ||
reg.value.toLocaleLowerCase() == regionQuery.toLocaleLowerCase()
)?.id || 'eu';
}
}
}
},
methods: { methods: {
init() {
this.loadLang();
this.setReleaseURL();
this.setupOfflineHandling();
this.apiStore.setupAPI();
},
setupOfflineHandling() {
this.store.isOffline = !window.navigator.onLine;
if (this.store.isOffline) this.handleOfflineMode();
window.addEventListener('offline', this.handleOfflineMode);
window.addEventListener('online', this.handleOnlineMode);
},
handleOfflineMode() {
this.store.isOffline = true;
this.apiStore.stopActiveDataScheduler();
this.apiStore.activeData = undefined;
this.apiStore.dataStatuses.connection = Status.Data.Offline;
},
handleOnlineMode() {
this.store.isOffline = false;
this.apiStore.setupAPI();
},
changeLang(lang: string) { changeLang(lang: string) {
this.$i18n.locale = lang; this.$i18n.locale = lang;
this.currentLang = lang; this.currentLang = lang;
+4 -4
View File
@@ -240,9 +240,9 @@ export default defineComponent({
const trainsDataStatus = statuses.trains; const trainsDataStatus = statuses.trains;
const dispatcherDataStatus = statuses.dispatchers; const dispatcherDataStatus = statuses.dispatchers;
if (this.store.isOffline) { if (connectionStatus == Status.Data.Offline) {
this.setSignalStatus(Status.Data.Initialized); this.setSignalStatus(Status.Data.Offline);
this.indicator.status = Status.Data.Initialized; this.indicator.status = Status.Data.Offline;
this.indicator.message = 'data-status.S1-offline'; this.indicator.message = 'data-status.S1-offline';
return; return;
} }
@@ -293,7 +293,7 @@ export default defineComponent({
this.orangeLight = false; this.orangeLight = false;
this.redBottomLight = false; this.redBottomLight = false;
if (status == Status.Data.Initialized) { if (status == Status.Data.Initialized || status == Status.Data.Offline) {
this.redTopLight = true; this.redTopLight = true;
} }
+15
View File
@@ -59,6 +59,21 @@ export default defineComponent({
'store.region.id': { 'store.region.id': {
handler(regionId) { handler(regionId) {
this.selectedItemIndex = this.regionList.findIndex((reg) => reg.id == regionId); this.selectedItemIndex = this.regionList.findIndex((reg) => reg.id == regionId);
console.log('region id', regionId);
}
},
'$route.query.region': {
immediate: true,
handler(regionQuery: string) {
if (regionQuery) {
this.store.region.id =
regionsJSON.find(
(reg) =>
reg.id == regionQuery.toLocaleLowerCase() ||
reg.value.toLocaleLowerCase() == regionQuery.toLocaleLowerCase()
)?.id || 'eu';
}
} }
} }
}, },
+3 -7
View File
@@ -279,7 +279,7 @@
</table> </table>
</div> </div>
<Loading v-if="!isDataLoaded && stations.length == 0" /> <Loading v-if="apiStore.dataStatuses.sceneries == Status.Loading" />
<div class="no-stations" v-else-if="stations.length == 0"> <div class="no-stations" v-else-if="stations.length == 0">
{{ $t('sceneries.no-stations') }} {{ $t('sceneries.no-stations') }}
@@ -288,7 +288,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, computed, PropType } from 'vue'; import { defineComponent, PropType } from 'vue';
import dateMixin from '../../mixins/dateMixin'; import dateMixin from '../../mixins/dateMixin';
import stationInfoMixin from '../../mixins/stationInfoMixin'; import stationInfoMixin from '../../mixins/stationInfoMixin';
import styleMixin from '../../mixins/styleMixin'; import styleMixin from '../../mixins/styleMixin';
@@ -330,12 +330,8 @@ export default defineComponent({
const apiStore = useApiStore(); const apiStore = useApiStore();
const stationFiltersStore = useStationFiltersStore(); const stationFiltersStore = useStationFiltersStore();
const isDataLoaded = computed(() => {
return apiStore.dataStatuses.sceneries != Status.Data.Loading;
});
return { return {
isDataLoaded, Status: Status.Data,
stationFiltersStore, stationFiltersStore,
mainStore, mainStore,
apiStore apiStore
+5 -6
View File
@@ -5,13 +5,12 @@
{{ $t('app.offline') }} {{ $t('app.offline') }}
</div> </div>
<Loading v-else-if="trains.length == 0 && apiStore.dataStatuses.trains == 0" key="loading" /> <Loading
v-else-if="trains.length == 0 && apiStore.dataStatuses.connection == 0"
key="loading"
/>
<div <div class="table-info" key="no-trains" v-else-if="trains.length == 0">
class="table-info"
key="no-trains"
v-else-if="trains.length == 0 && apiStore.dataStatuses.trains != 0"
>
{{ $t('trains.no-trains') }} {{ $t('trains.no-trains') }}
</div> </div>
+10 -3
View File
@@ -18,7 +18,8 @@ const routes: Array<RouteRecordRaw> = [
props: (route) => ({ props: (route) => ({
train: route.query.train, train: route.query.train,
driver: route.query.driver, driver: route.query.driver,
trainId: route.query.trainId trainId: route.query.trainId,
region: route.query.region
}) })
}, },
{ {
@@ -37,12 +38,18 @@ const routes: Array<RouteRecordRaw> = [
{ {
path: '/journal/timetables', path: '/journal/timetables',
name: 'JournalTimetables', name: 'JournalTimetables',
component: JournalTimetablesVue component: JournalTimetablesVue,
props: (route) => ({
region: route.query.region
})
}, },
{ {
path: '/journal/dispatchers', path: '/journal/dispatchers',
name: 'JournalDispatchers', name: 'JournalDispatchers',
component: JournalDispatchersVue component: JournalDispatchersVue,
props: (route) => ({
region: route.query.region
})
}, },
{ {
path: '/:catchAll(.*)', path: '/:catchAll(.*)',
+39 -19
View File
@@ -18,7 +18,9 @@ export const useApiStore = defineStore('apiStore', {
activeData: undefined as API.ActiveData.Response | undefined, activeData: undefined as API.ActiveData.Response | undefined,
rollingStockData: undefined as API.RollingStock.Response | undefined, rollingStockData: undefined as API.RollingStock.Response | undefined,
donatorsData: [] as API.Donators.Response, donatorsData: [] as API.Donators.Response,
sceneryData: [] as StationJSONData[] sceneryData: [] as StationJSONData[],
activeDataTimeout: undefined as number | undefined
}), }),
actions: { actions: {
@@ -28,22 +30,32 @@ export const useApiStore = defineStore('apiStore', {
this.fetchDonatorsData(); this.fetchDonatorsData();
this.fetchStationsGeneralInfo(); this.fetchStationsGeneralInfo();
this.scheduleFetchActiveData(); if (this.activeDataTimeout === undefined) this.startActiveDataScheduler();
}, },
async setDataStatuses() { // async setDataStatuses() {
if (!this.activeData?.activeSceneries) { // if (!window.navigator.onLine) {
this.dataStatuses.sceneries = Status.Data.Error; // this.dataStatuses.connection = Status.Data.Offline;
this.dataStatuses.trains = Status.Data.Error; // this.dataStatuses.sceneries = Status.Data.Offline;
this.dataStatuses.dispatchers = Status.Data.Error; // this.dataStatuses.trains = Status.Data.Offline;
// this.dataStatuses.dispatchers = Status.Data.Offline;
// this.dataStatuses.timetables = Status.Data.Offline;
// }
return; // if (!this.activeData?.activeSceneries) {
} // this.dataStatuses.connection = Status.Data.Loaded;
// this.dataStatuses.sceneries = Status.Data.Error;
// this.dataStatuses.trains = Status.Data.Error;
// this.dataStatuses.dispatchers = Status.Data.Error;
this.dataStatuses.sceneries = Status.Data.Loaded; // return;
this.dataStatuses.trains = !this.activeData.trains ? Status.Data.Warning : Status.Data.Loaded; // }
this.dataStatuses.dispatchers = Status.Data.Loaded;
}, // this.dataStatuses.connection = Status.Data.Loaded;
// this.dataStatuses.sceneries = Status.Data.Loaded;
// this.dataStatuses.trains = !this.activeData.trains ? Status.Data.Warning : Status.Data.Loaded;
// this.dataStatuses.dispatchers = Status.Data.Loaded;
// },
async fetchDonatorsData() { async fetchDonatorsData() {
try { try {
@@ -67,12 +79,16 @@ export const useApiStore = defineStore('apiStore', {
} }
}, },
async scheduleFetchActiveData() { async startActiveDataScheduler() {
if (!window.navigator.onLine) {
this.dataStatuses.connection = Status.Data.Offline;
return;
}
if (import.meta.env.VITE_API_MODE === 'mock') { if (import.meta.env.VITE_API_MODE === 'mock') {
const mockActiveData = await import('../data/mockActiveData.json'); const mockActiveData = await import('../data/mockActiveData.json');
this.dataStatuses.connection = Status.Data.Loaded; this.dataStatuses.connection = Status.Data.Loaded;
this.activeData = mockActiveData; this.activeData = mockActiveData;
this.setDataStatuses();
console.warn('Stacjownik działa w trybie mockowania danych z WS'); console.warn('Stacjownik działa w trybie mockowania danych z WS');
@@ -84,21 +100,24 @@ export const useApiStore = defineStore('apiStore', {
this.activeData = data; this.activeData = data;
this.dataStatuses.connection = Status.Data.Loaded; this.dataStatuses.connection = Status.Data.Loaded;
this.setDataStatuses();
} catch (error) { } catch (error) {
this.dataStatuses.connection = Status.Data.Error; this.dataStatuses.connection = Status.Data.Error;
console.error('Wystąpił błąd podczas pobierania danych online z API!'); console.error('Wystąpił błąd podczas pobierania danych online z API!');
} finally { } finally {
setTimeout( this.activeDataTimeout = window.setTimeout(
() => { () => {
this.scheduleFetchActiveData(); this.startActiveDataScheduler();
}, },
~~(1000 * (Math.random() * (25 - 20) + 25)) ~~(1000 * (Math.random() * (25 - 20) + 25))
); );
} }
}, },
async stopActiveDataScheduler() {
window.clearTimeout(this.activeDataTimeout);
this.activeDataTimeout = undefined;
},
async fetchStationsGeneralInfo() { async fetchStationsGeneralInfo() {
const sceneryData: StationJSONData[] = (await http.get<StationJSONData[]>('api/getSceneries')) const sceneryData: StationJSONData[] = (await http.get<StationJSONData[]>('api/getSceneries'))
.data; .data;
@@ -108,6 +127,7 @@ export const useApiStore = defineStore('apiStore', {
return; return;
} }
this.dataStatuses.sceneries = Status.Data.Loaded;
this.sceneryData = sceneryData; this.sceneryData = sceneryData;
} }
} }
+2 -1
View File
@@ -5,6 +5,7 @@
overflow-y: auto; overflow-y: auto;
height: 90vh; height: 90vh;
min-height: 550px; min-height: 550px;
margin-top: 0.5em;
padding-right: 0.2em; padding-right: 0.2em;
} }
@@ -24,7 +25,7 @@
text-align: end; text-align: end;
padding: 0.25em; padding: 0.25em;
margin: 0.5em 0; margin-top: 0.5em;
} }
.journal_warning { .journal_warning {
+49
View File
@@ -0,0 +1,49 @@
@font-face {
font-family: 'Quicksand';
src:
url('/fonts/Quicksand-Bold.woff2') format('woff2'),
url('/fonts/Quicksand-Bold.woff') format('woff');
font-weight: bold;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Quicksand';
src:
url('/fonts/Quicksand-SemiBold.woff2') format('woff2'),
url('/fonts/Quicksand-SemiBold.woff') format('woff');
font-weight: 600;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Quicksand';
src:
url('/fonts/Quicksand-Medium.woff2') format('woff2'),
url('/fonts/Quicksand-Medium.woff') format('woff');
font-weight: 500;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Quicksand';
src:
url('/fonts/Quicksand-Regular.woff2') format('woff2'),
url('/fonts/Quicksand-Regular.woff') format('woff');
font-weight: normal;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Quicksand';
src:
url('/fonts/Quicksand-Light.woff2') format('woff2'),
url('/fonts/Quicksand-Light.woff') format('woff');
font-weight: 300;
font-style: normal;
font-display: swap;
}
+2
View File
@@ -1,3 +1,5 @@
@import 'fonts.scss';
:root { :root {
--clr-primary: #ffc014; --clr-primary: #ffc014;
--clr-secondary: #2f2f2f; --clr-secondary: #2f2f2f;
+1 -1
View File
@@ -11,7 +11,7 @@ export namespace Status {
} }
export enum Data { export enum Data {
Offline = 2, Offline = -2,
Initialized = -1, Initialized = -1,
Loading = 0, Loading = 0,
Error = 1, Error = 1,
+4 -2
View File
@@ -6,20 +6,22 @@ export default defineConfig({
server: { server: {
port: 5001 port: 5001
}, },
publicDir: 'public',
plugins: [ plugins: [
vue(), vue(),
VitePWA({ VitePWA({
registerType: 'autoUpdate', registerType: 'autoUpdate',
includeAssets: ['/images/*.png', '/fonts/*.woff', '/fonts/*.woff2'],
workbox: { workbox: {
disableDevLogs: true,
globPatterns: ['**/*.{js,css,html,png,svg,jpg}'], globPatterns: ['**/*.{js,css,html,png,svg,jpg}'],
runtimeCaching: [ runtimeCaching: [
{ {
urlPattern: new RegExp('^https://stacjownik.spythere.pl/api/getSceneries', 'i'), urlPattern: new RegExp('^https://stacjownik.spythere.eu/api/getSceneries', 'i'),
handler: 'NetworkFirst', handler: 'NetworkFirst',
options: { options: {
cacheName: 'sceneries-cache', cacheName: 'sceneries-cache',
cacheableResponse: { cacheableResponse: {
statuses: [0, 200] statuses: [0, 200]
} }