From af631bc4e5e92bcd91b4e108b421a67eb7f86402 Mon Sep 17 00:00:00 2001 From: izawartka <59137928+izawartka@users.noreply.github.com> Date: Mon, 23 Jun 2025 11:19:28 +0200 Subject: [PATCH] Added sceneries list +Added built-in sceneries list (sceneries are not provided with the repo due to copyright reasons) *Renamed FileSelect to CustomFileSelect *Updated README.md --- .gitignore | 2 + README.md | 7 ++- .../{FileSelect.js => CustomFileSelect.js} | 8 +-- src/components/toolbar/SceneriesList.js | 49 +++++++++++++++++++ src/components/toolbar/Toolbar.js | 7 ++- src/helpers/constants.js | 5 ++ src/helpers/sceneryFilesHelper.js | 35 +++++++++++++ 7 files changed, 105 insertions(+), 8 deletions(-) rename src/components/toolbar/{FileSelect.js => CustomFileSelect.js} (78%) create mode 100644 src/components/toolbar/SceneriesList.js diff --git a/.gitignore b/.gitignore index 4d29575..34fe017 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,5 @@ npm-debug.log* yarn-debug.log* yarn-error.log* + +public/sceneries/* \ No newline at end of file diff --git a/README.md b/README.md index ae6cec4..e2a682f 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,11 @@ # td2-visualizer -Narzędzie do przeglądania plików scenerii symulatora Train Driver 2 (.SC) w przeglądarce. Umożliwia szybkie podejrzenie układu torowego dowolnej scenerii, jej szlaków zewnętrzych, semaforów, wskaźników, spawnów, numerów rozjazdów i wiele więcej +Narzędzie do przeglądania plików scenerii symulatora Train Driver 2 (.SC) w przeglądarce. Umożliwia szybkie podejrzenie układu torowego dowolnej scenerii, jej szlaków zewnętrzych, semaforów, wskaźników, spawnów, numerów rozjazdów i wiele więcej. Z powodów praw autorskich wersja github nie posiada dołączonych plików scenerii. -A tool for viewing Train Driver 2 simulator scenery files (.SC) in the browser. It allows for quick lookup of the track layout of any scenery, its external routes, signals, signs, spawns, switch numbers, and much more +A tool for viewing Train Driver 2 simulator scenery files (.SC) in the browser. It allows for quick lookup of the track layout of any scenery, its external routes, signals, signs, spawns, switch numbers, and much more. Due to copyright reasons, the github version does not include scenery files. + +Wątek na forum / Forum thread: +https://td2.info.pl/narzedzie-tools/nowy-td2-visualizer!/ # diff --git a/src/components/toolbar/FileSelect.js b/src/components/toolbar/CustomFileSelect.js similarity index 78% rename from src/components/toolbar/FileSelect.js rename to src/components/toolbar/CustomFileSelect.js index 399ecef..126b662 100644 --- a/src/components/toolbar/FileSelect.js +++ b/src/components/toolbar/CustomFileSelect.js @@ -3,7 +3,7 @@ import MainContext from "../../contexts/MainContext"; import { useZoomPanEmitter } from "../../hooks/useZoomPubSub"; import SceneryFilesHelper from "../../helpers/sceneryFilesHelper"; -export default function FileSelect() { +export default function CustomFileSelect() { const {setScenery, setIsLoading} = useContext(MainContext); const { center } = useZoomPanEmitter(); @@ -18,8 +18,8 @@ export default function FileSelect() { }; return ( -
- +
+
); -} \ No newline at end of file +} diff --git a/src/components/toolbar/SceneriesList.js b/src/components/toolbar/SceneriesList.js new file mode 100644 index 0000000..43480fe --- /dev/null +++ b/src/components/toolbar/SceneriesList.js @@ -0,0 +1,49 @@ +import { useContext, useState, useEffect } from 'react'; +import MainContext from '../../contexts/MainContext'; +import { useZoomPanEmitter } from '../../hooks/useZoomPubSub'; +import SceneryFilesHelper from '../../helpers/sceneryFilesHelper'; + +export default function SceneriesList(props) { + const {setScenery, setIsLoading} = useContext(MainContext); + const [ sceneryNames, setSceneryNames ] = useState(null); + const { center } = useZoomPanEmitter(); + + const handleSelectChange = async (event) => { + const name = event.target.value; + + setIsLoading(true); + const scenery = await SceneryFilesHelper.load(name, center); + setScenery(scenery); + setIsLoading(false); + }; + + const updateSceneryList = async () => { + const sceneries = await SceneryFilesHelper.getList(); + setSceneryNames(sceneries); + }; + + useEffect(() => { + updateSceneryList(); + }, []); + + return ( +
+ +
+ ); +} + +function SceneriesListOption({ name }) { + // remove .lite.sc or .sc + const regex = /\.(lite\.)?sc$/i; + const displayName = name.replace(regex, ''); + + return ( + + ); +} \ No newline at end of file diff --git a/src/components/toolbar/Toolbar.js b/src/components/toolbar/Toolbar.js index af0d722..d9b908c 100644 --- a/src/components/toolbar/Toolbar.js +++ b/src/components/toolbar/Toolbar.js @@ -1,14 +1,17 @@ import React from 'react'; import './Toolbar.css'; import LoadingIndicator from './LoadingIndicator'; -import FileSelect from './FileSelect'; +import CustomFileSelect from './CustomFileSelect'; import SideMenuToggle from './SideMenuToggle'; +import SceneriesList from './SceneriesList'; export default function Toolbar() { return (
- + + or +
diff --git a/src/helpers/constants.js b/src/helpers/constants.js index e631108..b91deb0 100644 --- a/src/helpers/constants.js +++ b/src/helpers/constants.js @@ -41,6 +41,11 @@ const Constants = { errors: { invalidSceneryInfo: true }, + sceneryFiles: { + fetchDisable: false, + fetchBaseUrl: `${process.env.PUBLIC_URL}/sceneries/`, + fetchListUrl: `${process.env.PUBLIC_URL}/sceneries/sceneries.json`, + }, layers: [ { id: 'tracks', diff --git a/src/helpers/sceneryFilesHelper.js b/src/helpers/sceneryFilesHelper.js index 418f60a..d1eb46e 100644 --- a/src/helpers/sceneryFilesHelper.js +++ b/src/helpers/sceneryFilesHelper.js @@ -1,9 +1,44 @@ +import Constants from "./constants"; import SceneryParser from "../model/scenery-parser"; import SceneryParserLog from "../model/scenery-parser-log"; import { showCustomDialog } from "../services/dialogService"; import SceneryLoadedDialog from "../components/scenery-loaded-dialog/SceneryLoadedDialog"; export default class SceneryFilesHelper { + static async fetch(url, toJson) { + try { + const response = await fetch(url); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return toJson ? await response.json() : await response.blob(); + } catch (error) { + console.error(`Failed to fetch from ${url}:`, error); + throw error; + } + } + + static async getList() { + try { + const url = Constants.sceneryFiles.fetchListUrl; + return await this.fetch(url, true); + } catch(error) { + console.error(`Failed to fetch sceneries list`); + return null; + } + } + + static async getScenery(name) { + const url = `${Constants.sceneryFiles.fetchBaseUrl}/${name}`; + return await this.fetch(url, false); + } + + static async load(name, centerFn = null) { + const file = await this.getScenery(name); + return await this.loadCustom(file, centerFn); + } + static async loadCustom(file, centerFn = null) { return new Promise((resolve, reject) => { SceneryParserLog.clear();