feat: loading data from clipboard; error detection

This commit is contained in:
2025-03-07 14:04:58 +01:00
parent c9459eb8f4
commit ff6622bc29
12 changed files with 147 additions and 65 deletions
+3 -3
View File
@@ -3,7 +3,7 @@
<LogoSection />
<InputsSection />
<TrainImageSection />
<StockSection />
<TabsSection />
</main>
</template>
@@ -12,10 +12,10 @@ import { defineComponent } from 'vue';
import LogoSection from '../sections/LogoSection.vue';
import InputsSection from '../sections/InputsSection.vue';
import TrainImageSection from '../sections/TrainImageSection.vue';
import StockSection from '../sections/StockSection.vue';
import TabsSection from '../sections/TabsSection.vue';
export default defineComponent({
components: { LogoSection, InputsSection, TrainImageSection, StockSection },
components: { LogoSection, InputsSection, TrainImageSection, TabsSection },
});
</script>
@@ -1,9 +1,17 @@
<template>
<section class="stock-section">
<div class="section_modes">
<router-link v-for="(route, i) in routes" :key="route.name" class="link-btn" :to="route.href">
<section class="tabs-section">
<div class="tabs-modes">
<router-link
v-for="(route, i) in routes"
:key="route.name"
class="link-btn"
:to="route.href"
:style="{ 'grid-area': route.name }"
>
<span class="text--accent">{{ i + 1 }}.</span> {{ $t(`topbar.${route.name}`) }}
<span class="text--grayed" v-if="route.name == 'stock'">({{ store.stockList.length }})</span>
<span class="text--grayed" v-if="route.name == 'stock'"
>({{ store.stockList.length }})</span
>
</router-link>
</div>
@@ -33,6 +41,10 @@ const routes = [
name: 'wiki',
href: '/wiki',
},
{
name: 'storage',
href: '/storage',
},
{
name: 'numgen',
href: '/numgen',
@@ -47,7 +59,7 @@ onMounted(() => {
window.addEventListener('keydown', (e) => {
if (e.target instanceof HTMLInputElement) return;
if (/^[1234]$/.test(e.key)) {
if (/^[12345]$/.test(e.key)) {
const keyNum = Number(e.key);
router.push(routes[keyNum - 1].href);
@@ -57,8 +69,6 @@ onMounted(() => {
</script>
<style lang="scss">
// Tab change animation
.tab-change {
&-enter-from,
@@ -73,7 +83,7 @@ onMounted(() => {
}
// Section styles
.stock-section {
.tabs-section {
grid-row: 1 / 4;
grid-column: 2;
@@ -81,9 +91,13 @@ onMounted(() => {
padding: 1px;
}
.section_modes {
.tabs-modes {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-areas:
'stock stock wiki wiki storage storage'
'numgen numgen numgen stockgen stockgen stockgen';
grid-template-columns: repeat(6, 1fr);
// grid-template-rows: 1fr 1fr;
padding: 1px;
gap: 0.5em;
@@ -91,9 +105,15 @@ onMounted(() => {
margin-bottom: 1em;
}
@media screen and (max-width: 650px) {
.section_modes {
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
@media screen and (max-width: 400px) {
.tabs-modes {
grid-template-areas:
'stock wiki'
'storage storage'
'numgen stockgen';
grid-template-columns: repeat(2, 1fr);
// grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}
}
</style>
+1 -2
View File
@@ -224,8 +224,7 @@ const randomizeTrainNumber = (randomizeRegions = false) => {
</script>
<style lang="scss" scoped>
@use '../../styles/tab';
.category-select {
select {
@@ -1,9 +1,5 @@
<template>
<div class="stock-generator tab">
<div class="tab_header">
<h2>{{ $t('stockgen.title') }}</h2>
</div>
<div class="tab_content">
<div>
<h2>{{ $t('stockgen.properties-title') }}</h2>
+26 -9
View File
@@ -1,15 +1,16 @@
<template>
<section class="stock-list-tab">
<div class="tab_header">
<h2>{{ $t('stocklist.title') }}</h2>
</div>
<div class="tab_content">
<div class="stock_actions">
<button class="btn btn--image" @click="clickFileInput">
<input type="file" @change="uploadStock" ref="conFile" accept=".con,.txt" />
<input type="file" @change="uploadStockFromFile" ref="conFile" accept=".con,.txt" />
<img src="/images/icon-upload.svg" alt="upload icon" />
{{ $t('stocklist.action-upload') }}
{{ $t('stocklist.action-upload-file') }}
</button>
<button class="btn btn--image" @click="uploadStockFromClipboard">
<img src="/images/icon-upload.svg" alt="upload icon" />
{{ $t('stocklist.action-upload-clipboard') }}
</button>
<button
@@ -471,7 +472,7 @@ export default defineComponent({
a.dispatchEvent(e);
},
uploadStock() {
uploadStockFromFile() {
const inputEl = this.$refs['conFile'] as HTMLInputElement;
const files = inputEl.files;
@@ -494,6 +495,23 @@ export default defineComponent({
inputEl.value = '';
},
async uploadStockFromClipboard() {
try {
const content = await navigator.clipboard.readText();
this.loadStockFromString(content);
} catch (error) {
switch (error) {
case 'stock-loading-error':
alert(this.$t('stocklist.stock-loading-error'));
break;
default:
alert(this.$t('stocklist.stock-clipboard-error'));
break;
}
}
},
onDragStart(vehicleIndex: number) {
this.draggedVehicleID = vehicleIndex;
},
@@ -521,7 +539,6 @@ export default defineComponent({
</script>
<style lang="scss" scoped>
@use '@/styles/tab';
.tab_content {
@@ -571,7 +588,7 @@ export default defineComponent({
display: grid;
gap: 0.5em;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
button {
width: 100%;
+45
View File
@@ -0,0 +1,45 @@
<template>
<section class="tab storage-tab">
<div class="tab_header">
<h2>ZAPISANE SKŁADY</h2>
<h3>Zarządzaj składami zapisanymi w pamięci przeglądarki</h3>
</div>
<div class="tab_content">
<div class="storage_actions">
<!-- :data-disabled="stockIsEmpty"
:disabled="stockIsEmpty"
@click="downloadStock" -->
<button
class="btn btn--image"
>
<img src="/images/icon-download.svg" alt="download icon" />
POBIERZ DO PLIKU
</button>
<button
class="btn btn--image"
>
<img src="/images/icon-download.svg" alt="download icon" />
SKOPIUJ DO SCHOWKA
</button>
</div>
</div>
</section>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
name: 'storage-tab',
setup() {
return {};
},
});
</script>
<style lang="scss" scoped>
@use '@/styles/tab';
</style>
-4
View File
@@ -1,9 +1,5 @@
<template>
<section class="wiki-list tab">
<div class="tab_header">
<h2>{{ $t('wiki.title') }}</h2>
</div>
<div class="tab_content">
<div class="actions">
<div class="action action-input">
+8 -14
View File
@@ -57,9 +57,10 @@
"action-move-up": "MOVE UP",
"action-move-down": "MOVE DOWN",
"action-remove": "REMOVE",
"action-upload": "LOAD",
"action-download": "DOWNLOAD",
"action-copy": "COPY",
"action-upload-file": "LOAD FILE",
"action-upload-clipboard": "LOAD CLIPBOARD",
"action-download": "DOWNLOAD TO FILE",
"action-copy": "COPY TO CLIPBOARD",
"action-reset": "RESET",
"action-shuffle": "SHUFFLE",
"mass": "Mass",
@@ -69,14 +70,15 @@
"coldstart-info": "Locomotive cold start",
"doublemanning-info": "Double manning",
"list-empty": "Stock list is empty!",
"warning-not-suitable": "EP series locomotives are designed for passenger traffic only!",
"warning-passenger-too-long": "Maximum length of a passenger train may not be greater than 350m!",
"warning-freight-too-long": "Maximum length of a freight train may not be greater than 650m!",
"warning-too-many-locos": "This train has too many traction units!",
"warning-too-heavy": "This train is too heavy! Check {href}",
"warning-team-only-vehicle": "There's at least one vehicle available only for TD2 team members in your stock composition! ({0})",
"acceptable-mass-docs": "acceptable rolling stock masses (PL)"
"acceptable-mass-docs": "acceptable rolling stock masses (PL)",
"stock-loading-error": "An error occurred: cannot parse data from the file - make sure it's in a proper format!",
"stock-clipboard-error": "An error occurred: cannot read data from the clipboard - make sure the site has been granted right permissions for this action!"
},
"stockgen": {
"title": "FREIGHT TRAIN GENERATOR",
@@ -108,7 +110,6 @@
"action-random-region": "DRAW REGIONS",
"action-random-number": "DRAW LAST DIGITS",
"action-random-category": "DRAW A CATEGORY",
"rules": {
"first-digit": "First digit:",
"second-digit": "Second digit:",
@@ -123,25 +124,20 @@
"for-region-end": "for the terminating construction region",
"from-range": "from range of"
},
"categories": {
"EI": "EI - domestic express",
"EC": "EC - international express",
"EN": "EN - domestic night express",
"MP": "MP - intervoivodeship bullet",
"MO": "MO - intervoivodeship regio",
"MM": "MM - international bullet",
"MH": "MH - intervoivodeship night bullet",
"RP": "RP - voivodeship bullet",
"RM": "RM - international voivodeship regio",
"RO": "RO - voivodeship regio",
"RA": "RA - voivodeship regio (urban)",
"PW": "PW - empty passenger",
"PX": "PX - empty passenger test drive",
"TC": "TC - international freight (intermodal)",
"TG": "TG - international freight (organized cargo)",
"TR": "TR - international freight (unorganized cargo)",
@@ -151,12 +147,10 @@
"TK": "TK - freight (for stations & sidings)",
"TS": "TS - empty freight test drive",
"TH": "TH - locomotive rolling stock (over 3 vehicles)",
"LT": "LT - freight locomotive only",
"LP": "LP - passenger locomotive only",
"LS": "LS - shunting locomotive only",
"LZ": "LS - shunting locomotive only",
"ZN": "ZN - inspection / diagnostic type",
"ZU": "ZU - other maintenance type"
}
@@ -313,4 +307,4 @@
"418Vb_ZOS": "loose cargo (sand, stone)",
"418Vb_ZUE": "loose cargo (sand, stone)"
}
}
}
+10 -15
View File
@@ -44,7 +44,8 @@
"stock": "SKŁAD",
"wiki": "POJAZDY",
"numgen": "GNR NUMERU",
"stockgen": "GNR SKŁADU"
"stockgen": "GNR SKŁADU",
"storage": "ZAPISANE SKŁADY"
},
"stocklist": {
"title": "EDYTOR SKŁADU",
@@ -57,9 +58,10 @@
"action-move-up": "PRZENIEŚ WYŻEJ",
"action-move-down": "PRZENIEŚ NIŻEJ",
"action-remove": "USUŃ",
"action-upload": "WCZYTAJ",
"action-download": "POBIERZ",
"action-copy": "SKOPIUJ",
"action-upload-file": "WCZYTAJ (PLIK)",
"action-upload-clipboard": "WCZYTAJ (SCHOWEK)",
"action-download": "POBIERZ DO PLIKU",
"action-copy": "SKOPIUJ DO SCHOWKA",
"action-reset": "ZRESETUJ",
"action-shuffle": "PRZETASUJ",
"mass": "Masa",
@@ -69,14 +71,15 @@
"coldstart-info": "Zimny start",
"doublemanning-info": "Podwójna obsada",
"list-empty": "Lista pojazdów jest pusta!",
"warning-not-suitable": "Lokomotywy serii EP są przeznaczone jedynie do ruchu pasażerskiego!",
"warning-passenger-too-long": "Maksymalna długość składów pasażerskich nie może przekraczać 350m!",
"warning-freight-too-long": "Maksymalna długość składów innych niż pasażerskie nie może przekraczać 650m!",
"warning-too-many-locos": "Ten skład posiada za dużo pojazdów trakcyjnych!",
"warning-too-heavy": "Ten skład jest za ciężki! Sprawdź {href}",
"warning-team-only-vehicle": "W zestawieniu znajduje się co najmniej jeden pojazd dostępny tylko dla członków zespołu TD2! ({0})",
"acceptable-mass-docs": "dopuszczalne masy składów"
"acceptable-mass-docs": "dopuszczalne masy składów",
"stock-loading-error": "Wystąpił błąd: nie można przetworzyć danych ze schowka! Upewnij się, że są one w poprawnym formacie!",
"stock-clipboard-error": "Wystąpił błąd: nie można odczytać danych ze schowka! Upewnij się, że nadałeś uprawnienia stronie do tej akcji!"
},
"stockgen": {
"title": "GENERATOR SKŁADU TOWAROWEGO",
@@ -108,7 +111,6 @@
"action-random-region": "LOSUJ OBSZARY",
"action-random-number": "LOSUJ KOŃCÓWKĘ",
"action-random-category": "LOSUJ KATEGORIĘ",
"rules": {
"first-digit": "Pierwsza cyfra:",
"second-digit": "Druga cyfra:",
@@ -123,25 +125,20 @@
"for-region-end": "dla końcowego obszaru konstrukcyjnego",
"from-range": "z przedziału"
},
"categories": {
"EI": "EI - ekspres krajowy",
"EC": "EC - ekspres międzynarodowy",
"EN": "EN - ekspres krajowy nocny",
"MP": "MP - międzywoj. pośpieszny",
"RP": "RP - wojewódzki pośpieszny",
"MO": "MO - międzywoj. osobowy",
"RO": "RO - wojewódzki osobowy",
"MM": "MM - międzynar. pośpieszny",
"MH": "MH - międzywoj. pośpieszny hotelowy",
"RM": "RM - woj. osobowy międzynarodowy",
"RA": "RA - woj. osobowy aglomeracyjny",
"PW": "PW - pasażerski próżny - służbowy",
"PX": "PX - pasażerski próżny próbny",
"TC": "TC - towarowy międzynarodowy intermodalny",
"TG": "TG - towarowy międzynarodowy masowy",
"TR": "TR - towarowy międzynarodowy niemasowy",
@@ -151,12 +148,10 @@
"TK": "TK - towarowy (stacje i bocznice)",
"TS": "TS - towarowy próżny próbny",
"TH": "TH - skład lokomotyw (powyżej 3 pojazdów)",
"LT": "LT - lokomotywa towarowa luzem",
"LP": "LP - lokomotywa pasażerska luzem",
"LS": "LS - lokomotywa manewrowa luzem",
"LZ": "LZ - lokomotywa dla poc. utrzymaniowo-naprawczych",
"ZN": "ZN - inspekcyjny / diagnostyczny",
"ZU": "ZU - inny utrzymaniowy"
}
@@ -312,4 +307,4 @@
"418Vb_ZOS": "drobnica, ładunki sypkie (piasek, kamień)",
"418Vb_ZUE": "drobnica, ładunki sypkie (piasek, kamień)"
}
}
}
+11 -1
View File
@@ -53,6 +53,7 @@ export default defineComponent({
loadStockFromString(stockString: string) {
const stockArray = stockString.trim().split(';');
let failureCount = 0;
this.store.stockList.length = 0;
this.store.chosenVehicle = null;
@@ -85,10 +86,19 @@ export default defineComponent({
if (cargo) vehicleCargo = vehicle?.cargoTypes.find((c) => c.id == cargo) || null;
}
if (!vehicle) console.warn('Brak pojazdu / rodzaj pojazdu źle wczytany:', type);
if (!vehicle && type) {
failureCount++;
console.warn(`Wystąpił błąd - nie wczytano pojazdu o nazwie: ${type}`);
return;
}
this.addVehicle(vehicle, vehicleCargo);
});
if (failureCount != 0 && failureCount == stockArray.length) {
console.warn('Wystąpił błąd - niepoprawny format');
throw 'stock-loading-error';
}
},
},
});
+8
View File
@@ -4,6 +4,7 @@ import WikiListTab from './components/tabs/WikiListTab.vue';
import StockListTab from './components/tabs/StockListTab.vue';
import NumberGeneratorTab from './components/tabs/NumberGeneratorTab.vue';
import StockGeneratorTab from './components/tabs/StockGeneratorTab.vue';
import StorageTab from './components/tabs/StorageTab.vue';
const routes: RouteRecordRaw[] = [
{
@@ -41,6 +42,13 @@ const routes: RouteRecordRaw[] = [
viewMode: StockGeneratorTab,
},
},
{
path: 'storage',
component: AppContainerView,
meta: {
viewMode: StorageTab,
},
},
],
},
];
+2
View File
@@ -44,6 +44,8 @@ export const useStore = defineStore({
stockList: [] as IStock[],
cargoOptions: [] as any[][],
storageStockList: [] as IStock[][],
swapVehicles: false,
chosenStockListIndex: -1,