diff --git a/.gitignore b/.gitignore index 65da70e..555e66e 100644 --- a/.gitignore +++ b/.gitignore @@ -15,13 +15,12 @@ pnpm-debug.log* # Editor directories and files .idea -.vscode *.suo *.ntvs* *.njsproj *.sln *.sw? -node_modules +.vscode/settings.json *.log diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..6b858bb --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://json.schemastore.org/prettierrc", + "tabWidth": 2, + "singleQuote": true, + "printWidth": 100, + "trailingComma": "none" +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..da9331c --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["Vue.volar", "esbenp.prettier-vscode"] +} diff --git a/env.d.ts b/env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/package.json b/package.json index d51d4db..cc42052 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "stacjownik", - "version": "1.33.0", + "version": "1.34.0", "private": true, "type": "module", "scripts": { @@ -26,12 +26,12 @@ "vue-router": "^4.4.0" }, "devDependencies": { - "@types/node": "^24.3.1", + "@tsconfig/node24": "^24.0.4", + "@types/node": "^24.12.0", "@types/showdown": "^2.0.6", "@vite-pwa/assets-generator": "^1.0.0", "@vitejs/plugin-vue": "^6.0.1", "@vue/tsconfig": "^0.8.1", - "axios": "^1.9.0", "prettier": "^3.3.3", "typescript": "^5.5.4", "vite": "^7.1.4", diff --git a/src/App.vue b/src/App.vue index 2ebcbe3..dd2dab0 100644 --- a/src/App.vue +++ b/src/App.vue @@ -30,7 +30,6 @@ + + diff --git a/src/components/SceneryView/utils.ts b/src/components/SceneryView/utils.ts index cc116e9..6912661 100644 --- a/src/components/SceneryView/utils.ts +++ b/src/components/SceneryView/utils.ts @@ -18,23 +18,31 @@ export function getTrainStopStatus( return StopStatus.TERMINATED; } - if (!stopInfo.terminatesHere && stopInfo.confirmed && currentStationName == sceneryName) { + if ( + !stopInfo.terminatesHere && + stopInfo.confirmed && + currentStationName.startsWith(sceneryName) + ) { return StopStatus.DEPARTED; } - if (!stopInfo.terminatesHere && stopInfo.confirmed && currentStationName != sceneryName) { + if ( + !stopInfo.terminatesHere && + stopInfo.confirmed && + !currentStationName.startsWith(sceneryName) + ) { return StopStatus.DEPARTED_AWAY; } - if (currentStationName == sceneryName && !stopInfo.stopped) { + if (currentStationName.startsWith(sceneryName) && !stopInfo.stopped) { return StopStatus.ONLINE; } - if (currentStationName == sceneryName && stopInfo.stopped) { + if (currentStationName.startsWith(sceneryName) && stopInfo.stopped) { return StopStatus.STOPPED; } - if (currentStationName != sceneryName) { + if (!currentStationName.startsWith(sceneryName)) { return StopStatus.ARRIVING; } diff --git a/src/components/StationsView/StationStats.vue b/src/components/StationsView/StationStats.vue index a73a4e8..ebcfcf2 100644 --- a/src/components/StationsView/StationStats.vue +++ b/src/components/StationsView/StationStats.vue @@ -278,6 +278,10 @@ export default defineComponent({ color: #ccc; } +.dropdown_wrapper { + top: 2.5em; +} + @include responsive.smallScreen { .stats-title { text-align: center; @@ -286,5 +290,9 @@ export default defineComponent({ .filter-button > span { display: none; } + + .no-data { + text-align: center; + } } diff --git a/src/components/TrainsView/TrainOptions.vue b/src/components/TrainsView/TrainOptions.vue index 8805099..1a3ac1d 100644 --- a/src/components/TrainsView/TrainOptions.vue +++ b/src/components/TrainsView/TrainOptions.vue @@ -210,6 +210,10 @@ export default defineComponent({ @use '../../styles/dropdown'; @use '../../styles/dropdown-filters'; +.dropdown_wrapper { + top: 2.5em; +} + .search_content > div { margin: 0.5em auto; } diff --git a/src/components/TrainsView/TrainStats.vue b/src/components/TrainsView/TrainStats.vue index 1c73f55..60f506c 100644 --- a/src/components/TrainsView/TrainStats.vue +++ b/src/components/TrainsView/TrainStats.vue @@ -250,9 +250,10 @@ h3 { .dropdown_wrapper { max-width: 600px; + top: 2.5em; } -@include responsive.smallScreen{ +@include responsive.smallScreen { .no-data { text-align: center; } diff --git a/src/http.ts b/src/http.ts new file mode 100644 index 0000000..a1df38b --- /dev/null +++ b/src/http.ts @@ -0,0 +1,23 @@ +export class HttpClient { + constructor(private readonly baseURL: string) {} + + async get(url: string, params?: Record): Promise { + 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.statusText}`); + } + + return data.json(); + } +} diff --git a/src/i18n.ts b/src/i18n.ts index bf1b9b2..fa46716 100644 --- a/src/i18n.ts +++ b/src/i18n.ts @@ -3,12 +3,35 @@ import plLang from './locales/pl.json'; import { createI18n } from 'vue-i18n'; +function customRule(choice: number, choicesLength: number) { + if (choice === 0) { + return 0; + } + + const teen = choice > 10 && choice < 20; + const endsWithOne = choice % 10 === 1; + + if (!teen && endsWithOne) { + return 1; + } + + if (!teen && choice % 10 >= 2 && choice % 10 <= 4) { + return 2; + } + + return choicesLength < 4 ? 2 : 3; +} + const i18n = createI18n({ locale: 'pl', legacy: false, warnHtmlMessage: false, fallbackLocale: 'pl', + pluralizationRules: { + pl: customRule + }, + messages: { en: enLang, pl: plLang diff --git a/src/locales/en.json b/src/locales/en.json index 9c47e75..c870c3d 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -199,6 +199,7 @@ "search-date-from": "Date (UTC+2 / CEST)", "search-date-to": "Date (UTC+2 / CEST)", "select-categoryCode": "Train category", + "search-headUnit": "Traction unit (e.g. EP09, ET22-401)", "sort-mass": "mass", "sort-speed": "speed", "sort-length": "length", @@ -573,6 +574,7 @@ "option-active-timetables": "Active timetables", "option-timetables-history": "Timetables history PL1", "option-dispatchers-history": "Dispatchers history PL1", + "option-top-list": "Scenery records", "btn-show-timetable-thumbnails": "Show rolling stock thumbnails", "btn-hide-timetable-thumbnails": "Hide rolling stock thumbnails", "timetable-includesScenery": "ALL TIMETABLES", @@ -592,7 +594,20 @@ "tablice-link": "Timetable summary board
(by Thundo)", "bottom-info": "Show full history in the Journal tab", "btn-show-internal-routes": "Show internal routes", - "btn-hide-internal-routes": "Hide internal routes" + "btn-hide-internal-routes": "Hide internal routes", + "top-list": { + "header": "RECORDS ON THE SCENERY (PL1)", + "mode-dutyCount": "DUTIES", + "mode-dispatcherRating": "RATING", + "mode-dutyDuration": "DUTY DURATION", + "scope-name": "GENERAL", + "scope-hash": "CURRENT HASH", + + "place": "{n}. place", + "dispatcher-rating": "Rating: {n}", + "duty-count": "No duties | 1 duty | Duties: {n}", + "duration": "Duration:" + } }, "availability": { "title": "Availability", diff --git a/src/locales/pl.json b/src/locales/pl.json index c01c5fa..ceec118 100644 --- a/src/locales/pl.json +++ b/src/locales/pl.json @@ -196,6 +196,7 @@ "search-date-from": "Data (UTC+2 / CEST)", "search-date-to": "Data (UTC+2 / CEST)", "select-categoryCode": "Kategoria pociągu", + "search-headUnit": "Pojazd trakcyjny (np. EP09, ET22-137)", "sort-routeDistance": "kilometraż", "sort-allStopsCount": "stacje", "sort-beginDate": "data", @@ -559,6 +560,7 @@ "option-active-timetables": "Aktywne rozkłady jazdy", "option-timetables-history": "Historia rozkładów PL1", "option-dispatchers-history": "Historia dyżurów PL1", + "option-top-list": "Rekordy scenerii", "btn-show-timetable-thumbnails": "Pokazuj podglądy składów", "btn-hide-timetable-thumbnails": "Ukrywaj podglądy składów", "timetable-includesScenery": "WSZYSTKIE RJ", @@ -578,7 +580,20 @@ "tablice-link": "Tablica informacyjna zbiorcza
(autorstwa Thundo)", "bottom-info": "Pokaż pełną historię w zakładce Dziennika", "btn-show-internal-routes": "Pokazuj szlaki wewnętrzne", - "btn-hide-internal-routes": "Ukrywaj szlaki wewnętrzne" + "btn-hide-internal-routes": "Ukrywaj szlaki wewnętrzne", + "top-list": { + "header": "REKORDY NA SCENERII (PL1)", + "mode-dutyCount": "DYŻURY", + "mode-dispatcherRating": "OCENA", + "mode-dutyDuration": "CZAS DYŻURU", + "scope-name": "OGÓLNIE", + "scope-hash": "OBECNY HASH", + + "place": "{n}. miejsce", + "dispatcher-rating": "Ocena: {n}", + "duty-count": "Brak dyżurów | 1 dyżur | Dyżury: {n}", + "duration": "Czas:" + } }, "availability": { "title": "Dostępność", diff --git a/src/store/apiStore.ts b/src/store/apiStore.ts index 05a2b9c..9b0d483 100644 --- a/src/store/apiStore.ts +++ b/src/store/apiStore.ts @@ -2,7 +2,20 @@ import { defineStore } from 'pinia'; import { API } from '../typings/api'; import { Status } from '../typings/common'; import { StationJSONData } from './typings'; -import axios, { AxiosInstance } from 'axios'; +import { HttpClient } from '../http'; + +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('apiStore', { state: () => ({ @@ -25,30 +38,13 @@ export const useApiStore = defineStore('apiStore', { nextUpdateTime: 0, nextDataCheckTime: 0, - client: undefined as AxiosInstance | undefined, + client: new HttpClient(baseURL), activeDataScheduler: undefined as number | undefined }), actions: { async setupAPIData() { - 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; - } - - this.client = axios.create({ - baseURL - }); - this.connectToAPI(); }, @@ -82,9 +78,9 @@ export const useApiStore = defineStore('apiStore', { if (!this.activeData) this.dataStatuses.connection = Status.Data.Loading; try { - const response = await this.client!.get('api/getActiveData'); + const response = await this.client.get('api/getActiveData'); - this.activeData = response.data; + this.activeData = response; this.dataStatuses.connection = Status.Data.Loaded; } catch (error) { this.dataStatuses.connection = Status.Data.Error; @@ -94,9 +90,9 @@ export const useApiStore = defineStore('apiStore', { async fetchDonatorsData() { try { - const response = await this.client!.get('api/getDonators'); + const response = await this.client.get('api/getDonators'); - this.donatorsData = response.data; + this.donatorsData = response; } catch (error) { console.error('Ups! Wystąpił błąd podczas pobierania informacji o donatorach:', error); } @@ -104,9 +100,7 @@ export const useApiStore = defineStore('apiStore', { async fetchStationsGeneralInfo() { try { - const sceneryData: StationJSONData[] = ( - await this.client!.get(`api/getSceneries`) - ).data; + const sceneryData = await this.client.get(`api/getSceneries`); this.dataStatuses.sceneries = Status.Data.Loaded; this.sceneryData = sceneryData; @@ -118,10 +112,10 @@ export const useApiStore = defineStore('apiStore', { async fetchVehiclesInfo() { try { - const response = await this.client!.get('api/getVehiclesData'); + const response = await this.client.get('api/getVehiclesData'); - this.vehiclesData = response.data; - this.dataStatuses.vehicles = response.data ? Status.Data.Loaded : Status.Data.Warning; + this.vehiclesData = response; + this.dataStatuses.vehicles = response ? Status.Data.Loaded : Status.Data.Warning; } catch (error) { this.dataStatuses.vehicles = Status.Data.Error; console.error('Ups! Wystąpił błąd podczas pobierania informacji o pojazdach:', error); @@ -130,9 +124,7 @@ export const useApiStore = defineStore('apiStore', { async fetchDailyStats() { try { - const res: API.DailyStats.Response = await ( - await this.client!.get('api/getDailyStats') - ).data; + const res = await this.client.get('api/getDailyStats'); this.dailyStatsData = res; diff --git a/src/styles/_dropdown-filters.scss b/src/styles/_dropdown-filters.scss index 6bcbb4f..0a20faf 100644 --- a/src/styles/_dropdown-filters.scss +++ b/src/styles/_dropdown-filters.scss @@ -78,14 +78,14 @@ h1.option-title { display: flex; gap: 0.5em; width: 100%; - margin-top: 0.5em; + margin-top: 1em; button { width: 100%; } } -@include responsive.smallScreen{ +@include responsive.smallScreen { h1 { text-align: center; diff --git a/src/styles/_dropdown.scss b/src/styles/_dropdown.scss index 3199222..a8628f7 100644 --- a/src/styles/_dropdown.scss +++ b/src/styles/_dropdown.scss @@ -26,7 +26,7 @@ .dropdown_wrapper { position: absolute; left: 0; - top: calc(100% + 0.5em); + top: 0; background-color: var(--clr-bg3); box-shadow: 0 0 5px 1px var(--clr-primary); @@ -34,7 +34,6 @@ width: 100%; max-width: 550px; - max-height: 750px; overflow: auto; padding: 1em; diff --git a/src/styles/_journal-section.scss b/src/styles/_journal-section.scss index f809b29..6bb8832 100644 --- a/src/styles/_journal-section.scss +++ b/src/styles/_journal-section.scss @@ -24,8 +24,8 @@ width: 100%; margin: 0 auto; - padding: 1em 0; + position: relative; } .journal_refreshed-date { @@ -57,7 +57,6 @@ justify-content: space-between; align-items: center; gap: 0.5em; - position: relative; } .btn--load-data { @@ -68,7 +67,7 @@ font-size: 1.2em; } -@include responsive.smallScreen{ +@include responsive.smallScreen { .journal_top-bar { justify-content: center; flex-wrap: wrap; diff --git a/src/styles/_search-box.scss b/src/styles/_search-box.scss index 59f5df9..bc5d6e1 100644 --- a/src/styles/_search-box.scss +++ b/src/styles/_search-box.scss @@ -15,7 +15,6 @@ gap: 0.25em; min-width: 200px; - margin-right: 0.25em; } &-input { @@ -61,4 +60,4 @@ top: 50%; transform: translateY(-50%); padding-right: 0.5em; -} \ No newline at end of file +} diff --git a/src/typings/api.ts b/src/typings/api.ts index 606f1b8..a3aa92b 100644 --- a/src/typings/api.ts +++ b/src/typings/api.ts @@ -253,8 +253,10 @@ export namespace API { pn?: number; tn?: number; - returnType?: 'all' | 'short' | 'detailed'; + headUnitName?: string; + headUnitType?: string; + returnType?: 'all' | 'short' | 'detailed'; sortBy?: Journal.TimetableSorter['id']; } diff --git a/src/views/JournalDispatchers.vue b/src/views/JournalDispatchers.vue index 47e4d04..5bb6707 100644 --- a/src/views/JournalDispatchers.vue +++ b/src/views/JournalDispatchers.vue @@ -217,9 +217,10 @@ export default defineComponent({ this.scrollDataLoaded = false; this.currentQueryParams['countFrom'] = this.historyList.length; - const responseData: API.DispatcherHistory.Response = await ( - await this.apiStore.client!.get(`api/getDispatchers`, { params: this.currentQueryParams }) - ).data; + const responseData: API.DispatcherHistory.Response = await await this.apiStore.client.get( + `api/getDispatchers`, + this.currentQueryParams + ); if (!responseData) return; @@ -276,9 +277,10 @@ export default defineComponent({ this.currentQueryParams = queryParams; try { - const responseData: API.DispatcherHistory.Response = await ( - await this.apiStore.client!.get(`api/getDispatchers`, { params: this.currentQueryParams }) - ).data; + const responseData: API.DispatcherHistory.Response = await this.apiStore.client.get( + `api/getDispatchers`, + this.currentQueryParams + ); if (!responseData) { this.dataStatus = Status.Data.Error; diff --git a/src/views/JournalTimetables.vue b/src/views/JournalTimetables.vue index 1e7986d..4e39886 100644 --- a/src/views/JournalTimetables.vue +++ b/src/views/JournalTimetables.vue @@ -173,8 +173,9 @@ export default defineComponent({ 'search-issuedFrom': '', 'search-via': '', 'search-terminatingAt': '', - 'select-categoryCode': '', - 'search-date-from': '' + 'search-headUnit': '', + 'search-date-from': '', + 'select-categoryCode': '' } as Journal.TimetableSearchType); const countFromIndex = ref(0); @@ -277,11 +278,10 @@ export default defineComponent({ this.currentQueryParams['countFrom'] = this.timetableHistory.length; - const responseData: API.TimetableHistory.Response = await ( - await this.apiStore.client!.get('api/getTimetables', { - params: this.currentQueryParams - }) - ).data; + const responseData: API.TimetableHistory.Response = await this.apiStore.client.get( + 'api/getTimetables', + this.currentQueryParams + ); if (!responseData) return; @@ -297,6 +297,8 @@ export default defineComponent({ async fetchHistoryData() { this.extraInfoIndexes.length = 0; + const queryParams: API.TimetableHistory.QueryParams = {}; + const driverName = this.searchersValues['search-driver'].trim() || undefined; const trainNo = this.searchersValues['search-train'].trim() || undefined; const authorName = this.searchersValues['search-dispatcher'].trim() || undefined; @@ -306,6 +308,7 @@ export default defineComponent({ const via = this.searchersValues['search-via'].trim() || undefined; const terminatingAt = this.searchersValues['search-terminatingAt'].trim() || undefined; const categoryCode = this.searchersValues['select-categoryCode'].trim() || undefined; + const headUnit = this.searchersValues['search-headUnit'].trim() || undefined; let dateFromISO: string | undefined = undefined; let dateToISO: string | undefined = undefined; @@ -321,8 +324,6 @@ export default defineComponent({ dateToISO = dateTo.toISOString(); } - const queryParams: API.TimetableHistory.QueryParams = {}; - this.filterList .filter((f) => f.isActive) .forEach((f) => { @@ -394,17 +395,27 @@ export default defineComponent({ queryParams['sortBy'] = this.sorterActive.id != 'timetableId' ? this.sorterActive.id : undefined; + // Head unit params + if (headUnit) { + const [headUnitName, headUnitNumber] = headUnit.split('-'); + + if (headUnitNumber && !isNaN(Number(headUnitNumber))) { + queryParams['headUnitName'] = `${headUnitName}-${headUnitNumber}`; + } else { + queryParams['headUnitType'] = headUnitName; + } + } + if (JSON.stringify(this.currentQueryParams) != JSON.stringify(queryParams)) this.dataStatus = Status.Data.Loading; this.currentQueryParams = queryParams; try { - const responseData: API.TimetableHistory.ResponseShort = await ( - await this.apiStore.client!.get('api/getTimetables', { - params: this.currentQueryParams - }) - ).data; + const responseData: API.TimetableHistory.ResponseShort = await this.apiStore.client.get( + 'api/getTimetables', + this.currentQueryParams + ); if (!responseData) { this.dataStatus = Status.Data.Error; diff --git a/src/views/PlayerProfileView.vue b/src/views/PlayerProfileView.vue index 5266a22..d092c23 100644 --- a/src/views/PlayerProfileView.vue +++ b/src/views/PlayerProfileView.vue @@ -40,7 +40,6 @@ import Loading from '../components/Global/Loading.vue'; import ProfileSummary from '../components/PlayerProfileView/ProfileSummary.vue'; import ProfileRecentStats from '../components/PlayerProfileView/ProfileRecentStats.vue'; import ProfileHistoryList from '../components/PlayerProfileView/ProfileHistoryList.vue'; -import axios from 'axios'; const { t } = useI18n(); const router = useRouter(); @@ -71,29 +70,28 @@ onDeactivated(() => { }); async function fetchPlayerInfo(playerId: number) { - return apiStore.client!.get('api/getPlayerInfo', { - params: { - playerId - } + return apiStore.client.get('api/getPlayerInfo', { + playerId }); } async function fetchPlayerJournal(playerId: number) { - return apiStore.client!.get('api/getPlayerJournal', { - params: { - playerId, - dateScope: '30d' - } + return apiStore.client.get('api/getPlayerJournal', { + playerId, + dateScope: '30d' }); } -async function fetchPlayerTd2Info(playerName: string) { - return axios.get('https://api.td2.info.pl', { - params: { - method: 'getUsersInfoByName', - name: playerName - } - }); +async function fetchPlayerTd2Info(playerName: string): Promise { + const response = await fetch( + `https://api.td2.info.pl?method=getUsersInfoByName&name=${playerName}` + ); + + if (!response.ok) { + throw new Error('fetchPlayerTd2Info: could not fetch data'); + } + + return response.json(); } async function fetchPlayerData() { @@ -116,23 +114,21 @@ async function fetchPlayerData() { const playerInfoResp = await fetchPlayerInfo(playerId.value); playerName.value = - playerInfoResp.data.driverStats.driverName || - playerInfoResp.data.dispatcherStats.dispatcherName || - ''; + playerInfoResp.driverStats.driverName || playerInfoResp.dispatcherStats.dispatcherName || ''; if (!playerName.value) { router.push('/'); return; } - playerInfo.value = playerName.value ? playerInfoResp.data : undefined; + playerInfo.value = playerName.value ? playerInfoResp : undefined; playerInfoStatus.value = Status.Data.Loaded; if (playerName.value) { const playerTD2InfoResp = await fetchPlayerTd2Info(playerName.value); - if (playerTD2InfoResp.data.success && playerTD2InfoResp.data.message.length == 1) { - playerTD2Info.value = playerTD2InfoResp.data.message[0]; + if (playerTD2InfoResp.success && playerTD2InfoResp.message.length == 1) { + playerTD2Info.value = playerTD2InfoResp.message[0]; } } } catch (error) { @@ -144,7 +140,7 @@ async function fetchPlayerData() { try { const playerJournalResp = await fetchPlayerJournal(playerId.value); - playerJournal.value = playerJournalResp.data; + playerJournal.value = playerJournalResp; playerJournalStatus.value = Status.Data.Loaded; } catch (error) { playerJournal.value = undefined; diff --git a/src/views/SceneryView.vue b/src/views/SceneryView.vue index 3afd75a..e3877e6 100644 --- a/src/views/SceneryView.vue +++ b/src/views/SceneryView.vue @@ -58,6 +58,7 @@ import SceneryDispatchersHistory from '../components/SceneryView/SceneryDispatch import { useApiStore } from '../store/apiStore'; import { Status } from '../typings/common'; +import SceneryTopList from '../components/SceneryView/SceneryTopList.vue'; const route = useRoute(); const router = useRouter(); @@ -89,6 +90,10 @@ const viewModes = [ { id: 'scenery.option-dispatchers-history', component: SceneryDispatchersHistory + }, + { + id: 'scenery.option-top-list', + component: SceneryTopList } ]; @@ -184,7 +189,7 @@ function setViewMode(componentName: string) { background-color: #181818; border-radius: 0.5em; - padding: 0.5em; + padding: 1em; } .scenery-left { diff --git a/src/views/TrainsView.vue b/src/views/TrainsView.vue index 5ba5ad2..cb92f59 100644 --- a/src/views/TrainsView.vue +++ b/src/views/TrainsView.vue @@ -116,13 +116,10 @@ export default defineComponent({