feature: modal darowizn

This commit is contained in:
2023-11-26 19:49:32 +01:00
parent 6ef04f0dbd
commit a224b58d17
22 changed files with 756 additions and 373 deletions
+1 -29
View File
@@ -1,33 +1,7 @@
@import './styles/responsive.scss';
@import './styles/variables.scss';
@import './styles/global.scss';
// VUE ROUTE CHANGE ANIMATION
.view-anim {
&-enter-from,
&-leave-to {
opacity: 0.02;
}
&-enter-active,
&-leave-active {
transition: all $animDuration $animType;
min-height: 100%;
}
}
.modal-anim {
&-enter-active,
&-leave-active {
transition: all $animDuration $animType;
}
&-enter-from,
&-leave-to {
transform: translateY(-25%);
opacity: 0;
}
}
@import './styles/animations.scss';
.route {
margin: 0 0.2em;
@@ -56,8 +30,6 @@
// CONTAINER
.app_container {
// display: flex;
// flex-flow: column;
display: grid;
grid-template-rows: auto 1fr auto;
grid-template-columns: 100%;
+83
View File
@@ -0,0 +1,83 @@
<template>
<transition name="modal-anim" tag="div" class="modal">
<div class="body" v-if="isOpen">
<div class="background" @click="toggleModal(false)"></div>
<div class="wrapper" ref="wrapper" tabindex="0">
<slot></slot>
</div>
<div class="tab-exit" ref="exit" tabindex="0" @focus="toggleModal(false)"></div>
</div>
</transition>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { useStore } from '../../store/mainStore';
export default defineComponent({
emits: ['toggleModal'],
props: {
isOpen: Boolean
},
data() {
return {
store: useStore()
};
},
watch: {
isOpen(v) {
this.$nextTick(() => {
if (v) (this.$refs['wrapper'] as HTMLElement).focus();
else (this.store.modalLastClickedTarget as HTMLElement)?.focus();
});
}
},
methods: {
toggleModal(value: boolean) {
this.$emit('toggleModal', value);
}
}
});
</script>
<style lang="scss" scoped>
.body {
position: fixed;
top: 0;
left: 0;
z-index: 200;
width: 100vw;
height: 100vh;
}
.background {
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
cursor: pointer;
background-color: rgba(0, 0, 0, 0.55);
}
.wrapper {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #1a1a1a;
box-shadow: 0 0 15px 10px #333333;
width: 95%;
max-width: 800px;
max-height: 95vh;
}
</style>
+179
View File
@@ -0,0 +1,179 @@
<template>
<div class="donation-modal" @keydown.esc="toggleModal(false)">
<button
class="modal_button action btn--image"
ref="btn"
@click="toggleModal(true)"
@focus="toggleModal(false)"
>
<img src="/images/icon-dollar.svg" alt="dollar donation icon" />
<span>{{ $t('donations.button-title') }}</span>
</button>
<AnimatedModal :isOpen="isModalOpen" @toggleModal="toggleModal">
<div class="modal_content">
<div class="modal_main">
<h1 v-html="$t('donations.header')"></h1>
<br />
<p v-html="$t('donations.p1')"></p>
<br />
<i18n-t keypath="donations.p2" tag="p">
<template v-slot:b1>
<b>{{ $t('donations.p2-b1') }}</b>
</template>
<template v-slot:b2>
<b>{{ $t('donations.p2-b2') }}</b>
</template>
<template v-slot:b3>
<b>{{ $t('donations.p2-b3') }}</b>
</template>
<template v-slot:link>
<a href="https://discord.gg/x2mpNN3svk" target="_blank">
{{ $t('donations.p2-a1') }}
</a>
</template>
</i18n-t>
<br />
<p v-html="$t('donations.p3')"></p>
<br />
<i18n-t keypath="donations.p4" tag="p">
<template v-slot:img>
<img src="/images/icon-diamond.svg" alt="diamond donator icon" />
</template>
<template v-slot:b1>
<b>{{ $t('donations.p4-b1') }}</b>
</template>
<template v-slot:b2>
<b class="text--honorable">{{ $t('donations.p4-b2') }}</b>
</template>
</i18n-t>
<br />
<i
v-html="$t('donations.p5')"
style="display: flex; justify-content: flex-end; text-align: right"
>
</i>
</div>
<div class="modal_actions">
<button class="modal_button exit btn--image" @click="toggleModal(false)">
<img src="/images/icon-exit.svg" alt="dollar donation icon" />
{{ $t('donations.action-exit') }}
</button>
<form action="https://www.paypal.com/donate" method="post">
<input type="hidden" name="hosted_button_id" value="EDB3SKFAHXFTW" />
<button class="modal_button action btn--image" @click="toggleModal(false)">
<img src="/images/icon-dollar.svg" alt="dollar donation icon" />
{{ $t('donations.action-confirm') }}
</button>
</form>
</div>
</div>
</AnimatedModal>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import AnimatedModal from './AnimatedModal.vue';
export default defineComponent({
props: {
isModalOpen: Boolean
},
emits: ['toggleModal'],
methods: {
toggleModal(value: boolean) {
this.$emit('toggleModal', value);
}
},
components: { AnimatedModal }
});
</script>
<style lang="scss" scoped>
@import '../../styles/responsive.scss';
.modal_button {
&.action {
$btnColor: #254069;
background-color: $btnColor;
&:hover {
background-color: lighten($btnColor, 10%);
}
}
&.exit {
$btnColor: crimson;
background-color: $btnColor;
&:hover {
background-color: lighten($btnColor, 10%);
}
}
@include smallScreen {
span {
display: none;
}
}
}
.modal-logo {
position: absolute;
top: 0;
left: 50%;
width: 6em;
transform: translate(-50%, -50%);
}
.modal_content {
display: grid;
grid-template-rows: 1fr auto;
gap: 1em;
max-height: 95vh;
font-size: 1.1em;
& > div {
padding: 1em;
}
h1 {
font-size: 1.95em;
text-align: center;
}
p {
text-align: justify;
}
a {
text-decoration: underline;
}
}
.modal_main {
overflow: auto;
img {
max-height: 20px;
margin-right: 5px;
vertical-align: text-bottom;
}
}
.modal_actions {
display: flex;
justify-content: flex-end;
gap: 0.5em;
}
</style>
-11
View File
@@ -35,17 +35,6 @@ export default defineComponent({
this.$nextTick(() => {
contentEl.focus();
});
},
methods: {
handleContentScroll(e: Event) {
const trainInfoCompHeight: number = (
this.$refs['trainInfo'] as any
).$el.getBoundingClientRect().height;
const posTop = (e.target as HTMLElement).scrollTop;
this.isTopBarVisible = posTop > trainInfoCompHeight;
}
}
});
</script>
@@ -85,9 +85,11 @@
<strong>{{ scheduledTrain.category }}</strong>
{{ scheduledTrain.trainNo }}
<span class="g-tooltip" v-if="scheduledTrain.stopInfo.comments">
<span
v-if="scheduledTrain.stopInfo.comments"
:title="scheduledTrain.stopInfo.comments"
>
<img src="/images/icon-warning.svg" />
<span class="content" v-html="scheduledTrain.stopInfo.comments"> </span>
</span>
</span>
&nbsp;|&nbsp;
@@ -295,19 +295,7 @@ export default defineComponent({
<style lang="scss" scoped>
@import '../../styles/responsive.scss';
@import '../../styles/card.scss';
.card-anim {
&-enter-active,
&-leave-active {
transition: all $animDuration $animType;
}
&-enter-from,
&-leave-to {
opacity: 0;
transform: translate(-50%, -50%) scale(0.45);
}
}
@import '../../styles/animations.scss';
.card {
display: grid;
+260 -241
View File
@@ -1,270 +1,278 @@
<template>
<section class="station_table">
<div class="table_wrapper">
<table>
<thead>
<tr>
<th
v-for="headerName in headIds"
:key="headerName"
@click="changeSorter(headerName)"
class="header-text"
>
<span class="header_wrapper">
<div v-html="$t(`sceneries.${headerName}`)"></div>
<img
class="sort-icon"
v-if="sorterActive.headerName == headerName"
:src="`/images/icon-arrow-${sorterActive.dir == 1 ? 'asc' : 'desc'}.svg`"
alt="sort icon"
/>
</span>
</th>
<th
v-for="headerName in headIconsIds"
:key="headerName"
@click="changeSorter(headerName)"
class="header-image"
>
<span class="header_wrapper">
<img
:src="`/images/icon-${headerName}.svg`"
:alt="headerName"
:title="$t(`sceneries.${headerName}`)"
/>
<img
class="sort-icon"
v-if="sorterActive.headerName == headerName"
:src="`/images/icon-arrow-${sorterActive.dir == 1 ? 'asc' : 'desc'}.svg`"
alt="sort icon"
/>
</span>
</th>
</tr>
</thead>
<tbody>
<tr
class="station"
:class="{ 'last-selected': lastSelectedStationName == station.name }"
v-for="(station, i) in stations"
:key="i + station.name"
@click.left="setScenery(station.name)"
@click.right="openForumSite($event, station.generalInfo?.url)"
@keydown.enter="setScenery(station.name)"
@keydown.space="openForumSite($event, station.generalInfo?.url)"
tabindex="0"
<table>
<thead>
<tr>
<th
v-for="headerName in headIds"
:key="headerName"
@click="changeSorter(headerName)"
class="header-text"
>
<td class="station_name" :class="station.generalInfo?.availability">
<b v-if="station.generalInfo?.project" style="color: salmon">{{
station.generalInfo.project
}}</b>
{{ station.name }}
</td>
<span class="header_wrapper">
<div v-html="$t(`sceneries.${headerName}`)"></div>
<td class="station_level">
<span v-if="station.generalInfo">
<span
v-if="
station.generalInfo.reqLevel > -1 &&
station.generalInfo.availability != 'nonPublic' &&
station.generalInfo.availability != 'unavailable'
"
:style="calculateExpStyle(station.generalInfo.reqLevel)"
>
{{ station.generalInfo.reqLevel >= 2 ? station.generalInfo.reqLevel : 'L' }}
</span>
<span v-else-if="station.generalInfo.availability == 'abandoned'">
<img
src="/images/icon-abandoned.svg"
alt="non-public"
:title="$t('desc.abandoned')"
/>
</span>
<span v-else-if="station.generalInfo.availability == 'nonPublic'">
<img
src="/images/icon-lock.svg"
alt="non-public"
:title="$t('desc.non-public')"
/>
</span>
<span v-else>
<img
src="/images/icon-unavailable.svg"
alt="unavailable"
:title="$t('desc.unavailable')"
/>
</span>
</span>
<span v-else> ? </span>
</td>
<td class="station_status">
<StationStatusBadge
:isOnline="station.onlineInfo ? true : false"
:dispatcherStatus="station.onlineInfo?.dispatcherStatus"
<img
class="sort-icon"
v-if="sorterActive.headerName == headerName"
:src="`/images/icon-arrow-${sorterActive.dir == 1 ? 'asc' : 'desc'}.svg`"
alt="sort icon"
/>
</td>
</span>
</th>
<td class="station_dispatcher-name">
{{ station.onlineInfo ? station.onlineInfo.dispatcherName : '' }}
</td>
<th
v-for="headerName in headIconsIds"
:key="headerName"
@click="changeSorter(headerName)"
class="header-image"
>
<span class="header_wrapper">
<img
:src="`/images/icon-${headerName}.svg`"
:alt="headerName"
:title="$t(`sceneries.${headerName}`)"
/>
<td class="station_dispatcher-exp">
<span
v-if="station.onlineInfo"
:style="
calculateExpStyle(
station.onlineInfo.dispatcherExp,
station.onlineInfo.dispatcherIsSupporter
)
"
>
{{ 2 > station.onlineInfo.dispatcherExp ? 'L' : station.onlineInfo.dispatcherExp }}
</span>
</td>
<img
class="sort-icon"
v-if="sorterActive.headerName == headerName"
:src="`/images/icon-arrow-${sorterActive.dir == 1 ? 'asc' : 'desc'}.svg`"
alt="sort icon"
/>
</span>
</th>
</tr>
</thead>
<td class="station_tracks twoway">
<tbody>
<tr
class="station"
:class="{ 'last-selected': lastSelectedStationName == station.name }"
v-for="(station, i) in stations"
:key="i + station.name"
@click.left="setScenery(station.name)"
@click.right="openForumSite($event, station.generalInfo?.url)"
@keydown.enter="setScenery(station.name)"
@keydown.space="openForumSite($event, station.generalInfo?.url)"
tabindex="0"
>
<td class="station_name" :class="station.generalInfo?.availability">
<b v-if="station.generalInfo?.project" style="color: salmon">{{
station.generalInfo.project
}}</b>
{{ station.name }}
</td>
<td class="station_level">
<span v-if="station.generalInfo">
<span
v-if="
station.generalInfo &&
station.generalInfo.routes.twoWayCatenaryRouteNames.length > 0
station.generalInfo.reqLevel > -1 &&
station.generalInfo.availability != 'nonPublic' &&
station.generalInfo.availability != 'unavailable'
"
class="track catenary"
:title="`Liczba zelektryfikowanych szlaków dwutorowych: ${station.generalInfo.routes.twoWayCatenaryRouteNames.length}`"
:style="calculateExpStyle(station.generalInfo.reqLevel)"
>
{{ station.generalInfo.routes.twoWayCatenaryRouteNames.length }}
{{ station.generalInfo.reqLevel >= 2 ? station.generalInfo.reqLevel : 'L' }}
</span>
<span
v-if="
station.generalInfo &&
station.generalInfo.routes.twoWayNoCatenaryRouteNames.length > 0
"
class="track no-catenary"
:title="`Liczba niezelektryfikowanych szlaków dwutorowych: ${station.generalInfo.routes.twoWayNoCatenaryRouteNames.length}`"
>
{{ station.generalInfo.routes.twoWayNoCatenaryRouteNames.length }}
</span>
<span class="separator"></span>
<span
v-if="
station.generalInfo &&
station.generalInfo.routes.oneWayCatenaryRouteNames.length > 0
"
class="track catenary"
:title="`Liczba zelektryfikowanych szlaków jednotorowych: ${station.generalInfo.routes.oneWayCatenaryRouteNames.length}`"
>
{{ station.generalInfo.routes.oneWayCatenaryRouteNames.length }}
</span>
<span
v-if="
station.generalInfo &&
station.generalInfo.routes.oneWayNoCatenaryRouteNames.length > 0
"
class="track no-catenary"
:title="`Liczba niezelektryfikowanych szlaków jednotorowych: ${station.generalInfo.routes.oneWayNoCatenaryRouteNames.length}`"
>
{{ station.generalInfo.routes.oneWayNoCatenaryRouteNames.length }}
</span>
</td>
<td class="station_info" v-if="station.generalInfo">
<span
class="scenery-icon icon-info"
:class="station.generalInfo.controlType.replace('+', '-')"
:title="$t('desc.control-type') + $t(`controls.${station.generalInfo.controlType}`)"
v-html="getControlTypeAbbrev(station.generalInfo.controlType)"
>
</span>
<span>
<span v-else-if="station.generalInfo.availability == 'abandoned'">
<img
class="icon-info"
v-if="station.generalInfo.SUP"
src="/images/icon-SUP.svg"
alt="SUP (RASP-UZK)"
:title="$t('desc.SUP')"
src="/images/icon-abandoned.svg"
alt="non-public"
:title="$t('desc.abandoned')"
/>
</span>
<span>
<img
class="icon-info"
v-if="station.generalInfo.signalType"
:src="`/images/icon-${station.generalInfo.signalType}.svg`"
:alt="station.generalInfo.signalType"
:title="$t('desc.signals-type') + $t(`signals.${station.generalInfo.signalType}`)"
/>
<span v-else-if="station.generalInfo.availability == 'nonPublic'">
<img src="/images/icon-lock.svg" alt="non-public" :title="$t('desc.non-public')" />
</span>
<span>
<span v-else>
<img
class="icon-info"
v-if="station.generalInfo && station.generalInfo.routes.sblRouteNames.length > 0"
src="/images/icon-SBL.svg"
alt="SBL"
:title="$t('desc.SBL') + `${station.generalInfo.routes.sblRouteNames.join(',')}`"
src="/images/icon-unavailable.svg"
alt="unavailable"
:title="$t('desc.unavailable')"
/>
</span>
</td>
</span>
<td class="station_info" v-else>
<span v-else> ? </span>
</td>
<td class="station_status">
<StationStatusBadge
:isOnline="station.onlineInfo ? true : false"
:dispatcherStatus="station.onlineInfo?.dispatcherStatus"
/>
</td>
<td class="station_dispatcher-name">
<span v-if="station.onlineInfo?.dispatcherName">
<div
v-if="store.donatorsData.includes(station.onlineInfo.dispatcherName)"
title="Dyżurny wspierający projekt Stacjownika!"
class="text--honorable"
@click.stop="openDonationModal"
>
<img src="/images/icon-diamond.svg" alt="" />
{{ station.onlineInfo.dispatcherName }}
</div>
<div v-else>
{{ station.onlineInfo.dispatcherName }}
</div>
</span>
</td>
<td class="station_dispatcher-exp">
<span
v-if="station.onlineInfo"
:style="
calculateExpStyle(
station.onlineInfo.dispatcherExp,
station.onlineInfo.dispatcherIsSupporter
)
"
>
{{ station.onlineInfo.dispatcherExp < 2 ? 'L' : station.onlineInfo.dispatcherExp }}
</span>
</td>
<td class="station_tracks twoway">
<span
v-if="
station.generalInfo &&
station.generalInfo.routes.twoWayCatenaryRouteNames.length > 0
"
class="track catenary"
:title="`Liczba zelektryfikowanych szlaków dwutorowych: ${station.generalInfo.routes.twoWayCatenaryRouteNames.length}`"
>
{{ station.generalInfo.routes.twoWayCatenaryRouteNames.length }}
</span>
<span
v-if="
station.generalInfo &&
station.generalInfo.routes.twoWayNoCatenaryRouteNames.length > 0
"
class="track no-catenary"
:title="`Liczba niezelektryfikowanych szlaków dwutorowych: ${station.generalInfo.routes.twoWayNoCatenaryRouteNames.length}`"
>
{{ station.generalInfo.routes.twoWayNoCatenaryRouteNames.length }}
</span>
<span class="separator"></span>
<span
v-if="
station.generalInfo &&
station.generalInfo.routes.oneWayCatenaryRouteNames.length > 0
"
class="track catenary"
:title="`Liczba zelektryfikowanych szlaków jednotorowych: ${station.generalInfo.routes.oneWayCatenaryRouteNames.length}`"
>
{{ station.generalInfo.routes.oneWayCatenaryRouteNames.length }}
</span>
<span
v-if="
station.generalInfo &&
station.generalInfo.routes.oneWayNoCatenaryRouteNames.length > 0
"
class="track no-catenary"
:title="`Liczba niezelektryfikowanych szlaków jednotorowych: ${station.generalInfo.routes.oneWayNoCatenaryRouteNames.length}`"
>
{{ station.generalInfo.routes.oneWayNoCatenaryRouteNames.length }}
</span>
</td>
<td class="station_info" v-if="station.generalInfo">
<span
class="scenery-icon icon-info"
:class="station.generalInfo.controlType.replace('+', '-')"
:title="$t('desc.control-type') + $t(`controls.${station.generalInfo.controlType}`)"
v-html="getControlTypeAbbrev(station.generalInfo.controlType)"
>
</span>
<span>
<img
class="icon-info"
src="/images/icon-unknown.svg"
alt="icon-unknown"
:title="$t('desc.unknown')"
v-if="station.generalInfo.SUP"
src="/images/icon-SUP.svg"
alt="SUP (RASP-UZK)"
:title="$t('desc.SUP')"
/>
</td>
</span>
<td class="station_users" :class="{ inactive: !station.onlineInfo }">
<span>{{ station.onlineInfo?.currentUsers || 0 }}</span>
/
<span>{{ station.onlineInfo?.maxUsers || 0 }}</span>
</td>
<span>
<img
class="icon-info"
v-if="station.generalInfo.signalType"
:src="`/images/icon-${station.generalInfo.signalType}.svg`"
:alt="station.generalInfo.signalType"
:title="$t('desc.signals-type') + $t(`signals.${station.generalInfo.signalType}`)"
/>
</span>
<td class="station_spawns" :class="{ inactive: !station.onlineInfo }">
<span>{{ station.onlineInfo?.spawns.length || 0 }}</span>
</td>
<span>
<img
class="icon-info"
v-if="station.generalInfo && station.generalInfo.routes.sblRouteNames.length > 0"
src="/images/icon-SBL.svg"
alt="SBL"
:title="$t('desc.SBL') + `${station.generalInfo.routes.sblRouteNames.join(',')}`"
/>
</span>
</td>
<td
class="station_schedules all"
style="width: 30px"
:class="{ inactive: !station.onlineInfo }"
>
{{ station.onlineInfo?.scheduledTrainCount.all }}
</td>
<td class="station_info" v-else>
<img
class="icon-info"
src="/images/icon-unknown.svg"
alt="icon-unknown"
:title="$t('desc.unknown')"
/>
</td>
<td
class="station_schedules unconfirmed"
style="width: 30px"
:class="{ inactive: !station.onlineInfo }"
>
{{ station.onlineInfo?.scheduledTrainCount.unconfirmed }}
</td>
<td class="station_users" :class="{ inactive: !station.onlineInfo }">
<span>{{ station.onlineInfo?.currentUsers || 0 }}</span>
/
<span>{{ station.onlineInfo?.maxUsers || 0 }}</span>
</td>
<td
class="station_schedules confirmed"
style="width: 30px"
:class="{ inactive: !station.onlineInfo }"
>
{{ station.onlineInfo?.scheduledTrainCount.confirmed }}
</td>
</tr>
</tbody>
</table>
</div>
<td class="station_spawns" :class="{ inactive: !station.onlineInfo }">
<span>{{ station.onlineInfo?.spawns.length || 0 }}</span>
</td>
<td
class="station_schedules all"
style="width: 30px"
:class="{ inactive: !station.onlineInfo }"
>
{{ station.onlineInfo?.scheduledTrainCount.all }}
</td>
<td
class="station_schedules unconfirmed"
style="width: 30px"
:class="{ inactive: !station.onlineInfo }"
>
{{ station.onlineInfo?.scheduledTrainCount.unconfirmed }}
</td>
<td
class="station_schedules confirmed"
style="width: 30px"
:class="{ inactive: !station.onlineInfo }"
>
{{ station.onlineInfo?.scheduledTrainCount.confirmed }}
</td>
</tr>
</tbody>
</table>
<Loading v-if="!isDataLoaded && stations.length == 0" />
@@ -295,6 +303,7 @@ export default defineComponent({
}
},
emits: ['toggleDonationModal'],
components: { Loading, StationStatusBadge },
mixins: [styleMixin, dateMixin, stationInfoMixin],
@@ -320,7 +329,8 @@ export default defineComponent({
return {
isDataLoaded,
stationFiltersStore
stationFiltersStore,
store
};
},
@@ -340,6 +350,11 @@ export default defineComponent({
});
},
openDonationModal(e: Event) {
this.$emit('toggleDonationModal', true);
this.store.modalLastClickedTarget = e.target;
},
openForumSite(e: Event, url: string | undefined) {
if (!url) return;
e.preventDefault();
@@ -380,11 +395,6 @@ section.station_table {
font-weight: 500;
}
.table_wrapper {
overflow: auto;
overflow-y: hidden;
}
table {
white-space: nowrap;
border-collapse: collapse;
@@ -495,6 +505,15 @@ td.station {
}
}
// &_dispatcher-name {
// position: relative;
// }
&_dispatcher-name img {
max-width: 1.35em;
vertical-align: text-bottom;
}
&_level {
span {
background-color: #888;
+17
View File
@@ -1,4 +1,21 @@
{
"donations": {
"button-title": "TOSS A COIN",
"header": "Toss a coin to Stacjownik!",
"p1": "<b>Hello o7!</b> This is Spythere, the creator of Stacjownik, Pojazdownik and several other applications that enhance the gameplay of Train Driver 2!",
"p2": "{b1} is a completely free tool, created and continuously developed for the Train Driver 2 simulator community since 2020. However, a part of the project is sustained solely through my private financial contribution. Features such as {b2} or {b3} (operating on my {link} - to which you are warmly invited) must function on a dedicated server where they can collect and process data, and then display it on the website.",
"p2-b1": "Stacjownik",
"p2-b2": "Journal",
"p2-b3": "Stacjobot (Stacjownik bot)",
"p2-a1": "Discord server",
"p3": "<b>If you have the means and would like to support my work, I would be grateful for any financial assistance that could help cover at least some of the server costs and further enhance the capabilities of the application!</b>",
"p4": "Every person who decides to contribute at least {b1} for the development of Stacjownik, will recieve (upon a personal request) {img}{b2} of username in the \"Sceneries\" and \"Trains\" tabs of the application, as well as on my Discord server (after verifying the payment author, preferably by providing the username directly with the payment).",
"p4-b1": "5 PLN",
"p4-b2": "a symbolic highlight",
"p5": "Thank you and enjoy further using my tools!<br />~Spythere",
"action-exit": "Maybe next time...",
"action-confirm": "DONATE!"
},
"general": {
"and": " and ",
"refresh": "REFRESH",
+17
View File
@@ -1,4 +1,21 @@
{
"donations": {
"button-title": "GROSZA DAJ",
"header": "Grosza daj Stacjownikowi!",
"p1": "<b>Hej o7!</b> Z tej strony Spythere, twórca Stacjownika, Pojazdownika oraz kilku innych aplikacji wspomagających rozgrywkę symulatora Train Driver 2!",
"p2": "{b1} to narzędzie całkowicie darmowe, tworzone i rozwijane dla społeczności symulatora TD2 nieprzerwanie od 2020 roku. Jednakże, część projektu jest podtrzymywana wyłącznie dzięki mojemu prywatnemu wkładowi finansowemu. Funkcje takie jak {b2} czy też {b3} działający na moim {link} (na który serdeczne zapraszam) muszą działać na wydzielonym serwerze, gdzie będą mogły zbierać i przetwarzać dane, aby następnie pokazać je na stronie.",
"p2-b1": "Stacjownik",
"p2-b2": "Dziennik",
"p2-b3": "Stacjobot",
"p2-a1": "serwerze Discord",
"p3": "<b>Jeśli masz możliwość i chcesz wspomóc moją twórczość dla symulatora, będę wdzięczny za wszelkie pomoce finansowe, które pozwolą na choćby częściowe pokrycie kosztów serwera oraz dalsze rozwijanie możliwości aplikacji!</b>",
"p4": "Każda osoba, która postanowi przelać co najmniej {b1} na rozwój Stacjownika, otrzyma na życzenie symboliczne {img}{b2} nicku użytkownika w zakładkach \"Scenerie\" i \"Pociągi\" aplikacji i moim serwerze Discord (po zweryfikowaniu autora płatności, najlepiej poprzez podanie nicku bezpośrednio przy niej).",
"p4-b1": "5 złotych",
"p4-b2": "wyróżnienie",
"p5": "Dzięki i miłego dalszego korzystania z moich narzędzi! <br /> ~Spythere",
"action-exit": "Może kiedy indziej...",
"action-confirm": "WSPOMÓŻ!"
},
"general": {
"and": " oraz ",
"refresh": "ODŚWIEŻ",
+1 -1
View File
@@ -2,6 +2,6 @@ export const URLs = {
stacjownikAPI:
import.meta.env.VITE_APP_API_DEV === '1' && !import.meta.env.PROD
? 'http://localhost:3001'
: 'https://stacjownik.spythere.pl',
: 'https://stacjownik.spythere.eu',
stacjownikAPIDev: 'localhost:3000'
};
+23 -4
View File
@@ -9,7 +9,7 @@ import { parseSpawns, getScheduledTrains, getStationTrains } from './utils';
import { OnlineScenery, ScheduledTrain, StationJSONData, StoreState } from './typings';
import packageInfo from '../../package.json';
import { Websocket, API } from '../typings/api';
import { Websocket, API, GithubAPI } from '../typings/api';
import { Status } from '../typings/common';
export const useStore = defineStore('store', {
@@ -17,6 +17,7 @@ export const useStore = defineStore('store', {
({
activeData: {} as unknown,
rollingStockData: undefined,
donatorsData: [],
stationList: [],
regionOnlineCounters: [],
@@ -55,7 +56,14 @@ export const useStore = defineStore('store', {
blockScroll: false,
listenerLaunched: false,
modalLastClickedTarget: null
modalLastClickedTarget: null,
tooltip: {
content: '',
visible: false,
x: 0,
y: 0
}
}) as StoreState,
getters: {
@@ -280,6 +288,7 @@ export const useStore = defineStore('store', {
async connectToAPI() {
this.connectToWebsocket();
this.fetchStockInfoData();
this.fetchDonatorsData();
this.fetchStationsGeneralInfo();
},
@@ -299,6 +308,18 @@ export const useStore = defineStore('store', {
}
},
async fetchDonatorsData() {
try {
const response = await axios.get<GithubAPI.Donators.Response>(
'https://raw.githubusercontent.com/Spythere/api/main/td2/data/donators.json'
);
if (response.data) this.donatorsData = ['Kryszakos'];
} catch (error) {
console.error('Ups! Wystąpił błąd podczas pobierania informacji o donatorach:', error);
}
},
async setStatuses() {
if (!this.activeData.activeSceneries) {
this.dataStatuses.sceneries = Status.Data.Error;
@@ -311,8 +332,6 @@ export const useStore = defineStore('store', {
this.dataStatuses.sceneries = Status.Data.Loaded;
this.dataStatuses.trains = !this.activeData.trains ? Status.Data.Warning : Status.Data.Loaded;
this.dataStatuses.dispatchers = Status.Data.Loaded;
// if (this.apiData.dispatchers != null) this.lastDispatcherStatuses = prevDispatcherStatuses;
}
}
});
+10 -2
View File
@@ -1,6 +1,6 @@
import { Socket } from 'socket.io-client';
import Station from '../scripts/interfaces/Station';
import { API, Websocket } from '../typings/api';
import { API, GithubAPI, Websocket } from '../typings/api';
import { Status } from '../typings/common';
export type Availability = 'default' | 'unavailable' | 'nonPublic' | 'abandoned' | 'nonDefault';
@@ -15,6 +15,7 @@ export interface StoreState {
stationList: Station[];
activeData: Websocket.ActiveData;
rollingStockData?: API.RollingStock.Response;
donatorsData: GithubAPI.Donators.Response;
regionOnlineCounters: RegionCounters[];
@@ -55,6 +56,13 @@ export interface StoreState {
listenerLaunched: boolean;
blockScroll: boolean;
modalLastClickedTarget: EventTarget | null;
tooltip: {
visible: boolean;
x: number;
y: number;
content: string;
};
}
export interface StationRoutesInfo {
@@ -187,7 +195,7 @@ export interface TrainStop {
departureDelay: number;
pointId: number;
comments?: any;
comments?: string;
beginsHere: boolean;
terminatesHere: boolean;
+50 -3
View File
@@ -1,7 +1,11 @@
$animDuration: 150ms;
$animType: ease-in-out;
// List animation
.list-anim-move,
.list-anim-enter-active,
.list-anim-leave-active {
transition: all 250ms ease;
transition: all $animDuration ease;
}
.list-anim-enter-from,
@@ -15,6 +19,21 @@
width: 100%;
}
// View animation
.view-anim {
&-enter-from,
&-leave-to {
opacity: 0.02;
}
&-enter-active,
&-leave-active {
transition: all $animDuration $animType;
min-height: 100%;
}
}
// Data status list animation
.status-anim {
&-enter-from,
&-leave-to {
@@ -22,10 +41,38 @@
}
&-enter-active {
transition: all 100ms ease-out;
transition: all $animDuration ease-out;
}
&-leave-active {
transition: all 100ms ease-out;
transition: all $animDuration ease-out;
}
}
// Card animation
.card-anim {
&-enter-active,
&-leave-active {
transition: all $animDuration $animType;
}
&-enter-from,
&-leave-to {
opacity: 0;
transform: translate(-50%, -50%) scale(0.45);
}
}
// Modal animation
.modal-anim {
&-enter-active,
&-leave-active {
transition: all $animDuration $animType;
}
&-enter-from,
&-leave-to {
transform: translateY(-25%);
opacity: 0;
}
}
+9 -34
View File
@@ -14,6 +14,8 @@
--clr-error: #df3e3e;
--clr-warning: #c59429;
--clr-honorable: #f47fff;
font-size: 16px;
}
@@ -54,38 +56,6 @@ body {
}
}
.g-tooltip {
position: relative;
display: inline-block;
vertical-align: middle;
.content {
position: absolute;
left: 0;
z-index: 100;
visibility: hidden;
opacity: 0;
min-width: 250px;
background-color: #202020;
text-align: center;
border-radius: 0.5em;
transition: opacity 0.3s;
padding: 0.25em;
}
&:hover > .content {
visibility: visible;
opacity: 1;
}
}
button,
input,
select {
@@ -187,16 +157,23 @@ ul {
&--grayed {
color: #ccc;
}
&--honorable {
color: var(--clr-honorable);
text-shadow: var(--clr-honorable) 0 0 10px;
}
}
button {
cursor: pointer;
color: white;
background: none;
border-radius: 0.25em;
display: flex;
align-items: center;
justify-content: center;
gap: 0.5em;
padding: 0.25em 0.5em;
@@ -211,7 +188,6 @@ button {
button.btn--filled {
background-color: #1a1a1a;
border-radius: 0.25em;
&:hover {
background-color: #2a2a2a;
@@ -245,7 +221,6 @@ button.btn--image {
img {
width: 1.5em;
margin-right: 0.5em;
vertical-align: middle;
}
}
-3
View File
@@ -12,6 +12,3 @@ $accent2Col: #ff3d5d;
$skr: #ff5100;
$twr: #ffbb00;
$animDuration: 150ms;
$animType: ease-in-out;
+4
View File
@@ -328,4 +328,8 @@ export namespace GithubAPI {
body: string;
}
}
export namespace Donators {
export type Response = string[];
}
}
+28 -20
View File
@@ -1,17 +1,17 @@
<template>
<section class="stations-view">
<div class="wrapper">
<div class="body">
<div class="options-bar">
<StationFilterCard
:showCard="filterCardOpen"
:exit="(filterCardOpen = false)"
ref="filterCardRef"
/>
</div>
<div class="stations-options">
<StationFilterCard
:showCard="filterCardOpen"
:exit="(filterCardOpen = false)"
ref="filterCardRef"
/>
<StationTable :stations="computedStationList" />
<Donation :isModalOpen="isDonationModalOpen" @toggleModal="toggleDonationModal" />
</div>
<StationTable :stations="computedStationList" @toggleDonationModal="toggleDonationModal" />
</div>
</section>
</template>
@@ -22,11 +22,13 @@ import StationTable from '../components/StationsView/StationTable.vue';
import StationFilterCard from '../components/StationsView/StationFilterCard.vue';
import { useStationFiltersStore } from '../store/stationFiltersStore';
import { useStore } from '../store/mainStore';
import Donation from '../components/Global/Donation.vue';
export default defineComponent({
components: {
StationTable,
StationFilterCard
StationFilterCard,
Donation
},
data: () => ({
@@ -35,17 +37,25 @@ export default defineComponent({
STORAGE_KEY: 'options_saved',
focusedStationName: '',
filterStore: useStationFiltersStore(),
store: useStore()
store: useStore(),
isDonationModalOpen: false
}),
mounted() {
this.filterStore.setupFilters();
},
computed: {
computedStationList() {
return this.filterStore.filteredStationList;
}
},
mounted() {
this.filterStore.setupFilters();
methods: {
toggleDonationModal(value: boolean) {
this.isDonationModalOpen = value;
}
}
});
</script>
@@ -80,23 +90,21 @@ export default defineComponent({
.stations-view {
position: relative;
display: flex;
justify-content: center;
padding: 1em 0;
min-height: 100%;
}
.wrapper {
display: flex;
justify-content: center;
}
.body {
max-width: 100%;
}
.options-bar {
.stations-options {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.5em;
margin-bottom: 0.5em;
}