Refactored scenery list logic

*Moved scenery list logic from SceneriesList to SceneryManager
*Renamed SceneriesList to SceneryList
*Updated InfoFooter texts
-Removed sceneriesListService as it is not necessary anymore
This commit is contained in:
izawartka 2025-07-27 13:59:22 +02:00
parent e10b410f4f
commit 95ec3898ad
6 changed files with 55 additions and 78 deletions

View File

@ -1,4 +1,4 @@
import { useState, useCallback } from 'react'; import { useState, useCallback, useEffect } from 'react';
import SceneryContext from '../contexts/SceneryContext'; import SceneryContext from '../contexts/SceneryContext';
import { useZoomPanEmitter } from '../hooks/useZoomPubSub'; import { useZoomPanEmitter } from '../hooks/useZoomPubSub';
import SceneryParser from '../model/scenery-parser'; import SceneryParser from '../model/scenery-parser';
@ -11,6 +11,7 @@ import Constants from '../helpers/constants';
export default function SceneryManager(props) { export default function SceneryManager(props) {
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [scenery, setScenery] = useState(null); const [scenery, setScenery] = useState(null);
const [sceneryListData, setSceneryListData] = useState(null);
const { setCamera } = useZoomPanEmitter(); const { setCamera } = useZoomPanEmitter();
const myFetch = useCallback(async (url, toJson) => { const myFetch = useCallback(async (url, toJson) => {
@ -23,13 +24,14 @@ export default function SceneryManager(props) {
return toJson ? await response.json() : await response.blob(); return toJson ? await response.json() : await response.blob();
}, []); }, []);
const getSceneryList = useCallback(async () => { const updateSceneryList = useCallback(async () => {
try { try {
const url = Constants.sceneryFiles.fetchListUrl; const url = Constants.sceneryFiles.fetchListUrl;
return await myFetch(url, true); const listData = await myFetch(url, true);
setSceneryListData(listData || null);
} catch(error) { } catch(error) {
console.error(`Failed to fetch sceneries list`, error); console.error(`Failed to fetch scenery list`, error);
return null; setSceneryListData(null);
} }
}, [myFetch]); }, [myFetch]);
@ -84,8 +86,12 @@ export default function SceneryManager(props) {
return await loadCustomScenery(file); return await loadCustomScenery(file);
}, [myFetch, loadCustomScenery]); }, [myFetch, loadCustomScenery]);
useEffect(() => {
if(!Constants.sceneryFiles.fetchDisable) updateSceneryList();
}, [updateSceneryList]);
return ( return (
<SceneryContext.Provider value={{ isLoading, scenery, getSceneryList, loadScenery, loadCustomScenery }}> <SceneryContext.Provider value={{ isLoading, scenery, sceneryListData, loadScenery, loadCustomScenery }}>
{props.children} {props.children}
</SceneryContext.Provider> </SceneryContext.Provider>
); );

View File

@ -1,27 +1,20 @@
import { useEffect, useState } from 'react'; import { useContext } from 'react';
import { sceneriesListVersionDate$ } from "../../services/sceneriesListService";
import Constants from '../../helpers/constants'; import Constants from '../../helpers/constants';
import SceneryContext from '../../contexts/SceneryContext';
export default function InfoFooter() { export default function InfoFooter() {
const [sceneriesVersionDate, setSceneriesVersionDate] = useState(null); const { sceneryListData } = useContext(SceneryContext);
const sceneryListVersionDate = sceneryListData?.info?.versionDate || null;
useEffect(() => { const sceneryListText = sceneryListVersionDate ?
const subscription = sceneriesListVersionDate$.subscribe(date => { `included sceneries as of ${sceneryListVersionDate}` :
setSceneriesVersionDate(date); 'included sceneries not available';
});
return () => subscription.unsubscribe();
}, []);
const sceneriesListText = sceneriesVersionDate ?
`included sceneries as of ${sceneriesVersionDate}` :
'included sceneries list not available';
return ( return (
<div className="info-footer"> <div className="info-footer">
<span>TD2 Visualizer v{Constants.buildVersion}</span> <span>TD2 Visualizer v{Constants.buildVersion}</span>
<span>masuo 2025.06</span> <span>masuo 2025.06</span>
<span>{sceneriesListText}</span> <span>{sceneryListText}</span>
</div> </div>
); );
} }

