mirror of
https://github.com/Spythere/genera-tor.git
synced 2026-05-02 21:18:12 +00:00
@@ -2,3 +2,4 @@ VITE_APP_API_URL=https://stacjownik.spythere.eu/api
|
||||
VITE_APP_SWDR_URL=https://api.td2.info.pl
|
||||
|
||||
VITE_APP_ORDER_VERSION=2
|
||||
#VITE_UPDATE_TEST='test'
|
||||
@@ -0,0 +1,17 @@
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
github-releases-to-discord:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Github Releases To Discord
|
||||
uses: SethCohen/github-releases-to-discord@v1.13.1
|
||||
with:
|
||||
webhook_url: '${{ secrets.WEBHOOK_URL }}'
|
||||
color: '9936031'
|
||||
footer_title: 'Changelog - GeneraTOR'
|
||||
footer_timestamp: true
|
||||
+5
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "genera-tor",
|
||||
"version": "1.5.2",
|
||||
"version": "1.6.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
@@ -13,14 +13,17 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.6.2",
|
||||
"lucide-vue-next": "^0.525.0",
|
||||
"pinia": "^2.1.7",
|
||||
"showdown": "^2.1.0",
|
||||
"vue": "^3.3.11",
|
||||
"vue-i18n": "9.8.0",
|
||||
"vue-i18n": "11",
|
||||
"vue-router": "^4.2.5",
|
||||
"vue-tsc": "^2.2.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.13.10",
|
||||
"@types/showdown": "^2.0.6",
|
||||
"@vitejs/plugin-vue": "^4.5.2",
|
||||
"@vue/eslint-config-prettier": "^8.0.0",
|
||||
"@vue/eslint-config-typescript": "^12.0.0",
|
||||
|
||||
+77
-3
@@ -1,17 +1,20 @@
|
||||
<template>
|
||||
<div id="app_wrapper">
|
||||
<UpdateCard />
|
||||
|
||||
<router-view />
|
||||
|
||||
<transition name="slide-anim">
|
||||
<div v-if="needRefresh" class="update-prompt" @click="updateServiceWorker(true)">
|
||||
Nowa wersja GeneraTORa dostępna!
|
||||
<u>Kliknij, aby odświeżyć aplikację!</u>
|
||||
{{ $t('update.update-available-text') }}
|
||||
<u>{{ $t('update.update-available-underline') }}</u>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<footer>
|
||||
© <a href="https://td2.info.pl/profile/?u=20777">Spythere</a>
|
||||
{{ new Date().getUTCFullYear() }} | v.{{ appVersion }}
|
||||
{{ new Date().getUTCFullYear() }} |
|
||||
<button class="g-button text" @click="store.updateCardOpen = true">v{{ appVersion }}</button>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
@@ -22,8 +25,15 @@ import { defineComponent } from 'vue';
|
||||
import packageInfo from '../package.json';
|
||||
import { useStore } from './store/store';
|
||||
import orderStorageMixin from './mixins/orderStorageMixin';
|
||||
import StorageManager from './managers/storageManager';
|
||||
import axios from 'axios';
|
||||
import UpdateCard from './components/UpdateCard.vue';
|
||||
|
||||
const STORAGE_VERSION_KEY = 'app_version';
|
||||
|
||||
export default defineComponent({
|
||||
components: { UpdateCard },
|
||||
|
||||
mixins: [orderStorageMixin],
|
||||
|
||||
setup() {
|
||||
@@ -31,14 +41,29 @@ export default defineComponent({
|
||||
|
||||
return { offlineReady, needRefresh, updateServiceWorker };
|
||||
},
|
||||
|
||||
data() {
|
||||
return { appVersion: packageInfo.version, store: useStore() };
|
||||
},
|
||||
|
||||
created() {
|
||||
this.init();
|
||||
},
|
||||
|
||||
methods: {
|
||||
init() {
|
||||
this.loadLang();
|
||||
this.loadSettings();
|
||||
this.checkAppVersion();
|
||||
this.handleQueries();
|
||||
},
|
||||
|
||||
loadSettings() {
|
||||
document.title = `GeneraTOR ${this.appVersion}`;
|
||||
this.store.orderDarkMode = this.getOrderSetting('dark-mode') === 'true';
|
||||
},
|
||||
|
||||
handleQueries() {
|
||||
const query = new URLSearchParams(window.location.search);
|
||||
|
||||
const id = query.get('sceneryId');
|
||||
@@ -46,6 +71,55 @@ export default defineComponent({
|
||||
if (id != null) {
|
||||
this.store.orderMode = 'OrderTrainPicker';
|
||||
}
|
||||
},
|
||||
|
||||
async checkAppVersion() {
|
||||
const storageVersion = StorageManager.getStringValue(STORAGE_VERSION_KEY);
|
||||
|
||||
try {
|
||||
const releaseData = await (
|
||||
await axios.get('https://api.github.com/repos/Spythere/genera-tor/releases/latest')
|
||||
).data;
|
||||
|
||||
if (!releaseData) return;
|
||||
|
||||
this.store.appUpdateData.version = this.appVersion;
|
||||
this.store.appUpdateData.changelog = releaseData.body;
|
||||
this.store.appUpdateData.releaseURL = releaseData.html_url;
|
||||
|
||||
this.store.updateCardOpen =
|
||||
(storageVersion != '' && storageVersion != this.appVersion) ||
|
||||
import.meta.env.VITE_UPDATE_TEST === 'test';
|
||||
} catch (error) {
|
||||
console.error(`Wystąpił błąd podczas pobierania danych z API GitHuba: ${error}`);
|
||||
}
|
||||
|
||||
StorageManager.setStringValue(STORAGE_VERSION_KEY, this.appVersion);
|
||||
},
|
||||
|
||||
changeLang(lang: string) {
|
||||
this.$i18n.locale = lang;
|
||||
this.store.currentAppLocale = lang;
|
||||
|
||||
StorageManager.setStringValue('lang', lang);
|
||||
},
|
||||
|
||||
loadLang() {
|
||||
const storageLang = StorageManager.getStringValue('lang');
|
||||
|
||||
if (storageLang) {
|
||||
this.changeLang(storageLang);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!window.navigator.language) return;
|
||||
|
||||
const naviLanguage = window.navigator.language.toString();
|
||||
|
||||
if (!naviLanguage.startsWith('pl')) {
|
||||
this.changeLang('en');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<section class="order-list">
|
||||
<h3>Zapisane rozkazy pisemne ({{ localOrderList.length }})</h3>
|
||||
<h3>{{ $t('order-list.title') }} ({{ localOrderList.length }})</h3>
|
||||
|
||||
<transition-group name="list" tag="ul">
|
||||
<li class="no-orders-warning" v-if="sortedOrderList.length == 0" :key="-1">
|
||||
Brak zapisanych rozkazów!
|
||||
{{ $t('order-list.no-saved-orders') }}
|
||||
</li>
|
||||
|
||||
<li
|
||||
@@ -14,8 +14,13 @@
|
||||
>
|
||||
<b class="text--accent">#{{ order.id.split('-')[1] }} </b>
|
||||
<b>
|
||||
{{ getOrderName(order.orderType) }} nr {{ order.orderBody['header']['orderNo'] }} dla
|
||||
pociągu nr {{ order.orderBody['header']['trainNo'] }}
|
||||
{{
|
||||
$t('order-list.order-title', {
|
||||
orderName: getOrderName(order.orderType),
|
||||
orderNo: order.orderBody['header']['orderNo'],
|
||||
trainNo: order.orderBody['header']['trainNo']
|
||||
})
|
||||
}}
|
||||
</b>
|
||||
<span
|
||||
v-if="!order.orderVersion || order.orderVersion != ORDER_VERSION"
|
||||
@@ -25,14 +30,18 @@
|
||||
>⚠
|
||||
</span>
|
||||
<br />
|
||||
{{ order.createdAt ? 'Dodano: ' : 'Zaktualizowano: ' }}
|
||||
{{ $t(`order-list.order-${order.createdAt ? 'added' : 'updated'}`) }}
|
||||
{{ new Date(order.createdAt || order.updatedAt || 0).toLocaleString('pl-PL') }}
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="buttons">
|
||||
<button class="g-button" @click="selectLocalOrder(order)">Wybierz</button>
|
||||
<button class="g-button" @click="removeOrder(order)">Usuń</button>
|
||||
<button class="g-button" @click="selectLocalOrder(order)">
|
||||
{{ $t('order-list.button-order-select') }}
|
||||
</button>
|
||||
<button class="g-button" @click="removeOrder(order)">
|
||||
{{ $t('order-list.button-order-remove') }}
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
</transition-group>
|
||||
@@ -65,7 +74,7 @@ export default defineComponent({
|
||||
|
||||
methods: {
|
||||
getOrderName(orderType: string) {
|
||||
return `Rozkaz "${orderType.split('order')[1]}"`;
|
||||
return orderType.split('order')[1];
|
||||
},
|
||||
|
||||
removeOrder(order: LocalStorageOrder) {
|
||||
|
||||
@@ -1,22 +1,25 @@
|
||||
<template>
|
||||
<section class="order-message">
|
||||
<h3>Wiadomość do wyświetlenia na czacie symulatora:</h3>
|
||||
<h3>{{ $t('order-message.title') }}</h3>
|
||||
|
||||
<div class="message_body" v-html="fullOrderMessage"></div>
|
||||
<div class="message_body" v-html="orderMessagePreview"></div>
|
||||
<p class="message_info">
|
||||
Po wygenerowaniu rozkazu skopiuj jego treść lub zapisz w pamięci przeglądarki za pomocą
|
||||
przycisków poniżej
|
||||
{{ $t('order-message.info') }}
|
||||
</p>
|
||||
|
||||
<div class="message_actions">
|
||||
<button class="g-button action" @click="saveOrder">Zapisz nowy rozkaz</button>
|
||||
<button class="g-button action" @click="copyMessage">Kopiuj treść rozkazu</button>
|
||||
<button class="g-button action" @click="saveOrder">
|
||||
{{ $t('order-message.button-save') }}
|
||||
</button>
|
||||
<button class="g-button action" @click="copyMessage">
|
||||
{{ $t('order-message.button-copy') }}
|
||||
</button>
|
||||
<button
|
||||
class="g-button action"
|
||||
:data-disabled="!store.chosenLocalOrderId"
|
||||
@click="updateOrder"
|
||||
>
|
||||
Zaktualizuj rozkaz
|
||||
{{ $t('order-message.button-update') }}
|
||||
<span class="text--accent"
|
||||
>{{ store.chosenLocalOrderId && `#${store.chosenLocalOrderId.split('-')[1]}` }}
|
||||
</span>
|
||||
@@ -32,7 +35,7 @@
|
||||
v-model="store.orderDarkMode"
|
||||
@change="onCheckboxChange"
|
||||
/>
|
||||
<span>Ciemny motyw rozkazu</span>
|
||||
<span>{{ $t('order-options.dark-mode') }}</span>
|
||||
</label>
|
||||
|
||||
<label for="copy-increment" class="g-checkbox">
|
||||
@@ -43,7 +46,7 @@
|
||||
v-model="incrementOnCopy"
|
||||
@change="onCheckboxChange"
|
||||
/>
|
||||
<span>Aktualizuj numer rozkazu po skopiowaniu</span>
|
||||
<span>{{ $t('order-options.update-number-on-copy') }}</span>
|
||||
</label>
|
||||
|
||||
<label for="save-increment" class="g-checkbox">
|
||||
@@ -54,7 +57,7 @@
|
||||
v-model="incrementOnSave"
|
||||
@change="onCheckboxChange"
|
||||
/>
|
||||
<span>Aktualizuj numer rozkazu po zapisaniu</span>
|
||||
<span>{{ $t('order-options.update-number-on-save') }}</span>
|
||||
</label>
|
||||
|
||||
<label for="update-date" class="g-checkbox">
|
||||
@@ -65,7 +68,7 @@
|
||||
v-model="updateDate"
|
||||
@change="onCheckboxChange"
|
||||
/>
|
||||
<span>Aktualizuj godziny przy edycji</span>
|
||||
<span>{{ $t('order-options.update-hours') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -116,6 +119,11 @@ export default defineComponent({
|
||||
computed: {
|
||||
fullOrderMessage() {
|
||||
return this.store.orderMessage + this.store.footerMessage;
|
||||
},
|
||||
|
||||
// Replace all new line tags with <br> for preview and get rid of the first one (visible only on simulator's chat)
|
||||
orderMessagePreview() {
|
||||
return this.fullOrderMessage.replace(/\n/g, '<br>').replace('<br>', '');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -165,27 +173,25 @@ export default defineComponent({
|
||||
|
||||
copyMessage() {
|
||||
if (!navigator.clipboard)
|
||||
return this.showActionMonit(
|
||||
'Ups! Twoja przeglądarka musi być dosyć przestarzała, ponieważ nie obsługuje zapisu do schowka! :/'
|
||||
);
|
||||
return this.showActionMonit(this.$t('order-message.warning-outdated-clipboard'));
|
||||
|
||||
const hasAtLeastOneRow = /(\[ \d \])/g.test(this.fullOrderMessage);
|
||||
const hasAllInputsFilled = !/_/g.test(this.store.orderMessage);
|
||||
|
||||
if (!hasAllInputsFilled)
|
||||
return this.showActionMonit(
|
||||
`<span class="text--warn">Wypełnij puste rubryki rozkazu przed jego skopiowaniem!</span>`
|
||||
`<span class="text--warn">${this.$t('order-message.warning-fill-inputs')}</span>`
|
||||
);
|
||||
if (!hasAtLeastOneRow)
|
||||
return this.showActionMonit(
|
||||
`<span class="text--warn">Dodaj co najmniej jedną działkę rozkazu przed jego skopiowaniem!</span>`
|
||||
`<span class="text--warn">${this.$t('order-message.warning-add-rows')}</span>`
|
||||
);
|
||||
|
||||
const fieldsToCorrect = this.verifyOrderFields();
|
||||
|
||||
if (fieldsToCorrect.length > 0)
|
||||
return this.showActionMonit(
|
||||
`<span class="text--warn">Uzupełnij następujące rubryki na dole rozkazu przed jego skopiowaniem: ${fieldsToCorrect.join(
|
||||
`<span class="text--warn">${this.$t('order-message.warning-fill-footer')} ${fieldsToCorrect.join(
|
||||
', '
|
||||
)}</span>`
|
||||
);
|
||||
@@ -194,9 +200,7 @@ export default defineComponent({
|
||||
|
||||
if (this.incrementOnCopy) this.incrementOrderNo();
|
||||
|
||||
this.showActionMonit(
|
||||
'<b class="text--accent">Skopiowano!</b> Możesz teraz wkleić treść rozkazu na czacie symulatora!'
|
||||
);
|
||||
this.showActionMonit(this.$t('order-message.success-copy-html'));
|
||||
},
|
||||
|
||||
saveOrder() {
|
||||
@@ -205,18 +209,16 @@ export default defineComponent({
|
||||
switch (savedOrderStatus) {
|
||||
case -1:
|
||||
this.showActionMonit(
|
||||
'<span class="text--warn">Wypełnij numer rozkazu, numer pociągu i datę zanim dodasz rozkaz!</span>'
|
||||
`<span class="text--warn">${this.$t('order-message.warning-fill-top')}</span>`
|
||||
);
|
||||
break;
|
||||
case 0:
|
||||
this.showActionMonit(
|
||||
'<span class="text--warn">Ostatni zapisany rozkaz jest identyczny z obecnym!</span>'
|
||||
`<span class="text--warn">${this.$t('order-message.warning-order-identical')}</span>`
|
||||
);
|
||||
break;
|
||||
case 1:
|
||||
this.showActionMonit(
|
||||
'Zapisano treść <b class="text--accent">rozkazu</b> w pamięci przeglądarki!'
|
||||
);
|
||||
this.showActionMonit(this.$t('order-message.success-save-html'));
|
||||
|
||||
if (this.incrementOnSave) this.incrementOrderNo();
|
||||
break;
|
||||
@@ -232,18 +234,18 @@ export default defineComponent({
|
||||
switch (updatedOrderStatus) {
|
||||
case -1:
|
||||
this.showActionMonit(
|
||||
'<span class="text--warn">Wystąpił błąd podczas aktualizowania tego rozkazu! :/</span>'
|
||||
`<span class="text--warn">${this.$t('order-message.error-update')}</span>`
|
||||
);
|
||||
break;
|
||||
|
||||
case 0:
|
||||
this.showActionMonit(
|
||||
'<span class="text--warn">Nie wybrałeś żadnego zapisanego rozkazu!</span>'
|
||||
`<span class="text--warn">${this.$t('order-message.warning-no-order-selected')}</span>`
|
||||
);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
this.showActionMonit('Zaktualizowano treść <b class="text--accent">rozkazu</b>!');
|
||||
this.showActionMonit(this.$t('order-message.success-update-html'));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,7 +350,7 @@ export default defineComponent({
|
||||
() => {
|
||||
const { header } = order;
|
||||
|
||||
const message = `<i>Rozkaz pisemny "N" nr ${header.orderNo || '_'} dla pociągu nr ${
|
||||
const message = `\n<i><b>Rozkaz pisemny "N" nr ${header.orderNo || '_'}</b> dla pociągu nr ${
|
||||
header.trainNo || '_'
|
||||
} dnia ${header.date}</i>`;
|
||||
|
||||
@@ -508,7 +508,7 @@ export default defineComponent({
|
||||
for (let i = 0; i < this.order.rows.length; i++) {
|
||||
if (!this.order.rows[i].enabled) continue;
|
||||
|
||||
message += ` <b> [ ${i + 1} ] </b> ${this.rowMethods[i + 1]()}`;
|
||||
message += `\n--------\n<b>[ ${i + 1} ]</b> ${this.rowMethods[i + 1]()}`;
|
||||
}
|
||||
|
||||
this.store.orderMessage = message;
|
||||
|
||||
+11
-13
@@ -90,7 +90,7 @@ export default defineComponent({
|
||||
() => {
|
||||
const { header } = order;
|
||||
|
||||
return `<i>Rozkaz pisemny "O" nr ${header.orderNo || '_'} dla pociągu nr ${
|
||||
return `\n<i><b>Rozkaz pisemny "O" nr ${header.orderNo || '_'}</b> dla pociągu nr ${
|
||||
header.trainNo || '_'
|
||||
} dnia ${header.date || '_'}</i>`;
|
||||
}
|
||||
@@ -120,28 +120,26 @@ export default defineComponent({
|
||||
generateMessage() {
|
||||
let message = this.rowMethods[0]();
|
||||
|
||||
if (this.order.orderList.some((row) => row.name)) message += `<b> [ 1 ] </b>`;
|
||||
if (this.order.orderList.some((row) => row.name)) {
|
||||
message += `\n--------\n<b>[ 1 ]</b>`;
|
||||
message += '\n1) zmniejszyć prędkość jazdy i zachować ostrożność'
|
||||
message += '\n2) jechać ostrożnie (j.o.)\n'
|
||||
}
|
||||
|
||||
const rowsMessageList = [];
|
||||
|
||||
for (let i = 0; i < this.order.orderList.length; i++) {
|
||||
const row = this.order.orderList[i];
|
||||
if (!row.name) continue;
|
||||
|
||||
let rowMessage = '';
|
||||
rowMessage += ` ${row.name || '_'} od ${row.from || '_'} do ${row.to || '_'} kilometra`;
|
||||
message += `\n- ${row.name || '_'} od ${row.from || '_'} do ${row.to || '_'} kilometra`;
|
||||
|
||||
if (row.vmax) rowMessage += ` prędkość najwyżej ${row.vmax} km/h`;
|
||||
if (row.jo) rowMessage += ` jechać ostrożnie`;
|
||||
if (row.vmax) message += ` prędkość najwyżej ${row.vmax} km/h`;
|
||||
if (row.jo) message += ` jechać ostrożnie`;
|
||||
|
||||
rowMessage += ` z powodu: ${row.reason || '_'}`;
|
||||
|
||||
rowsMessageList.push(rowMessage);
|
||||
message += ` z powodu: ${row.reason || '_'}`;
|
||||
}
|
||||
|
||||
message += rowsMessageList.join('; ');
|
||||
|
||||
if (this.order.other) message += ` <b> [ 2 ] </b> Inne: ${this.order.other}`;
|
||||
if (this.order.other) message += `\n--------\n<b>[ 2 ]</b> Inne: ${this.order.other}`;
|
||||
|
||||
this.store.orderMessage = message;
|
||||
}
|
||||
|
||||
@@ -300,7 +300,7 @@ export default defineComponent({
|
||||
() => {
|
||||
const { header } = order;
|
||||
|
||||
return `<i>Rozkaz pisemny "S" nr ${header.orderNo || '_'} dla ${header.for || '_'} nr ${
|
||||
return `\n<i><b>Rozkaz pisemny "S" nr ${header.orderNo || '_'}</b> dla ${header.for || '_'} nr ${
|
||||
header.trainNo || '_'
|
||||
} dnia ${header.date || '_'}</i>`;
|
||||
},
|
||||
@@ -429,7 +429,7 @@ export default defineComponent({
|
||||
for (let i = 0; i < 4; i++) {
|
||||
if (!this.order.rows[i].enabled) continue;
|
||||
|
||||
message += ` <b> [ ${i + 1} ] </b> ${this.rowMethods[i + 1]()}`;
|
||||
message += `\n--------\n<b>[ ${i + 1} ]</b> ${this.rowMethods[i + 1]()}`;
|
||||
}
|
||||
|
||||
this.store.orderMessage = message;
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
v-model="selectedSceneryId"
|
||||
@change="selectOption"
|
||||
>
|
||||
<option :value="null" disabled>Sceneria</option>
|
||||
<option :value="null" disabled>
|
||||
{{ $t('order-train-picker.placeholder-scenery-name') }}
|
||||
</option>
|
||||
<option
|
||||
v-for="scenery in filteredSceneries"
|
||||
:value="`${scenery.stationName}|${scenery.stationHash}|${scenery.dispatcherName}|${scenery.region}`"
|
||||
@@ -24,7 +26,9 @@
|
||||
v-model="selectedRegion"
|
||||
@change="selectOption"
|
||||
>
|
||||
<option :value="null" disabled>Region</option>
|
||||
<option :value="null" disabled>
|
||||
{{ $t('order-train-picker.placeholder-region-name') }}
|
||||
</option>
|
||||
<option v-for="region in regions" :value="region" :key="region">
|
||||
{{ getRegionNameById(region) }}
|
||||
</option>
|
||||
@@ -44,7 +48,9 @@
|
||||
v-model="selectedCheckpointName"
|
||||
:disabled="!selectedScenery"
|
||||
>
|
||||
<option :value="null" disabled>Posterunek</option>
|
||||
<option :value="null" disabled>
|
||||
{{ $t('order-train-picker.placeholder-checkpoint-name') }}
|
||||
</option>
|
||||
<option :value="cp" v-for="cp in checkpointNameList" :key="cp">
|
||||
{{ cp }}
|
||||
</option>
|
||||
@@ -57,19 +63,19 @@
|
||||
id="fill-checkpoint"
|
||||
v-model="fillCheckpointName"
|
||||
/>
|
||||
<span> Uzupełniaj skrót wybranego posterunku</span>
|
||||
<span> {{ $t('order-train-picker.autofill-checkpoint-id') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<b v-if="!selectedSceneryId" class="text--accent">
|
||||
Wybierz dyżurnego oraz scenerię, aby zobaczyć pociągi
|
||||
{{ $t('order-train-picker.info') }}
|
||||
</b>
|
||||
|
||||
<div v-else>
|
||||
<div style="margin-bottom: 0.5em">
|
||||
<h3 style="margin-bottom: 0.5em">Aktywne RJ i gracze na scenerii</h3>
|
||||
<b class="text--accent">Kliknij na gracza, aby wypełnić obecny rozkaz jego danymi</b>
|
||||
<h3 style="margin-bottom: 0.5em">{{ $t('order-train-picker.title') }}</h3>
|
||||
<b class="text--accent">{{ $t('order-train-picker.subtitle') }}</b>
|
||||
</div>
|
||||
|
||||
<ul class="train-list">
|
||||
@@ -84,18 +90,18 @@
|
||||
class="online-indicator"
|
||||
></span>
|
||||
|
||||
<b>
|
||||
<span>
|
||||
{{ train.driverName }} •
|
||||
<span v-if="train.timetable" style="color: gold">{{
|
||||
train.timetable.category
|
||||
}}</span>
|
||||
{{ train.trainNo }}
|
||||
</b>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
|
||||
<li class="no-trains" v-if="sceneryTrains?.length == 0 && selectedSceneryId">
|
||||
Brak graczy
|
||||
{{ $t('order-train-picker.no-trains') }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -293,7 +299,7 @@ export default defineComponent({
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
overflow: auto;
|
||||
padding: 0 0.5em;
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
.options {
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
<template>
|
||||
<div class="update-card" v-if="store.updateCardOpen" @toggle-card="toggleCard(false)">
|
||||
<div class="card-background"></div>
|
||||
<div class="card-content">
|
||||
<h1 style="margin-bottom: 0.5em">🚀 {{ $t('update.title') }}</h1>
|
||||
|
||||
<div class="changelog" v-if="htmlChangelog != ''" v-html="htmlChangelog"></div>
|
||||
<div class="no-features" v-else>{{ $t('update.no-data') }}</div>
|
||||
|
||||
<button class="g-button action btn-confirm" ref="confirmButtonEl" @click="toggleCard(false)">
|
||||
{{ $t('update.confirm') }}
|
||||
</button>
|
||||
|
||||
<p class="bottom-info">
|
||||
{{ $t('update.info-1') }}
|
||||
<br />
|
||||
<span v-html="$t('update.info-2')"></span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, defineComponent, ref, watch } from 'vue';
|
||||
import { Converter } from 'showdown';
|
||||
import { useStore } from '../store/store';
|
||||
|
||||
const converter = new Converter();
|
||||
const store = useStore();
|
||||
const confirmButtonEl = ref<HTMLButtonElement | null>(null);
|
||||
|
||||
watch(
|
||||
computed(() => store.updateCardOpen),
|
||||
(val) => {
|
||||
console.log(val, confirmButtonEl);
|
||||
|
||||
if (val) {
|
||||
confirmButtonEl.value?.focus();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const htmlChangelog = computed(() => {
|
||||
if (store.appUpdateData.changelog == '') return '';
|
||||
|
||||
return converter.makeHtml(store.appUpdateData.changelog);
|
||||
});
|
||||
|
||||
function toggleCard(value: boolean) {
|
||||
store.updateCardOpen = value;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
// Converter styles
|
||||
::v-deep(h1) {
|
||||
text-align: center;
|
||||
color: var(--clr-primary);
|
||||
}
|
||||
|
||||
::v-deep(h2) {
|
||||
padding: 0.25em 0;
|
||||
border-bottom: 1px solid #aaa;
|
||||
}
|
||||
|
||||
::v-deep(ul) {
|
||||
list-style: disc;
|
||||
padding: 1em;
|
||||
line-height: 1.5em;
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
.update-card {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 200;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.card-background {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
z-index: 250;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
background-color: rgba(0, 0, 0, 0.55);
|
||||
}
|
||||
|
||||
.card-content {
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr auto;
|
||||
gap: 0.5em;
|
||||
|
||||
margin: 1em;
|
||||
|
||||
max-height: 95vh;
|
||||
max-height: 95dvh;
|
||||
|
||||
background-color: #1a1a1a;
|
||||
box-shadow: 0 0 15px 10px #0e0e0e;
|
||||
border-radius: 1em;
|
||||
|
||||
overflow: auto;
|
||||
|
||||
padding: 1em;
|
||||
min-height: 700px;
|
||||
overflow: auto;
|
||||
max-width: 700px;
|
||||
|
||||
z-index: 300;
|
||||
}
|
||||
|
||||
.no-features {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.changelog {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
button.btn-confirm {
|
||||
padding: 0.5em 0.75em;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
p.bottom-info {
|
||||
text-align: center;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
import enLang from './locales/en.json';
|
||||
import plLang from './locales/pl.json';
|
||||
|
||||
import { createI18n } from 'vue-i18n';
|
||||
|
||||
const i18n = createI18n({
|
||||
locale: 'pl',
|
||||
legacy: false,
|
||||
warnHtmlMessage: false,
|
||||
fallbackLocale: 'pl',
|
||||
|
||||
messages: {
|
||||
en: enLang,
|
||||
pl: plLang
|
||||
},
|
||||
enableLegacy: false
|
||||
});
|
||||
|
||||
export default i18n;
|
||||
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"locale": {
|
||||
"pl": "POL",
|
||||
"en": "ENG"
|
||||
},
|
||||
"navbar": {
|
||||
"order-message": "ORDER MESSAGE",
|
||||
"order-list": "SAVED ORDERS",
|
||||
"order-train-picker": "TRAINS"
|
||||
},
|
||||
"update": {
|
||||
"update-available-text": "New GeneraTOR version is available!",
|
||||
"update-available-underline": "Click here to update!",
|
||||
"title": "GeneraTOR update!",
|
||||
"confirm": "ROGER THAT!",
|
||||
"no-data": "No changelog available!",
|
||||
"info-1": "This changelog will be available to see once again after clicking the version number in the footer",
|
||||
"info-2": "The full app changelog available on <a href='https://github.com/Spythere/genera-tor' target='_blank'>the project's GitHub</a>"
|
||||
},
|
||||
"order-message": {
|
||||
"title": "Message to display in the simulator's chatbox:",
|
||||
"info": "Copy or save the content of the generated train order using buttons below:",
|
||||
"button-save": "Save as new order",
|
||||
"button-copy": "Copy the order message",
|
||||
"button-update": "Update the order",
|
||||
"warning-outdated-clipboard": "Oops! Your browser may be a little bit depraceted since it's not supporting saving data to the clipboard! :/",
|
||||
"warning-fill-inputs": "Fill all the empty fields before copying the order!",
|
||||
"warning-add-rows": "Add at least one row before copying the order!",
|
||||
"warning-fill-footer": "Fill the following rows in the order's footer before copying it:",
|
||||
"warning-fill-top": "Fill the order number, train number and date before saving it!",
|
||||
"warning-order-identical": "Last saved order is identical as the current one!",
|
||||
"warning-no-order-selected": "Choose the already saved order first!",
|
||||
"error-update": "An error occurred while saving this order! :/",
|
||||
"success-update-html": "Updated this <b class=\"text--accent\">order's</b> message!",
|
||||
"success-save-html": "Saved <b class=\"text--accent\">order's</b> message in the browser memory!",
|
||||
"success-copy-html": "<b class=\"text--accent\">Success!</b> You may paste the order message in the simulator's chatbox now!"
|
||||
},
|
||||
"order-footer": {
|
||||
"field-stationName": "station",
|
||||
"field-checkpointName": "checkpoint",
|
||||
"field-hour": "hour",
|
||||
"field-minutes": "minute",
|
||||
"field-dispatcherName": "dispatcher",
|
||||
"field-secondaryDispatcherName": "ordering dispatcher",
|
||||
"field-dispatcherOrSecondaryName": "dispatcher (or ordering dispatcher)"
|
||||
},
|
||||
"order-options": {
|
||||
"dark-mode": "Order dark theme",
|
||||
"update-number-on-copy": "Update order number on copy",
|
||||
"update-number-on-save": "Update order number on save",
|
||||
"update-hours": "Update order hour on edit"
|
||||
},
|
||||
"order-list": {
|
||||
"title": "Saved train orders",
|
||||
"order-title": "Order \"{orderName}\" no. {orderNo} for train no. {trainNo}",
|
||||
"no-saved-orders": "No saved orders!",
|
||||
"order-added": "Added:",
|
||||
"order-updated": "Updated:",
|
||||
"button-order-select": "Select",
|
||||
"button-order-remove": "Remove"
|
||||
},
|
||||
"order-train-picker": {
|
||||
"placeholder-scenery-name": "Scenery name",
|
||||
"placeholder-region-name": "Region",
|
||||
"placeholder-checkpoint-name": "Checkpoint name",
|
||||
"autofill-checkpoint-id": "Autofill checkpoint's abbreviation",
|
||||
"info": "Select scenery name to display active trains",
|
||||
"title": "Active timetables and trains on the scenery",
|
||||
"subtitle": "Click on the user below to fill the current order with their information",
|
||||
"no-trains": "No trains to display"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"locale": {
|
||||
"pl": "POL",
|
||||
"en": "ENG"
|
||||
},
|
||||
"update": {
|
||||
"update-available-text": "Nowa wersja GeneraTORa dostępna!",
|
||||
"update-available-underline": "Kliknij, aby odświeżyć aplikację!",
|
||||
"title": "Aktualizacja GeneraTORa!",
|
||||
"no-data": "Brak dostępnego changelogu!",
|
||||
"confirm": "Przyjąłem!",
|
||||
"info-1": "Ten changelog będzie zawsze dostępny po kliknięciu numeru wersji w stopce strony",
|
||||
"info-2": "Pełny changelog dostępny na <a href='https://github.com/Spythere/genera-tor' target='_blank'>GitHubie projektu</a>"
|
||||
},
|
||||
"navbar": {
|
||||
"order-message": "TREŚĆ ROZKAZU",
|
||||
"order-list": "ZAPISANE ROZ.",
|
||||
"order-train-picker": "POCIĄGI"
|
||||
},
|
||||
"order-message": {
|
||||
"title": "Wiadomość do wyświetlenia na czacie symulatora:",
|
||||
"info": "Po wygenerowaniu rozkazu skopiuj jego treść lub zapisz w pamięci przeglądarki za pomocą przycisków poniżej:",
|
||||
"button-save": "Zapisz nowy rozkaz",
|
||||
"button-copy": "Kopiuj treść rozkazu",
|
||||
"button-update": "Zaktualizuj rozkaz",
|
||||
"warning-outdated-clipboard": "Ups! Twoja przeglądarka musi być dosyć przestarzała, ponieważ nie obsługuje zapisu do schowka! :/",
|
||||
"warning-fill-inputs": "Wypełnij puste rubryki rozkazu przed jego skopiowaniem!",
|
||||
"warning-add-rows": "Dodaj co najmniej jedną działkę rozkazu przed jego skopiowaniem!",
|
||||
"warning-fill-footer": "Uzupełnij następujące rubryki na dole rozkazu przed jego skopiowaniem:",
|
||||
"warning-fill-top": "Wypełnij numer rozkazu, numer pociągu i datę zanim dodasz rozkaz!",
|
||||
"warning-order-identical": "Ostatni zapisany rozkaz jest identyczny z obecnym!",
|
||||
"warning-no-order-selected": "Wybierz rozkaz, który chcesz zaktualizować!",
|
||||
"error-update": "Wystąpił błąd podczas aktualizowania tego rozkazu! :/",
|
||||
"success-update-html": "Zaktualizowano treść <b class=\"text--accent\">rozkazu</b>!",
|
||||
"success-save-html": "Zapisano treść <b class=\"text--accent\">rozkazu</b> w pamięci przeglądarki!",
|
||||
"success-copy-html": "<b class=\"text--accent\">Skopiowano!</b> Możesz teraz wkleić treść rozkazu na czacie symulatora!"
|
||||
},
|
||||
"order-footer": {
|
||||
"field-stationName": "stacja",
|
||||
"field-checkpointName": "posterunek",
|
||||
"field-hour": "godzina",
|
||||
"field-minutes": "minuta",
|
||||
"field-dispatcherName": "dyżurny ruchu",
|
||||
"field-secondaryDispatcherName": "z polecenia dyżurnego ruchu",
|
||||
"field-dispatcherOrSecondaryName": "dyżurny ruchu (lub z polecenia dyżurnego ruchu)"
|
||||
},
|
||||
"order-options": {
|
||||
"dark-mode": "Ciemny motyw bloczka rozkazu",
|
||||
"update-number-on-copy": "Aktualizuj numer rozkazu po skopiowaniu",
|
||||
"update-number-on-save": "Aktualizuj numer rozkazu po zapisaniu",
|
||||
"update-hours": "Aktualizuj godziny przy edycji"
|
||||
},
|
||||
"order-list": {
|
||||
"title": "Zapisane rozkazy pisemne",
|
||||
"no-saved-orders": "Brak zapisanych rozkazów!",
|
||||
"order-title": "Rozkaz \"{orderName}\" nr {orderNo} dla pociągu nr {trainNo}",
|
||||
"order-added": "Dodano:",
|
||||
"order-updated": "Zaktualizowano:",
|
||||
"button-order-select": "Wybierz",
|
||||
"button-order-remove": "Usuń"
|
||||
},
|
||||
"order-train-picker": {
|
||||
"placeholder-scenery-name": "Sceneria",
|
||||
"placeholder-region-name": "Region",
|
||||
"placeholder-checkpoint-name": "Posterunek",
|
||||
"autofill-checkpoint-id": "Uzupełniaj skrót wybranego posterunku",
|
||||
"info": "Wybierz dyżurnego oraz scenerię, aby zobaczyć pociągi",
|
||||
"title": "Aktywne RJ i gracze na scenerii",
|
||||
"subtitle": "Kliknij na gracza, aby wypełnić obecny rozkaz jego danymi",
|
||||
"no-trains": "Brak pociągów do wyświetlenia"
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -3,4 +3,6 @@ import App from './App.vue';
|
||||
import router from './router';
|
||||
import { createPinia } from 'pinia';
|
||||
|
||||
createApp(App).use(router).use(createPinia()).mount('#app');
|
||||
import i18n from './i18n';
|
||||
|
||||
createApp(App).use(router).use(i18n).use(createPinia()).mount('#app');
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
export default class StorageManager {
|
||||
static registerStorage(name: string) {
|
||||
window.localStorage.setItem(name, '1');
|
||||
}
|
||||
|
||||
static unregisterStorage(name: string) {
|
||||
window.localStorage.removeItem(name);
|
||||
}
|
||||
|
||||
static isRegistered(name: string) {
|
||||
return window.localStorage.getItem(name) ? true : false;
|
||||
}
|
||||
|
||||
static setBooleanValue(key: string, val: boolean) {
|
||||
window.localStorage.setItem(key, val.toString());
|
||||
}
|
||||
|
||||
static setNumericValue(key: string, val: number) {
|
||||
window.localStorage.setItem(key, val.toString());
|
||||
}
|
||||
|
||||
static setStringValue(key: string, val: string) {
|
||||
window.localStorage.setItem(key, val);
|
||||
}
|
||||
|
||||
static setValue(key: string, val: any) {
|
||||
if (typeof val == 'boolean') this.setBooleanValue(key, val);
|
||||
else if (typeof val == 'number') this.setNumericValue(key, val);
|
||||
else if (typeof val == 'string') this.setStringValue(key, val);
|
||||
else this.setStringValue(key, val);
|
||||
}
|
||||
|
||||
static removeValue(key: string) {
|
||||
window.localStorage.removeItem(key);
|
||||
}
|
||||
|
||||
static getValue(key: string) {
|
||||
return window.localStorage.getItem(key);
|
||||
}
|
||||
|
||||
static getBooleanValue(key: string): boolean {
|
||||
return window.localStorage.getItem(key) === 'true' ? true : false;
|
||||
}
|
||||
|
||||
static getStringValue(key: string): string {
|
||||
return window.localStorage.getItem(key) || '';
|
||||
}
|
||||
|
||||
static getNumericValue(key: string): number {
|
||||
const itemValue = window.localStorage.getItem(key);
|
||||
return itemValue ? parseInt(itemValue) : 0;
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ export default defineComponent({
|
||||
|
||||
const messageArray = [];
|
||||
|
||||
if (footer.stationName) messageArray.push(`stacja: ${footer.stationName}`);
|
||||
messageArray.push(`stacja: ${footer.stationName ?? ''}`);
|
||||
if (footer.checkpointName) messageArray.push(`posterunek: ${footer.checkpointName}`);
|
||||
if (footer.hour) messageArray.push(`godz. ${footer.hour}`);
|
||||
if (footer.minutes) messageArray.push(`min. ${footer.minutes}`);
|
||||
@@ -22,9 +22,9 @@ export default defineComponent({
|
||||
if (footer.secondaryDispatcherName)
|
||||
messageArray.push(`z polecenia dyżurnego ruchu ${footer.secondaryDispatcherName}`);
|
||||
|
||||
this.store.footerMessage = ` <b>|</b> ${messageArray.join(
|
||||
this.store.footerMessage = `\n--------\n${messageArray.join(
|
||||
', '
|
||||
)} <b>|</b> Rozkaz otrzymałem, maszynista: (potwierdzić otrzymanie rozkazu)`;
|
||||
)}\n--------\nRozkaz otrzymałem, maszynista: <i>(potwierdzić otrzymanie rozkazu)</i>`;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useStore } from '../store/store';
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
return {
|
||||
store: useStore()
|
||||
store: useStore(),
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
@@ -9,6 +9,15 @@ import {
|
||||
export const useStore = defineStore('store', {
|
||||
state: () => {
|
||||
return {
|
||||
currentAppLocale: 'pl',
|
||||
|
||||
appUpdateData: {
|
||||
version: '',
|
||||
changelog: '',
|
||||
releaseURL: ''
|
||||
},
|
||||
|
||||
updateCardOpen: false,
|
||||
helperModalOpen: false,
|
||||
orderDarkMode: false,
|
||||
|
||||
|
||||
@@ -107,6 +107,12 @@ button.g-button {
|
||||
color: colors.$accentCol;
|
||||
}
|
||||
}
|
||||
|
||||
&.icon {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
// Text styles
|
||||
|
||||
+50
-11
@@ -10,17 +10,20 @@
|
||||
|
||||
<div class="message_container">
|
||||
<div class="message_nav">
|
||||
<span v-for="(action, i) in navActions" :key="action.mode">
|
||||
<b v-if="i > 0">•</b>
|
||||
<button class="g-button icon" @click="switchLanguages">
|
||||
<LanguagesIcon :size="18" />
|
||||
<span style="margin-left: 0.25em">{{ $t('locale.' + store.currentAppLocale) }}</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
v-for="(action, i) in navActions"
|
||||
:key="action.mode"
|
||||
class="g-button option"
|
||||
:data-active="store.orderMode == action.mode"
|
||||
@click="selectOrderMode(action.mode)"
|
||||
>
|
||||
{{ action.value }}
|
||||
{{ $t(`navbar.${action.value}`) }}
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<transition name="order-anim" mode="out-in">
|
||||
@@ -42,24 +45,26 @@ import OrderList from '../components/OrderList.vue';
|
||||
import { useStore } from '../store/store';
|
||||
import OrderHelper from '../components/OrderHelper.vue';
|
||||
import OrderTrainPicker from '../components/OrderTrainPicker.vue';
|
||||
import { LanguagesIcon } from 'lucide-vue-next';
|
||||
import StorageManager from '../managers/storageManager';
|
||||
|
||||
export default defineComponent({
|
||||
components: { OrderVue, SideBar, OrderHelper },
|
||||
components: { OrderVue, SideBar, OrderHelper, LanguagesIcon },
|
||||
|
||||
data() {
|
||||
return {
|
||||
navActions: [
|
||||
{
|
||||
mode: 'OrderMessage',
|
||||
value: 'TREŚĆ ROZKAZU'
|
||||
value: 'order-message'
|
||||
},
|
||||
{
|
||||
mode: 'OrderList',
|
||||
value: 'ZAPISANE ROZKAZY'
|
||||
value: 'order-list'
|
||||
},
|
||||
{
|
||||
mode: 'OrderTrainPicker',
|
||||
value: 'POCIĄGI'
|
||||
value: 'order-train-picker'
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -68,6 +73,15 @@ export default defineComponent({
|
||||
methods: {
|
||||
selectOrderMode(mode: string) {
|
||||
this.store.orderMode = mode;
|
||||
},
|
||||
|
||||
switchLanguages() {
|
||||
const lang = this.store.currentAppLocale == 'pl' ? 'en' : 'pl';
|
||||
|
||||
this.$i18n.locale = lang;
|
||||
this.store.currentAppLocale = lang;
|
||||
|
||||
StorageManager.setStringValue('lang', lang);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -95,6 +109,8 @@ export default defineComponent({
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@use '../styles/colors';
|
||||
|
||||
.home {
|
||||
min-height: 100vh;
|
||||
overflow-x: auto;
|
||||
@@ -104,6 +120,7 @@ export default defineComponent({
|
||||
align-items: center;
|
||||
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.home_container {
|
||||
display: flex;
|
||||
@@ -132,11 +149,13 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
.message_container {
|
||||
padding: 2px;
|
||||
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr;
|
||||
grid-template-rows: auto auto 1fr;
|
||||
|
||||
height: 95vh;
|
||||
overflow: auto;
|
||||
@@ -146,10 +165,30 @@ export default defineComponent({
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
gap: 0.25em;
|
||||
flex-wrap: wrap;
|
||||
|
||||
margin-bottom: 2em;
|
||||
margin-bottom: 1.5em;
|
||||
}
|
||||
|
||||
.message_nav > button {
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
content: '';
|
||||
bottom: -3px;
|
||||
left: 0;
|
||||
width: 0;
|
||||
height: 3px;
|
||||
|
||||
transition: all 0.25s;
|
||||
|
||||
background-color: colors.$accentCol;
|
||||
}
|
||||
|
||||
&[data-active='true']::before {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -963,26 +963,26 @@
|
||||
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3"
|
||||
integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==
|
||||
|
||||
"@intlify/core-base@9.8.0":
|
||||
version "9.8.0"
|
||||
resolved "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.8.0.tgz"
|
||||
integrity sha512-UxaSZVZ1DwqC/CltUZrWZNaWNhfmKtfyV4BJSt/Zt4Or/fZs1iFj0B+OekYk1+MRHfIOe3+x00uXGQI4PbO/9g==
|
||||
"@intlify/core-base@11.1.7":
|
||||
version "11.1.7"
|
||||
resolved "https://registry.yarnpkg.com/@intlify/core-base/-/core-base-11.1.7.tgz#497280e4774011cf0d42eaedb20e9cd4594c0a3f"
|
||||
integrity sha512-gYiGnQeJVp3kNBeXQ73m1uFOak0ry4av8pn+IkEWigyyPWEMGzB+xFeQdmGMFn49V+oox6294oGVff8bYOhtOw==
|
||||
dependencies:
|
||||
"@intlify/message-compiler" "9.8.0"
|
||||
"@intlify/shared" "9.8.0"
|
||||
"@intlify/message-compiler" "11.1.7"
|
||||
"@intlify/shared" "11.1.7"
|
||||
|
||||
"@intlify/message-compiler@9.8.0":
|
||||
version "9.8.0"
|
||||
resolved "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.8.0.tgz"
|
||||
integrity sha512-McnYWhcoYmDJvssVu6QGR0shqlkJuL1HHdi5lK7fNqvQqRYaQ4lSLjYmZxwc8tRNMdIe9/KUKfyPxU9M6yCtNQ==
|
||||
"@intlify/message-compiler@11.1.7":
|
||||
version "11.1.7"
|
||||
resolved "https://registry.yarnpkg.com/@intlify/message-compiler/-/message-compiler-11.1.7.tgz#047ba659cfd34b0f630dddf73c3f9224bd3af7f8"
|
||||
integrity sha512-0ezkep1AT30NyuKj8QbRlmvMORCCRlOIIu9v8RNU8SwDjjTiFCZzczCORMns2mCH4HZ1nXgrfkKzYUbfjNRmng==
|
||||
dependencies:
|
||||
"@intlify/shared" "9.8.0"
|
||||
"@intlify/shared" "11.1.7"
|
||||
source-map-js "^1.0.2"
|
||||
|
||||
"@intlify/shared@9.8.0":
|
||||
version "9.8.0"
|
||||
resolved "https://registry.npmjs.org/@intlify/shared/-/shared-9.8.0.tgz"
|
||||
integrity sha512-TmgR0RCLjzrSo+W3wT0ALf9851iFMlVI9EYNGeWvZFUQTAJx0bvfsMlPdgVtV1tDNRiAfhkFsMKu6jtUY1ZLKQ==
|
||||
"@intlify/shared@11.1.7":
|
||||
version "11.1.7"
|
||||
resolved "https://registry.yarnpkg.com/@intlify/shared/-/shared-11.1.7.tgz#54e60d52b73fb25019e2689d6531a54928b40194"
|
||||
integrity sha512-4yZeMt2Aa/7n5Ehy4KalUlvt3iRLcg1tq9IBVfOgkyWFArN4oygn6WxgGIFibP3svpaH8DarbNaottq+p0gUZQ==
|
||||
|
||||
"@jridgewell/gen-mapping@^0.3.5":
|
||||
version "0.3.8"
|
||||
@@ -1321,11 +1321,11 @@
|
||||
integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
|
||||
|
||||
"@types/node@^22.13.10":
|
||||
version "22.13.10"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.13.10.tgz#df9ea358c5ed991266becc3109dc2dc9125d77e4"
|
||||
integrity sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==
|
||||
version "22.15.34"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.15.34.tgz#3995a6461d2cfc51c81907da0065fc328f6a459e"
|
||||
integrity sha512-8Y6E5WUupYy1Dd0II32BsWAx5MWdcnRd8L84Oys3veg1YrYtNtzgO4CFhiBg6MDSjk7Ay36HYOnU7/tuOzIzcw==
|
||||
dependencies:
|
||||
undici-types "~6.20.0"
|
||||
undici-types "~6.21.0"
|
||||
|
||||
"@types/resolve@1.20.2":
|
||||
version "1.20.2"
|
||||
@@ -1337,6 +1337,11 @@
|
||||
resolved "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz"
|
||||
integrity sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==
|
||||
|
||||
"@types/showdown@^2.0.6":
|
||||
version "2.0.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/showdown/-/showdown-2.0.6.tgz#3d7affd5f971b4a17783ec2b23b4ad3b97477b7e"
|
||||
integrity sha512-pTvD/0CIeqe4x23+YJWlX2gArHa8G0J0Oh6GKaVXV7TAeickpkkZiNOgFcFcmLQ5lB/K0qBJL1FtRYltBfbGCQ==
|
||||
|
||||
"@types/trusted-types@^2.0.2":
|
||||
version "2.0.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.7.tgz#baccb07a970b91707df3a3e8ba6896c57ead2d11"
|
||||
@@ -1688,9 +1693,9 @@ available-typed-arrays@^1.0.7:
|
||||
possible-typed-array-names "^1.0.0"
|
||||
|
||||
axios@^1.6.2:
|
||||
version "1.8.4"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-1.8.4.tgz#78990bb4bc63d2cae072952d374835950a82f447"
|
||||
integrity sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==
|
||||
version "1.10.0"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-1.10.0.tgz#af320aee8632eaf2a400b6a1979fa75856f38d54"
|
||||
integrity sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==
|
||||
dependencies:
|
||||
follow-redirects "^1.15.6"
|
||||
form-data "^4.0.0"
|
||||
@@ -1861,6 +1866,11 @@ commander@^2.20.0:
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
|
||||
|
||||
commander@^9.0.0:
|
||||
version "9.5.0"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-9.5.0.tgz#bc08d1eb5cedf7ccb797a96199d41c7bc3e60d30"
|
||||
integrity sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==
|
||||
|
||||
common-tags@^1.8.0:
|
||||
version "1.8.2"
|
||||
resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.2.tgz#94ebb3c076d26032745fd54face7f688ef5ac9c6"
|
||||
@@ -2455,13 +2465,14 @@ for-each@^0.3.3, for-each@^0.3.5:
|
||||
is-callable "^1.2.7"
|
||||
|
||||
form-data@^4.0.0:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.2.tgz#35cabbdd30c3ce73deb2c42d3c8d3ed9ca51794c"
|
||||
integrity sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.3.tgz#608b1b3f3e28be0fccf5901fc85fb3641e5cf0ae"
|
||||
integrity sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==
|
||||
dependencies:
|
||||
asynckit "^0.4.0"
|
||||
combined-stream "^1.0.8"
|
||||
es-set-tostringtag "^2.1.0"
|
||||
hasown "^2.0.2"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
fs-extra@^9.0.1:
|
||||
@@ -2701,9 +2712,9 @@ ignore@^5.2.4:
|
||||
integrity sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==
|
||||
|
||||
immutable@^5.0.2:
|
||||
version "5.0.3"
|
||||
resolved "https://registry.yarnpkg.com/immutable/-/immutable-5.0.3.tgz#aa037e2313ea7b5d400cd9298fa14e404c933db1"
|
||||
integrity sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw==
|
||||
version "5.1.3"
|
||||
resolved "https://registry.yarnpkg.com/immutable/-/immutable-5.1.3.tgz#e6486694c8b76c37c063cca92399fa64098634d4"
|
||||
integrity sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==
|
||||
|
||||
import-fresh@^3.2.1:
|
||||
version "3.3.1"
|
||||
@@ -3111,6 +3122,11 @@ lru-cache@^5.1.1:
|
||||
dependencies:
|
||||
yallist "^3.0.2"
|
||||
|
||||
lucide-vue-next@^0.525.0:
|
||||
version "0.525.0"
|
||||
resolved "https://registry.yarnpkg.com/lucide-vue-next/-/lucide-vue-next-0.525.0.tgz#94bafb8dcb6b6344dbbd8a00d8230cf5478e444e"
|
||||
integrity sha512-Xf8+x8B2DrnGDV/rxylS+KBp2FIe6ljwDn2JsGTZZvXIfhmm/q+nv8RuGO1OyoMjOVkkz7CqtUqJfwtFPRbB2w==
|
||||
|
||||
magic-string@^0.25.0, magic-string@^0.25.7:
|
||||
version "0.25.9"
|
||||
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c"
|
||||
@@ -3430,9 +3446,9 @@ prettier-linter-helpers@^1.0.0:
|
||||
fast-diff "^1.1.2"
|
||||
|
||||
prettier@^3.0.3:
|
||||
version "3.5.3"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.5.3.tgz#4fc2ce0d657e7a02e602549f053b239cb7dfe1b5"
|
||||
integrity sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==
|
||||
version "3.6.2"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.6.2.tgz#ccda02a1003ebbb2bfda6f83a074978f608b9393"
|
||||
integrity sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==
|
||||
|
||||
pretty-bytes@^5.3.0:
|
||||
version "5.6.0"
|
||||
@@ -3659,9 +3675,9 @@ safe-regex-test@^1.1.0:
|
||||
is-regex "^1.2.1"
|
||||
|
||||
sass@^1.69.5:
|
||||
version "1.86.0"
|
||||
resolved "https://registry.yarnpkg.com/sass/-/sass-1.86.0.tgz#f49464fb6237a903a93f4e8760ef6e37a5030114"
|
||||
integrity sha512-zV8vGUld/+mP4KbMLJMX7TyGCuUp7hnkOScgCMsWuHtns8CWBoz+vmEhoGMXsaJrbUP8gj+F1dLvVe79sK8UdA==
|
||||
version "1.89.2"
|
||||
resolved "https://registry.yarnpkg.com/sass/-/sass-1.89.2.tgz#a771716aeae774e2b529f72c0ff2dfd46c9de10e"
|
||||
integrity sha512-xCmtksBKd/jdJ9Bt9p7nPKiuqrlBMBuuGkQlkhZjjQk3Ty48lv93k5Dq6OPkKt4XwxDJ7tvlfrTa1MPA9bf+QA==
|
||||
dependencies:
|
||||
chokidar "^4.0.0"
|
||||
immutable "^5.0.2"
|
||||
@@ -3729,6 +3745,13 @@ shebang-regex@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
|
||||
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
|
||||
|
||||
showdown@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/showdown/-/showdown-2.1.0.tgz#1251f5ed8f773f0c0c7bfc8e6fd23581f9e545c5"
|
||||
integrity sha512-/6NVYu4U819R2pUIk79n67SYgJHWCce0a5xTP979WbNp0FL9MN1I1QK662IDU1b6JzKTvmhgI7T7JYIxBi3kMQ==
|
||||
dependencies:
|
||||
commander "^9.0.0"
|
||||
|
||||
side-channel-list@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad"
|
||||
@@ -4057,10 +4080,10 @@ unbox-primitive@^1.1.0:
|
||||
has-symbols "^1.1.0"
|
||||
which-boxed-primitive "^1.1.1"
|
||||
|
||||
undici-types@~6.20.0:
|
||||
version "6.20.0"
|
||||
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433"
|
||||
integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==
|
||||
undici-types@~6.21.0:
|
||||
version "6.21.0"
|
||||
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb"
|
||||
integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==
|
||||
|
||||
unicode-canonical-property-names-ecmascript@^2.0.0:
|
||||
version "2.0.1"
|
||||
@@ -4172,13 +4195,13 @@ vue-eslint-parser@^9.3.1, vue-eslint-parser@^9.4.3:
|
||||
lodash "^4.17.21"
|
||||
semver "^7.3.6"
|
||||
|
||||
vue-i18n@9.8.0:
|
||||
version "9.8.0"
|
||||
resolved "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.8.0.tgz"
|
||||
integrity sha512-Izho+6PYjejsTq2mzjcRdBZ5VLRQoSuuexvR8029h5CpN03FYqiqBrShMyf2I1DKkN6kw/xmujcbvC+4QybpsQ==
|
||||
vue-i18n@11:
|
||||
version "11.1.7"
|
||||
resolved "https://registry.yarnpkg.com/vue-i18n/-/vue-i18n-11.1.7.tgz#a26c0224d1311ac89b82ff6d0ee45f68b5099237"
|
||||
integrity sha512-CDrU7Cmyh1AxJjerQmipV9nVa//exVBdhTcWGlbfcDCN8bKp/uAe7Le6IoN4//5emIikbsSKe9Uofmf/xXkhOA==
|
||||
dependencies:
|
||||
"@intlify/core-base" "9.8.0"
|
||||
"@intlify/shared" "9.8.0"
|
||||
"@intlify/core-base" "11.1.7"
|
||||
"@intlify/shared" "11.1.7"
|
||||
"@vue/devtools-api" "^6.5.0"
|
||||
|
||||
vue-router@^4.2.5:
|
||||
|
||||
Reference in New Issue
Block a user