mirror of
https://github.com/Spythere/stacjownik.git
synced 2026-05-03 05:18:11 +00:00
restruct: added interal lines info to train schedule; minor fixes
This commit is contained in:
@@ -4,15 +4,17 @@
|
||||
:data-minor="stop.isSBL || (stop.nameRaw.endsWith(', po') && !stop.duration)"
|
||||
>
|
||||
<router-link v-if="/(, podg$|<strong>)/.test(stop.nameHtml)" :to="sceneryHref">
|
||||
<b class="stop-name"
|
||||
><i
|
||||
v-if="!stop.isSceneryOnline"
|
||||
class="fa-solid fa-ban"
|
||||
<b class="stop-name">
|
||||
<span>
|
||||
{{ stop.nameRaw }}
|
||||
</span>
|
||||
<i
|
||||
v-if="!stop.isSceneryOnline && stop.status != 'confirmed'"
|
||||
class="fa-solid fa-ban fa-md"
|
||||
data-tooltip-type="BaseTooltip"
|
||||
:data-tooltip-content="$t('app.tooltip-scenery-offline')"
|
||||
style="margin-right: 0.25rem; color: salmon"
|
||||
style="color: salmon; margin-left: 0.25em"
|
||||
></i>
|
||||
{{ stop.nameRaw }}
|
||||
</b>
|
||||
</router-link>
|
||||
|
||||
@@ -54,7 +56,10 @@
|
||||
<span
|
||||
v-if="
|
||||
stop.position != 'end' &&
|
||||
(stop.duration != 0 || stop.status == 'stopped' || stop.departureDelay != stop.arrivalDelay)
|
||||
(stop.duration != 0 ||
|
||||
stop.position == 'begin' ||
|
||||
stop.status == 'stopped' ||
|
||||
stop.departureDelay != stop.arrivalDelay)
|
||||
"
|
||||
class="date departure"
|
||||
:data-status-delayed="stop.departureDelay > 0"
|
||||
@@ -77,16 +82,16 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent } from 'vue';
|
||||
import { PropType, defineComponent, stop } from 'vue';
|
||||
import dateMixin from '../../mixins/dateMixin';
|
||||
import { TrainScheduleStop } from './typings';
|
||||
import { TrainSchedulePoint } from './typings';
|
||||
|
||||
export default defineComponent({
|
||||
mixins: [dateMixin],
|
||||
|
||||
props: {
|
||||
stop: {
|
||||
type: Object as PropType<TrainScheduleStop>,
|
||||
type: Object as PropType<TrainSchedulePoint>,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
@@ -3,15 +3,15 @@
|
||||
<div class="schedule-wrapper" v-if="train.timetableData">
|
||||
<div class="stops">
|
||||
<div
|
||||
v-for="(stop, i) in scheduleStops"
|
||||
v-for="(stop, i) in scheduleStopsV2"
|
||||
:key="i"
|
||||
class="stop"
|
||||
:data-status="stop.status"
|
||||
:data-sbl="stop.isSBL && stop.sceneryName == scheduleStopsV2[i + 1]?.sceneryName"
|
||||
:data-position="stop.position"
|
||||
:data-delayed="stop.departureDelay > 0"
|
||||
:data-stop-type="stop.type"
|
||||
:data-minor-stop-active="stop.isActive"
|
||||
:data-last-confirmed="stop.isLastConfirmed"
|
||||
:data-is-active="stop.isActive"
|
||||
>
|
||||
<span class="stop_info">
|
||||
<span class="distance">
|
||||
@@ -32,7 +32,7 @@
|
||||
<div></div>
|
||||
|
||||
<div class="progress">
|
||||
<div class="line line_connection" v-if="i < scheduleStops.length - 1"></div>
|
||||
<div class="line line_connection" v-if="i < scheduleStopsV2.length - 1"></div>
|
||||
</div>
|
||||
|
||||
<div class="bottom-line-info">
|
||||
@@ -42,18 +42,21 @@
|
||||
</div>
|
||||
|
||||
<!-- Routes -->
|
||||
|
||||
<span
|
||||
v-if="
|
||||
stop.departureLine &&
|
||||
scheduleStops[i + 1] != undefined &&
|
||||
!/-|_|(^it\d+)|(^sbl)/gi.test(stop.departureLine)
|
||||
(scheduleStopsV2[i + 1]?.arrivalLineInfo?.routeSpeed !=
|
||||
stop.arrivalLineInfo?.routeSpeed ||
|
||||
stop.sceneryName != scheduleStopsV2[i + 1]?.sceneryName)
|
||||
"
|
||||
>
|
||||
<div class="scenery-route">
|
||||
<span>{{ stop.departureLine }}</span>
|
||||
<span v-if="stop.departureLineInfo">
|
||||
| {{ stop.departureLineInfo.routeSpeed }}
|
||||
|
||||
<span v-if="stop.departureLineInfo">
|
||||
<span v-if="stop.departureLineInfo.routeTracks == 1"> ↕</span>
|
||||
<span v-else> ⇵</span> {{ stop.departureLineInfo.routeSpeed }}
|
||||
<img
|
||||
:src="
|
||||
stop.departureLineInfo.isElectric
|
||||
@@ -80,37 +83,42 @@
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="stop.sceneryName != scheduleStops[i + 1]?.sceneryName"
|
||||
v-if="stop.sceneryName != scheduleStopsV2[i + 1]?.sceneryName"
|
||||
class="scenery-change-name"
|
||||
>
|
||||
<span>{{ scheduleStops[i + 1].sceneryName }}</span>
|
||||
<span>{{ scheduleStopsV2[i + 1].sceneryName }}</span>
|
||||
<span v-if="stop.departureLineInfo?.routeTracks == 1"> ↕</span>
|
||||
<span v-else> ⇵</span>
|
||||
</div>
|
||||
|
||||
<div class="scenery-route">
|
||||
<span> {{ scheduleStops[i + 1].arrivalLine }}</span>
|
||||
<div
|
||||
class="scenery-route"
|
||||
v-if="stop.sceneryName != scheduleStopsV2[i + 1]?.sceneryName"
|
||||
>
|
||||
<span> {{ scheduleStopsV2[i + 1].arrivalLine }}</span>
|
||||
|
||||
<span v-if="scheduleStops[i + 1].arrivalLineInfo">
|
||||
| {{ scheduleStops[i + 1].arrivalLineInfo!.routeSpeed }}
|
||||
<span v-if="scheduleStopsV2[i + 1].arrivalLineInfo">
|
||||
<span v-if="stop.arrivalLineInfo?.routeTracks == 1"> ↕</span>
|
||||
<span v-else> ⇵</span>
|
||||
{{ scheduleStopsV2[i + 1].arrivalLineInfo!.routeSpeed }}
|
||||
|
||||
<img
|
||||
:src="
|
||||
scheduleStops[i + 1].arrivalLineInfo!.isElectric
|
||||
scheduleStopsV2[i + 1].arrivalLineInfo?.isElectric
|
||||
? '/images/icon-catenary.svg'
|
||||
: '/images/icon-we4a.png'
|
||||
"
|
||||
data-tooltip-type="BaseTooltip"
|
||||
:data-tooltip-content="
|
||||
$t(
|
||||
`trains.${!scheduleStops[i + 1].arrivalLineInfo!.isElectric ? 'no-' : ''}catenary-tooltip`
|
||||
`trains.${!scheduleStopsV2[i + 1].arrivalLineInfo?.isElectric ? 'no-' : ''}catenary-tooltip`
|
||||
)
|
||||
"
|
||||
width="10"
|
||||
/>
|
||||
|
||||
<img
|
||||
v-if="scheduleStops[i + 1].arrivalLineInfo!.isRouteSBL"
|
||||
v-if="scheduleStopsV2[i + 1].arrivalLineInfo!.isRouteSBL"
|
||||
src="/images/icon-sbl-transparent.svg"
|
||||
width="10"
|
||||
data-tooltip-type="BaseTooltip"
|
||||
@@ -134,8 +142,8 @@ import StopLabel from './StopLabel.vue';
|
||||
import StockList from '../Global/StockList.vue';
|
||||
import { useMainStore } from '../../store/mainStore';
|
||||
import { useApiStore } from '../../store/apiStore';
|
||||
import { Train } from '../../typings/common';
|
||||
import { TrainScheduleStop } from './typings';
|
||||
import { StationRoutesInfo, TimetablePathElement, Train } from '../../typings/common';
|
||||
import { TrainSchedulePoint } from './typings';
|
||||
|
||||
export default defineComponent({
|
||||
components: { StopLabel, StockList },
|
||||
@@ -157,74 +165,183 @@ export default defineComponent({
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
getPathSceneryData(pathEl: TimetablePathElement) {
|
||||
const sceneryData =
|
||||
this.store.stationList?.find((sc) => sc.name == pathEl.stationName) ?? null;
|
||||
|
||||
if (!sceneryData || !sceneryData.generalInfo) return null;
|
||||
|
||||
const activeScenery = this.apiStore.activeData?.activeSceneries?.find(
|
||||
(sc) => sc.stationName == pathEl.stationName
|
||||
);
|
||||
|
||||
const arrivalLineData = pathEl.arrivalRouteExt
|
||||
? (sceneryData.generalInfo.routes.all.find(
|
||||
(rt) => rt.routeName == pathEl.arrivalRouteExt
|
||||
) ?? null)
|
||||
: null;
|
||||
|
||||
const departureLineData = pathEl.departureRouteExt
|
||||
? (sceneryData.generalInfo.routes.all.find(
|
||||
(rt) => rt.routeName == pathEl.departureRouteExt
|
||||
) ?? null)
|
||||
: null;
|
||||
|
||||
return {
|
||||
generalInfo: sceneryData.generalInfo,
|
||||
isOnline:
|
||||
activeScenery &&
|
||||
(activeScenery.isOnline == 1 || activeScenery.lastSeen >= Date.now() - 60000),
|
||||
arrivalLineData,
|
||||
departureLineData
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
scheduleStops(): TrainScheduleStop[] {
|
||||
scheduleStopsV2() {
|
||||
if (!this.train.timetableData) return [];
|
||||
|
||||
const { timetablePath } = this.train.timetableData;
|
||||
const { timetablePath, followingStops } = this.train.timetableData;
|
||||
|
||||
const stopRows: TrainSchedulePoint[] = [];
|
||||
|
||||
let currentPathIndex = 0;
|
||||
let currentPath = timetablePath[0];
|
||||
|
||||
return (
|
||||
this.train.timetableData?.followingStops.map((stop, i, arr) => {
|
||||
const isExternal =
|
||||
i < arr.length - 1 &&
|
||||
stop.departureLine === timetablePath[currentPathIndex].departureRouteExt;
|
||||
let pathData = this.getPathSceneryData(currentPath);
|
||||
|
||||
const sceneryName = timetablePath[currentPathIndex].stationName;
|
||||
const sceneryInfo = this.apiStore.sceneryData.find((st) => st.name == sceneryName);
|
||||
let arrivalLineInfo: StationRoutesInfo | null = null;
|
||||
let departureLineInfo: StationRoutesInfo | null = null;
|
||||
|
||||
const isSceneryOnline =
|
||||
(this.apiStore.activeData?.activeSceneries?.find((sc) => sc.stationName == sceneryName)
|
||||
?.isOnline ?? 0) == 1;
|
||||
let isActive = false;
|
||||
|
||||
const arrivalLineInfo = sceneryInfo?.routesInfo.find(
|
||||
(r) => r.routeName == stop.arrivalLine
|
||||
);
|
||||
if (pathData?.departureLineData) {
|
||||
arrivalLineInfo = pathData.departureLineData;
|
||||
departureLineInfo = pathData.departureLineData;
|
||||
}
|
||||
|
||||
const departureLineInfo = sceneryInfo?.routesInfo.find(
|
||||
(r) => r.routeName == stop.departureLine
|
||||
);
|
||||
for (const stop of followingStops) {
|
||||
let isExternal = false;
|
||||
|
||||
if (isExternal) currentPathIndex++;
|
||||
if (
|
||||
stop.arrivalLine &&
|
||||
currentPath.arrivalRouteExt &&
|
||||
stop.arrivalLine == currentPath.arrivalRouteExt
|
||||
) {
|
||||
isExternal = true;
|
||||
|
||||
return {
|
||||
nameHtml: stop.stopName,
|
||||
nameRaw: stop.stopNameRAW,
|
||||
if (pathData?.arrivalLineData) {
|
||||
arrivalLineInfo = pathData.arrivalLineData;
|
||||
}
|
||||
}
|
||||
|
||||
arrivalScheduled: stop.arrivalTimestamp,
|
||||
arrivalReal: stop.arrivalRealTimestamp,
|
||||
let correctedDepartureLineData: StationRoutesInfo | null = null;
|
||||
|
||||
departureScheduled: stop.departureTimestamp,
|
||||
departureReal: stop.departureRealTimestamp,
|
||||
const internalRouteInfo = stop.departureLine
|
||||
? pathData?.generalInfo.routes.all.find(
|
||||
(route) => route.isInternal && route.routeName == stop.departureLine
|
||||
)
|
||||
: undefined;
|
||||
|
||||
departureDelay: stop.departureDelay,
|
||||
arrivalDelay: stop.arrivalDelay,
|
||||
if (internalRouteInfo) {
|
||||
correctedDepartureLineData = internalRouteInfo;
|
||||
departureLineInfo = internalRouteInfo;
|
||||
}
|
||||
|
||||
duration: stop.stopTime,
|
||||
let rowData: TrainSchedulePoint = {
|
||||
nameHtml: stop.stopName,
|
||||
nameRaw: stop.stopNameRAW,
|
||||
|
||||
comments: stop.comments ?? null,
|
||||
arrivalScheduled: stop.arrivalTimestamp,
|
||||
arrivalReal: stop.arrivalRealTimestamp,
|
||||
|
||||
arrivalLine: stop.arrivalLine,
|
||||
departureLine: stop.departureLine,
|
||||
departureScheduled: stop.departureTimestamp,
|
||||
departureReal: stop.departureRealTimestamp,
|
||||
|
||||
arrivalLineInfo: arrivalLineInfo,
|
||||
departureLineInfo: departureLineInfo,
|
||||
departureDelay: stop.departureDelay,
|
||||
arrivalDelay: stop.arrivalDelay,
|
||||
|
||||
isExternal,
|
||||
duration: stop.stopTime ?? 0,
|
||||
type: stop.stopType,
|
||||
distance: stop.stopDistance,
|
||||
|
||||
type: stop.stopType,
|
||||
distance: stop.stopDistance,
|
||||
isActive: this.activeMinorStops.includes(i),
|
||||
isLastConfirmed: this.lastConfirmed === i && !stop.terminatesHere,
|
||||
isSBL: /sbl/gi.test(stop.stopName),
|
||||
position: stop.beginsHere ? 'begin' : stop.terminatesHere ? 'end' : 'en-route',
|
||||
status: stop.confirmed ? 'confirmed' : stop.stopped ? 'stopped' : 'unconfirmed',
|
||||
comments: stop.comments ?? null,
|
||||
|
||||
sceneryName,
|
||||
isSceneryOnline
|
||||
};
|
||||
}) ?? []
|
||||
);
|
||||
arrivalLine: stop.arrivalLine,
|
||||
departureLine: stop.departureLine,
|
||||
|
||||
arrivalLineInfo,
|
||||
departureLineInfo,
|
||||
|
||||
isExternal,
|
||||
|
||||
isActive: isActive,
|
||||
|
||||
isSBL: /sbl/gi.test(stop.stopName),
|
||||
position: stop.beginsHere ? 'begin' : stop.terminatesHere ? 'end' : 'en-route',
|
||||
status: stop.confirmed ? 'confirmed' : stop.stopped ? 'stopped' : 'unconfirmed',
|
||||
|
||||
sceneryName: currentPath.stationName,
|
||||
isSceneryOnline: pathData?.isOnline ?? false
|
||||
};
|
||||
|
||||
if (internalRouteInfo) {
|
||||
arrivalLineInfo = departureLineInfo;
|
||||
}
|
||||
|
||||
if (
|
||||
stopRows[stopRows.length - 1]?.status == 'confirmed' &&
|
||||
rowData.status != 'confirmed' &&
|
||||
rowData.status != 'stopped'
|
||||
)
|
||||
stopRows[stopRows.length - 1].isActive = true;
|
||||
|
||||
if (
|
||||
stopRows[stopRows.length - 1]?.isActive == true &&
|
||||
!/(^<strong>|, podg$)/.test(rowData.nameHtml)
|
||||
)
|
||||
rowData.isActive = true;
|
||||
|
||||
stopRows.push(rowData);
|
||||
|
||||
if (
|
||||
stop.departureLine &&
|
||||
currentPath.departureRouteExt &&
|
||||
stop.departureLine == currentPath.departureRouteExt
|
||||
) {
|
||||
// Reverse search for last scenery checkpoint
|
||||
if (pathData?.departureLineData) {
|
||||
stopRows[stopRows.length - 1].isExternal = true;
|
||||
|
||||
for (let i = stopRows.length - 1; i > 0; i--) {
|
||||
stopRows[i].departureLineInfo = pathData.departureLineData;
|
||||
// stopRows[i].departureTracks = pathData.departureLineData.routeTracks;
|
||||
// stopRows[i].departureSpeed = pathData.departureLineData.routeSpeed;
|
||||
// stopRows[i].departureElectric = pathData.departureLineData.isElectric;
|
||||
// stopRows[i].realLineNo = pathData.departureLineData.realLineNo ?? 0;
|
||||
|
||||
if (/(^<strong>|, podg$)/.test(stopRows[i].nameHtml)) {
|
||||
// stopRows[i].departureSpeed = pathData.departureLineData.routeSpeed;
|
||||
// stopRows[i].departureTracks = pathData.departureLineData.routeTracks;
|
||||
// stopRows[i].departureElectric = pathData.departureLineData.isElectric;
|
||||
break;
|
||||
}
|
||||
stopRows[i].arrivalLineInfo = pathData.departureLineData;
|
||||
|
||||
// stopRows[i].arrivalSpeed = pathData.departureLineData.routeSpeed;
|
||||
// stopRows[i].arrivalTracks = pathData.departureLineData.routeTracks;
|
||||
// stopRows[i].arrivalElectric = pathData.departureLineData.isElectric;
|
||||
}
|
||||
}
|
||||
|
||||
currentPath = timetablePath[++currentPathIndex];
|
||||
pathData = this.getPathSceneryData(currentPath);
|
||||
}
|
||||
}
|
||||
|
||||
return stopRows;
|
||||
},
|
||||
|
||||
lastConfirmed() {
|
||||
@@ -297,6 +414,10 @@ $blinkAnim: 0.5s ease-in-out alternate infinite blink;
|
||||
}
|
||||
|
||||
.stop {
|
||||
&[data-sbl='true'] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Begin stop
|
||||
&[data-position='begin'] {
|
||||
.node {
|
||||
@@ -328,20 +449,19 @@ $blinkAnim: 0.5s ease-in-out alternate infinite blink;
|
||||
border-color: $haltClr;
|
||||
}
|
||||
|
||||
&[data-minor-stop-active='true'] {
|
||||
.progress > .line {
|
||||
animation: $blinkAnim;
|
||||
}
|
||||
// &[data-minor-stop-active='true'] {
|
||||
// .progress > .line {
|
||||
// animation: $blinkAnim;
|
||||
// }
|
||||
|
||||
& + div {
|
||||
.progress > .line_node-top {
|
||||
animation: $blinkAnim;
|
||||
}
|
||||
}
|
||||
}
|
||||
// & + div {
|
||||
// .progress > .line_node-top {
|
||||
// animation: $blinkAnim;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// Last confirmed outpost / checkpoint
|
||||
&[data-last-confirmed='true'] {
|
||||
&[data-is-active='true'] {
|
||||
.progress > .line_connection {
|
||||
animation: $blinkAnim;
|
||||
}
|
||||
@@ -362,6 +482,7 @@ $blinkAnim: 0.5s ease-in-out alternate infinite blink;
|
||||
.progress > .node {
|
||||
border-color: $confirmedClr;
|
||||
}
|
||||
|
||||
.progress > .line {
|
||||
border-left: 2px solid $confirmedClr;
|
||||
border-right: 2px solid $confirmedClr;
|
||||
|
||||
@@ -164,4 +164,36 @@ export interface TrainScheduleStop {
|
||||
isExternal: boolean;
|
||||
|
||||
comments: string | null;
|
||||
}
|
||||
|
||||
export interface TrainSchedulePoint {
|
||||
nameHtml: string;
|
||||
nameRaw: string;
|
||||
|
||||
status: 'confirmed' | 'unconfirmed' | 'stopped';
|
||||
position: 'begin' | 'end' | 'en-route';
|
||||
type: string;
|
||||
duration: number;
|
||||
distance: number;
|
||||
arrivalScheduled: number;
|
||||
arrivalReal: number;
|
||||
departureScheduled: number;
|
||||
departureReal: number;
|
||||
|
||||
comments: string | null;
|
||||
|
||||
arrivalDelay: number;
|
||||
departureDelay: number;
|
||||
arrivalLine: string | null;
|
||||
departureLine: string | null;
|
||||
|
||||
arrivalLineInfo: StationRoutesInfo | null;
|
||||
departureLineInfo: StationRoutesInfo | null;
|
||||
|
||||
isExternal: boolean,
|
||||
|
||||
isActive: boolean;
|
||||
isSBL: boolean;
|
||||
sceneryName: string | null;
|
||||
isSceneryOnline: boolean;
|
||||
}
|
||||
@@ -112,7 +112,10 @@ export const useMainStore = defineStore('mainStore', {
|
||||
: undefined
|
||||
} as Train;
|
||||
|
||||
const stationNameKey = train.currentStationName.indexOf('.sc') != -1 ? train.currentStationName.split(' ').slice(0, -1).join(' ') : train.currentStationName;
|
||||
const stationNameKey =
|
||||
train.currentStationName.indexOf('.sc') != -1
|
||||
? train.currentStationName.split(' ').slice(0, -1).join(' ')
|
||||
: train.currentStationName;
|
||||
|
||||
// Sceneries trains map
|
||||
if (sceneriesTrains.has(stationNameKey)) {
|
||||
@@ -319,6 +322,8 @@ export const useMainStore = defineStore('mainStore', {
|
||||
return apiStore.sceneryData.map((scenery) => {
|
||||
const routes = scenery.routesInfo.reduce(
|
||||
(acc, route) => {
|
||||
acc['all'].push(route);
|
||||
|
||||
if (route.hidden) return acc;
|
||||
|
||||
const tracksKey = route.routeTracks == 2 ? 'double' : 'single';
|
||||
@@ -349,6 +354,7 @@ export const useMainStore = defineStore('mainStore', {
|
||||
doubleElectrifiedNames: [],
|
||||
doubleOtherNames: [],
|
||||
sblNames: [],
|
||||
all: [],
|
||||
minRouteSpeed: 0,
|
||||
maxRouteSpeed: 0
|
||||
} as StationRoutes
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { RouteLocationRaw } from 'vue-router';
|
||||
import { StationJSONData } from '../store/typings';
|
||||
|
||||
export type Availability = 'default' | 'unavailable' | 'nonPublic' | 'abandoned' | 'nonDefault';
|
||||
export type ScenerySpawnType = 'passenger' | 'freight' | 'loco' | 'all';
|
||||
@@ -90,6 +91,7 @@ export interface TrainTimetableData {
|
||||
routeDistance: number;
|
||||
sceneries: string[];
|
||||
timetablePath: TimetablePathElement[];
|
||||
trainMaxSpeed: number;
|
||||
}
|
||||
|
||||
export interface Station {
|
||||
@@ -122,6 +124,7 @@ export interface StationGeneralInfo {
|
||||
export interface StationRoutes {
|
||||
single: StationRoutesInfo[];
|
||||
double: StationRoutesInfo[];
|
||||
all: StationRoutesInfo[];
|
||||
|
||||
singleElectrifiedNames: string[];
|
||||
singleOtherNames: string[];
|
||||
@@ -142,6 +145,7 @@ export interface StationRoutesInfo {
|
||||
routeSpeed: number;
|
||||
routeTracks: number;
|
||||
hidden?: boolean;
|
||||
realLineNo?: number;
|
||||
}
|
||||
|
||||
export interface ActiveScenery {
|
||||
|
||||
Reference in New Issue
Block a user