View File

@ -1,47 +0,0 @@
import { useContext, useCallback, useState, useEffect } from 'react';
import SceneryContext from '../../contexts/SceneryContext';
import { setSceneriesListVersionDate } from '../../services/sceneriesListService';
import Constants from '../../helpers/constants';
export default function SceneriesList() {
const {getSceneryList, loadScenery} = useContext(SceneryContext);
const [ sceneriesListData, setSceneriesListData ] = useState(null);
const handleSelectChange = async (event) => {
const name = event.target.value;
if (!name) return;
await loadScenery(name);
};
const updateSceneryList = useCallback(async () => {
const sceneryListData = await getSceneryList();
setSceneriesListData(sceneryListData);
setSceneriesListVersionDate(sceneryListData?.info?.versionDate || null);
}, [getSceneryList]);
useEffect(() => {
if(Constants.sceneryFiles.fetchDisable) return;
updateSceneryList();
}, [updateSceneryList]);
return (
<div className='sceneries-list'>
<select onChange={handleSelectChange} defaultValue="">
<option value="" disabled>Select a scenery</option>
{Array.isArray(sceneriesListData?.list) && sceneriesListData.list.map((name) => (
<SceneriesListOption key={name} name={name} />
))}
</select>
</div>
);
}
function SceneriesListOption({ name }) {
// remove .lite.sc or .sc
const regex = /\.(lite\.)?sc$/i;
const displayName = name.replace(regex, '');
return (
<option value={name}>{displayName}</option>
);
}

View File

@ -0,0 +1,33 @@
import { useContext } from 'react';
import SceneryContext from '../../contexts/SceneryContext';
export default function SceneryList() {
const { sceneryListData, loadScenery} = useContext(SceneryContext);
const handleSelectChange = async (event) => {
const name = event.target.value;
if (!name) return;
await loadScenery(name);
};
return (
<div className='scenery-list'>
<select onChange={handleSelectChange} defaultValue="">
<option value="" disabled>Select a scenery</option>
{Array.isArray(sceneryListData?.list) && sceneryListData.list.map((name) => (
<SceneryListOption key={name} name={name} />
))}
</select>
</div>
);
}
function SceneryListOption({ name }) {
// remove .lite.sc or .sc
const regex = /\.(lite\.)?sc$/i;
const displayName = name.replace(regex, '');
return (
<option value={name}>{displayName}</option>
);
}

View File

@ -1,15 +1,14 @@
import React from 'react';
import './Toolbar.css'; import './Toolbar.css';
import LoadingIndicator from './LoadingIndicator'; import LoadingIndicator from './LoadingIndicator';
import CustomFileSelect from './CustomFileSelect'; import CustomFileSelect from './CustomFileSelect';
import SideMenuToggle from './SideMenuToggle'; import SideMenuToggle from './SideMenuToggle';
import SceneriesList from './SceneriesList'; import SceneryList from './SceneryList';
export default function Toolbar() { export default function Toolbar() {
return ( return (
<div className='toolbar-cont'> <div className='toolbar-cont'>
<div className='toolbar'> <div className='toolbar'>
<SceneriesList /> <SceneryList />
or or
<CustomFileSelect /> <CustomFileSelect />
<div className='toolbar-spacer'></div> <div className='toolbar-spacer'></div>

View File

@ -1,7 +0,0 @@
import { BehaviorSubject } from 'rxjs';
export const sceneriesListVersionDate$ = new BehaviorSubject(null);
export function setSceneriesListVersionDate(versionDate) {
sceneriesListVersionDate$.next(versionDate);
}