diff --git a/src/components/tabs/StockListTab.vue b/src/components/tabs/StockListTab.vue index 7f5cce4..e7da9dd 100644 --- a/src/components/tabs/StockListTab.vue +++ b/src/components/tabs/StockListTab.vue @@ -1,245 +1,20 @@ @@ -251,42 +26,22 @@ import { useStore } from '../../store'; import imageMixin from '../../mixins/imageMixin'; import stockPreviewMixin from '../../mixins/stockPreviewMixin'; -import StockThumbnails from '../utils/StockThumbnails.vue'; import stockMixin from '../../mixins/stockMixin'; -import Checkbox from '../common/Checkbox.vue'; -import { isTractionUnit } from '../../utils/vehicleUtils'; -import { - ArrowDownTrayIcon, - ArrowPathIcon, - ArrowUpTrayIcon, - ArrowUturnLeftIcon, - BookmarkIcon, - ChevronDownIcon, - ChevronUpIcon, - ClipboardDocumentIcon, - FolderPlusIcon, - ClipboardDocumentCheckIcon, - TrashIcon, - DocumentDuplicateIcon, -} from '@heroicons/vue/20/solid'; + +import StockActions from './stock-list/StockActions.vue'; +import StockSpecs from './stock-list/StockSpecs.vue'; +import StockSpawnSettings from './stock-list/StockSpawnSettings.vue'; +import StockWarnings from './stock-list/StockWarnings.vue'; +import StockList from './stock-list/StockList.vue'; export default defineComponent({ name: 'stock-list', components: { - StockThumbnails, - Checkbox, - FolderPlusIcon, - ArrowDownTrayIcon, - ArrowPathIcon, - ArrowUpTrayIcon, - ArrowUturnLeftIcon, - BookmarkIcon, - ChevronDownIcon, - ChevronUpIcon, - ClipboardDocumentIcon, - ClipboardDocumentCheckIcon, - DocumentDuplicateIcon, - TrashIcon, + StockActions, + StockSpecs, + StockSpawnSettings, + StockWarnings, + StockList, }, mixins: [imageMixin, stockMixin, stockPreviewMixin], @@ -299,291 +54,17 @@ export default defineComponent({ }; }, - data: () => ({ - imageOffsetY: 0, - draggedVehicleID: -1, + data: () => ({}), - stockActions: [{}], - }), + // computed: { + // chosenStockVehicle() { + // return this.store.chosenStockListIndex == -1 + // ? undefined + // : this.store.stockList[this.store.chosenStockListIndex]; + // }, + // }, - computed: { - chosenRealComposition() { - const currentStockString = this.store.stockList.map((s) => s.vehicleRef.type).join(';'); - - return this.store.realCompositionList.find((rc) => rc.stockString == currentStockString); - }, - - stockIsEmpty() { - return this.store.stockList.length == 0; - }, - - chosenStockVehicle() { - return this.store.chosenStockListIndex == -1 - ? undefined - : this.store.stockList[this.store.chosenStockListIndex]; - }, - - lengthExceeded() { - return ( - (this.store.totalLength > 350 && this.store.isTrainPassenger) || - (this.store.totalLength > 650 && !this.store.isTrainPassenger) - ); - }, - - weightExceeded() { - return this.store.acceptableWeight && this.store.totalWeight > this.store.acceptableWeight; - }, - - locoNotSuitable() { - return ( - !this.store.isTrainPassenger && - this.store.stockList.length > 1 && - !this.store.stockList.every((stock) => isTractionUnit(stock.vehicleRef)) && - this.store.stockList.some( - (stock) => isTractionUnit(stock.vehicleRef) && stock.vehicleRef.type.startsWith('EP') - ) - ); - }, - - // locoCountExceeded() { - // return ( - // this.store.stockList.reduce((acc, stock) => { - // if (isTractionUnit(stock.vehicleRef)) acc += 1; - // return acc; - // }, 0) > 2 - // ); - // }, - - teamOnlyVehicles() { - return this.store.stockList.filter((stock) => stock.vehicleRef.teamOnly); - }, - - hasAnyWarnings() { - return ( - this.weightExceeded || this.lengthExceeded || this.locoNotSuitable || this.teamOnlyVehicles - ); - }, - }, - - methods: { - isTractionUnit, - - copyToClipboard() { - navigator.clipboard.writeText(this.store.stockString); - - setTimeout(() => { - alert(this.$t('stocklist.alert-copied')); - }, 20); - }, - - clickFileInput() { - (this.$refs['conFile'] as HTMLInputElement).click(); - }, - - onListItemClick(stockID: number) { - const stock = this.store.stockList[stockID]; - - this.store.chosenStockListIndex = - this.store.chosenStockListIndex == stockID && - this.store.chosenVehicle?.type == stock.vehicleRef.type - ? -1 - : stockID; - - if (this.store.chosenStockListIndex == -1) { - this.store.chosenVehicle = null; - return; - } - - if (this.store.swapVehicles) this.store.swapVehicles = false; - - this.previewStock(stock); - }, - - getCarSpecFromType(typeStr: string) { - const specArray = typeStr.split('_'); - - if (specArray.length == 0) return null; - - /* 111a_Grafitti_1 */ - if (specArray.length == 3) return `${specArray[0]} ${specArray[1]}-${specArray[2]}`; - - /* 111a_PKP_Bnouz_01 */ - return `${specArray[0]} ${specArray[2]}-${specArray[3]} (${specArray[1]})`; - }, - - resetStock() { - this.store.stockList.length = 0; - this.store.chosenStockListIndex = -1; - }, - - removeStock(index: number) { - if (index == -1) return; - - this.store.stockList = this.store.stockList.filter((stock, i) => i != index); - - if (this.store.stockList.length < index + 1) this.store.chosenStockListIndex = -1; - }, - - moveUpStock(index: number) { - if (index < 1) return; - - const tempStock = this.store.stockList[index]; - - this.store.stockList[index] = this.store.stockList[index - 1]; - this.store.stockList[index - 1] = tempStock; - - this.store.chosenStockListIndex = index - 1; - }, - - moveDownStock(index: number) { - if (index == -1) return; - if (index > this.store.stockList.length - 2) return; - - const tempStock = this.store.stockList[index]; - - this.store.stockList[index] = this.store.stockList[index + 1]; - this.store.stockList[index + 1] = tempStock; - - this.store.chosenStockListIndex = index + 1; - }, - - shuffleCars() { - const availableIndexes = this.store.stockList.reduce((acc, stock, i) => { - if (!isTractionUnit(stock.vehicleRef)) acc.push(i); - - return acc; - }, [] as number[]); - - for (let i = 0; i < this.store.stockList.length; i++) { - if (!availableIndexes.includes(i)) continue; - - availableIndexes.splice(i, -1); - - const randAvailableIndex = - availableIndexes[Math.floor(Math.random() * availableIndexes.length)]; - const tempSwap = this.store.stockList[randAvailableIndex]; - - this.store.stockList[randAvailableIndex] = this.store.stockList[i]; - this.store.stockList[i] = tempSwap; - } - }, - - downloadStock() { - if (this.store.stockList.length == 0) return alert(this.$t('stocklist.alert-empty')); - - const defaultName = `${this.chosenRealComposition ? this.chosenRealComposition.stockId + ' ' : ''}${this.store.stockList[0].vehicleRef.type} ${(this.store.totalWeight / 1000).toFixed(1)}t; ${ - this.store.totalLength - }m; vmax ${this.store.maxStockSpeed}`; - - const fileName = prompt(this.$t('stocklist.prompt-file'), defaultName); - - if (!fileName) return; - - const blob = new Blob([this.store.stockString]); - const file = fileName + '.con'; - - var e = document.createEvent('MouseEvents'), - a = document.createElement('a'); - a.download = file; - a.href = window.URL.createObjectURL(blob); - a.dataset.downloadurl = ['', a.download, a.href].join(':'); - e.initEvent('click', true, false); - a.dispatchEvent(e); - }, - - uploadStockFromFile() { - const inputEl = this.$refs['conFile'] as HTMLInputElement; - const files = inputEl.files; - - if (files?.length != 1) return; - if (!/\.con$/.test(files[0].name)) return; - - const reader = new FileReader(); - reader.readAsText(files[0]); - - reader.onload = (res) => { - const stockString = res.target?.result; - - if (!stockString || typeof stockString !== 'string') return; - - this.loadStockFromString(stockString); - }; - - reader.onerror = (err) => console.error(err); - - inputEl.value = ''; - }, - - saveStockDataToStorage() { - if (this.store.stockList.length == 0) return; - - const defaultName = `${this.store.stockList[0].vehicleRef.type} ${(this.store.totalWeight / 1000).toFixed(1)}t; ${this.store.totalLength}m; vmax ${this.store.maxStockSpeed}`; - const entryName = prompt(this.$t('stocklist.prompt-bookmark'), defaultName); - - if (!entryName) return; - - if (entryName in this.store.storageStockData) { - const overwriteDataConfirm = confirm(this.$t('stocklist.prompt-bookmark-overwrite')); - - if (!overwriteDataConfirm) return; - - this.store.storageStockData[entryName].updatedAt = new Date(); - } - - this.store.storageStockData[entryName] = { - id: entryName, - createdAt: new Date(), - stockString: this.store.stockString, - }; - - try { - localStorage.setItem('savedStockData', JSON.stringify(this.store.storageStockData)); - this.store.chosenStorageStockName = entryName; - } catch (error) { - console.error('Wystąpił błąd podczas zapisywania składu do localStorage!', error); - } - }, - - 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; - }, - - onDrop(e: DragEvent, vehicleIndex: number) { - e.preventDefault(); - - let targetEl = (this.$refs['itemRefs'] as Element[])[vehicleIndex]; - - if (!targetEl) return; - - const tempVehicle = this.store.stockList[vehicleIndex]; - - this.store.stockList[vehicleIndex] = this.store.stockList[this.draggedVehicleID]; - this.store.stockList[this.draggedVehicleID] = tempVehicle; - - this.store.chosenStockListIndex = vehicleIndex; - }, - - allowDrop(e: DragEvent) { - e.preventDefault(); - }, - }, + methods: {}, }); @@ -595,178 +76,4 @@ export default defineComponent({ flex-direction: column; gap: 0.5em; } - -.warning { - padding: 0.25em; - margin: 0.25em 0; - background: global.$accentColor; - color: black; - - font-weight: bold; - - a { - color: black; - text-decoration: underline; - } -} - -.stock_controls { - display: flex; - justify-content: center; - align-items: center; - flex-wrap: wrap; - - gap: 0.5em; - - padding: 0.5em; - - background-color: #353a57; - - &[data-disabled='true'] { - opacity: 0.8; - - user-select: none; - -moz-user-select: none; - -webkit-user-select: none; - - pointer-events: none; - } -} - -.stock_actions { - display: grid; - gap: 0.5em; - - grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); - - button { - width: 100%; - - &[data-button-tooltip] { - font-size: 0.85em; - } - - input { - opacity: 0; - width: 0; - height: 0; - } - } -} - -.stock_spawn-settings { - display: flex; - gap: 0.5em; -} - -.real-stock-info { - img { - height: 1.3ch; - } -} - -.list-wrapper { - position: relative; -} - -.list-empty { - background-color: global.$secondaryColor; - border-radius: 0.5em; - padding: 0.75em; - font-weight: bold; -} - -ul { - overflow-y: scroll; - height: 500px; -} - -ul > li { - display: flex; - align-items: center; - justify-content: space-between; - min-width: 500px; - - margin: 0.25em 0; - - outline: none; - cursor: pointer; - - &:focus-visible { - outline: 1px solid white; - } -} - -li > .stock-info { - display: flex; - gap: 0.25em; - - color: white; - font-weight: 700; - - transition: color 100ms; - - & > span { - padding: 0.5em; - } -} - -.stock-info-no, -.stock-info-type { - background-color: global.$secondaryColor; - - &[data-team-only='true'] { - color: global.$teamColor; - } - - &[data-sponsor-only='true'] { - color: global.$sponsorColor; - } -} - -.stock-info-no { - min-width: 3.5em; - text-align: right; - - &[data-selected='true'] { - color: global.$accentColor; - } -} - -.stock-info-cargo { - background-color: #333; -} - -.stock-info-length, -.stock-info-mass, -.stock-info-speed { - background-color: #555; -} - -.stock-list-anim { - &-move, /* apply transition to moving elements */ - &-enter-active, - &-leave-active { - transition: all 250ms ease; - } - - &-enter-from { - opacity: 0; - transform: translateY(-25px); - } - - &-leave-to { - opacity: 0; - } - - &-leave-active { - position: absolute; - } -} - -@media screen and (max-width: global.$breakpointMd) { - ul { - min-height: auto; - } -} diff --git a/src/components/tabs/StorageTab.vue b/src/components/tabs/StorageTab.vue index 4048815..aa42553 100644 --- a/src/components/tabs/StorageTab.vue +++ b/src/components/tabs/StorageTab.vue @@ -8,38 +8,51 @@
-
  • +
  • - +

    + {{ storageEntry.id }} +

    - + + -
    -
    - {{ - storageEntry.stockString - .split(';') - .map((s) => s.split(/:|,/)[0]) - .join(' + ') - }} +
    + Stworzony: {{ new Date(storageEntry.createdAt).toLocaleString($i18n.locale) }} + + • Zaktualizowany: + {{ new Date(storageEntry.updatedAt).toLocaleString($i18n.locale) }} + +
    + Skład: + {{ + storageEntry.stockString + .split(';') + .map((s) => s.split(/:|,/)[0]) + .join(' + ') + }} +
  • -
  • +
  • {{ $t('storage.no-entires') }}
  • @@ -52,6 +65,7 @@ import { defineComponent } from 'vue'; import { + ArrowRightEndOnRectangleIcon, ChevronDownIcon, ChevronUpIcon, FolderArrowDownIcon, @@ -67,6 +81,7 @@ export default defineComponent({ ChevronUpIcon, FolderArrowDownIcon, TrashIcon, + ArrowRightEndOnRectangleIcon, }, mixins: [stockMixin], @@ -78,7 +93,12 @@ export default defineComponent({ computed: { storageStockDataList() { - // return Object.keys(this.store.storageStockData). + return Object.values(this.store.storageStockData) + .sort((a, b) => (b.updatedAt ?? b.createdAt) - (a.updatedAt ?? a.createdAt)) + .map((data) => ({ + ...data, + isExpanded: false, + })); }, }, @@ -152,15 +172,20 @@ ul.storage-list > li { align-items: center; } -.storage-item-top button.btn-name { +.storage-item-top > h3 { font-size: 1.2em; width: 100%; text-align: left; + margin: 0; } .storage-item-top-actions { display: flex; gap: 0.5em; + + & > button { + background-color: #333; + } } .storage-item-expandable { diff --git a/src/components/tabs/stock-list/StockActions.vue b/src/components/tabs/stock-list/StockActions.vue new file mode 100644 index 0000000..40f6d05 --- /dev/null +++ b/src/components/tabs/stock-list/StockActions.vue @@ -0,0 +1,345 @@ + + + + + diff --git a/src/components/tabs/stock-list/StockList.vue b/src/components/tabs/stock-list/StockList.vue new file mode 100644 index 0000000..6900cc4 --- /dev/null +++ b/src/components/tabs/stock-list/StockList.vue @@ -0,0 +1,261 @@ + + + + + diff --git a/src/components/tabs/stock-list/StockSpawnSettings.vue b/src/components/tabs/stock-list/StockSpawnSettings.vue new file mode 100644 index 0000000..5cc6dbc --- /dev/null +++ b/src/components/tabs/stock-list/StockSpawnSettings.vue @@ -0,0 +1,32 @@ + + + + + diff --git a/src/components/tabs/stock-list/StockSpecs.vue b/src/components/tabs/stock-list/StockSpecs.vue new file mode 100644 index 0000000..e1388a9 --- /dev/null +++ b/src/components/tabs/stock-list/StockSpecs.vue @@ -0,0 +1,55 @@ + + + + + diff --git a/src/components/utils/StockThumbnails.vue b/src/components/tabs/stock-list/StockThumbnails.vue similarity index 99% rename from src/components/utils/StockThumbnails.vue rename to src/components/tabs/stock-list/StockThumbnails.vue index 8fb8633..5b850ab 100644 --- a/src/components/utils/StockThumbnails.vue +++ b/src/components/tabs/stock-list/StockThumbnails.vue @@ -46,7 +46,7 @@ diff --git a/src/styles/_global.scss b/src/styles/_global.scss index 2fd9616..34bdfb3 100644 --- a/src/styles/_global.scss +++ b/src/styles/_global.scss @@ -230,6 +230,14 @@ button { outline: 1px solid white; } } + + &--icon { + display: flex; + justify-content: center; + align-items: center; + + padding: 0.25em; + } } .link-btn.router-link-exact-active { diff --git a/src/types/common.types.ts b/src/types/common.types.ts index e20bf29..21d5609 100644 --- a/src/types/common.types.ts +++ b/src/types/common.types.ts @@ -100,7 +100,7 @@ export interface IVehicleLocoProps { export interface StorageStockEntry { id: string; - createdAt: Date; - updatedAt?: Date; + createdAt: number; + updatedAt?: number; stockString: string; } \ No newline at end of file diff --git a/src/utils/fileUtils.ts b/src/utils/fileUtils.ts new file mode 100644 index 0000000..6e3910c --- /dev/null +++ b/src/utils/fileUtils.ts @@ -0,0 +1,27 @@ +import { useStore } from '../store'; + +export const useFileUtils = () => { + const store = useStore(); + + function getCurrentStockFileName() { + let fileName = ''; + + const currentStockString = store.stockList.map((s) => s.vehicleRef.type).join(';'); + + const currentRealComp = store.realCompositionList.find( + (rc) => rc.stockString == currentStockString + ); + + // Append real composition to the name if chosen + if (currentRealComp != undefined) { + fileName += currentRealComp.stockId; + } + + // Append default props + fileName += `${store.stockList[0].vehicleRef.type} ${(store.totalWeight / 1000).toFixed(1)}t; ${store.totalLength}m; vmax ${store.maxStockSpeed}`; + + return fileName; + } + + return { getCurrentStockFileName }; +}; diff --git a/src/utils/stockListUtils.ts b/src/utils/stockListUtils.ts new file mode 100644 index 0000000..687c093 --- /dev/null +++ b/src/utils/stockListUtils.ts @@ -0,0 +1,38 @@ +import { useStore } from '../store'; + +export const useStockListUtils = () => { + const store = useStore(); + + function removeStock(index: number) { + if (index == -1) return; + + store.stockList = store.stockList.filter((stock, i) => i != index); + + if (store.stockList.length < index + 1) store.chosenStockListIndex = -1; + } + + function moveUpStock(index: number) { + if (index < 1) return; + + const tempStock = store.stockList[index]; + + store.stockList[index] = store.stockList[index - 1]; + store.stockList[index - 1] = tempStock; + + store.chosenStockListIndex = index - 1; + } + + function moveDownStock(index: number) { + if (index == -1) return; + if (index > store.stockList.length - 2) return; + + const tempStock = store.stockList[index]; + + store.stockList[index] = store.stockList[index + 1]; + store.stockList[index + 1] = tempStock; + + store.chosenStockListIndex = index + 1; + } + + return { removeStock, moveDownStock, moveUpStock }; +};