refactor: centralize world and imagery requests in Axios services
This commit is contained in:
Generated
+4315
-4
File diff suppressed because it is too large
Load Diff
+20
-1
@@ -18,10 +18,29 @@
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@fontsource/vazirmatn": "^5.2.5",
|
||||
"@reduxjs/toolkit": "^2.13.0",
|
||||
"axios": "^1.21.0",
|
||||
"clsx": "^2.1.1",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-redux": "^9.3.1",
|
||||
"three": "^0.180.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.5",
|
||||
"@testing-library/jest-dom": "^7.0.1",
|
||||
"@testing-library/react": "^16.3.3",
|
||||
"@vitejs/plugin-react": "^4.7.0",
|
||||
"autoprefixer": "^10.6.2",
|
||||
"eslint": "^9.39.5",
|
||||
"eslint-plugin-react-hooks": "^7.1.1",
|
||||
"eslint-plugin-react-refresh": "^0.5.7",
|
||||
"globals": "^17.13.0",
|
||||
"jsdom": "^26.1.0",
|
||||
"postcss": "^8.5.32",
|
||||
"prettier": "^3.9.10",
|
||||
"vite": "^6.4.3"
|
||||
"tailwindcss": "^3.4.19",
|
||||
"vite": "^6.4.3",
|
||||
"vitest": "^3.2.7"
|
||||
}
|
||||
}
|
||||
|
||||
+14
-2
@@ -1,3 +1,4 @@
|
||||
import { loadTileUrl } from './services/worldService.js';
|
||||
import * as THREE from 'three';
|
||||
import { toLocal } from './geo.js';
|
||||
|
||||
@@ -41,13 +42,23 @@ export function createImagery(
|
||||
}));
|
||||
let loads = 0;
|
||||
|
||||
function show(level, tile) {
|
||||
async function show(level, tile) {
|
||||
if (tile.mesh || tile.loading || loads >= 6) return;
|
||||
tile.loading = true;
|
||||
loads++;
|
||||
let tileUrl;
|
||||
try {
|
||||
tileUrl = await loadTileUrl(`${baseUrl}${level.z}/${tile.x}/${tile.y}.jpg`);
|
||||
} catch {
|
||||
loads--;
|
||||
tile.loading = false;
|
||||
tile.failed = true;
|
||||
return;
|
||||
}
|
||||
loader.load(
|
||||
`${baseUrl}${level.z}/${tile.x}/${tile.y}.jpg`,
|
||||
tileUrl,
|
||||
(texture) => {
|
||||
URL.revokeObjectURL(tileUrl);
|
||||
loads--;
|
||||
tile.loading = false;
|
||||
texture.colorSpace = THREE.SRGBColorSpace;
|
||||
@@ -78,6 +89,7 @@ export function createImagery(
|
||||
},
|
||||
undefined,
|
||||
() => {
|
||||
URL.revokeObjectURL(tileUrl);
|
||||
loads--;
|
||||
tile.loading = false;
|
||||
tile.failed = true;
|
||||
|
||||
+2
-8
@@ -1,3 +1,4 @@
|
||||
import { loadWorldFiles } from './services/worldService.js';
|
||||
import * as THREE from 'three';
|
||||
import '@fontsource/vazirmatn/400.css';
|
||||
import '@fontsource/vazirmatn/600.css';
|
||||
@@ -131,14 +132,7 @@ scene.add(sun, sun.target);
|
||||
// ---------- World data ----------
|
||||
// Microsoft's building footprints and the satellite tile list are downloaded files, not bundled code.
|
||||
// The game still runs without them (OSM buildings and drawn roads).
|
||||
const loadFile = (path, read) =>
|
||||
fetch(path)
|
||||
.then((r) => (r.ok ? r[read]() : null))
|
||||
.catch(() => null);
|
||||
const [buildingsFile, imageryIndex] = await Promise.all([
|
||||
loadFile('./data/buildings.bin', 'arrayBuffer'),
|
||||
loadFile('./imagery/index.json', 'json'),
|
||||
]);
|
||||
const [buildingsFile, imageryIndex] = await loadWorldFiles();
|
||||
// Footprints reaching into the factory compound are left out: the game's own buildings stand there.
|
||||
const inFactory = (x, z) => {
|
||||
const dx = x - FACTORY.center.x,
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import axios from 'axios';
|
||||
|
||||
/** Shared HTTP client; data paths resolve below the deployment directory. */
|
||||
export const apiClient = axios.create({
|
||||
baseURL: typeof document === 'undefined' ? 'http://localhost/' : document.baseURI,
|
||||
timeout: 15000,
|
||||
});
|
||||
apiClient.interceptors.response.use(
|
||||
(response) => response,
|
||||
(error) =>
|
||||
Promise.reject(
|
||||
Object.assign(new Error(error.message || 'بارگذاری داده ناموفق بود'), {
|
||||
status: error.response?.status ?? null,
|
||||
code: error.code,
|
||||
}),
|
||||
),
|
||||
);
|
||||
@@ -0,0 +1,26 @@
|
||||
import { apiClient } from './apiClient.js';
|
||||
|
||||
const WORLD_FILES = { buildings: './data/buildings.bin', imagery: './imagery/index.json' };
|
||||
|
||||
/** Loads optional world assets without preventing the OSM fallback on network failure.
|
||||
* @param {AbortSignal} signal - Cancels startup when the game unmounts.
|
||||
* @returns {Promise<Array>} Binary buildings and imagery index, or null fallbacks.
|
||||
*/
|
||||
export async function loadWorldFiles(signal) {
|
||||
const requests = [
|
||||
apiClient.get(WORLD_FILES.buildings, { responseType: 'arraybuffer', signal }),
|
||||
apiClient.get(WORLD_FILES.imagery, { signal }),
|
||||
];
|
||||
const results = await Promise.allSettled(requests);
|
||||
return results.map((result) => (result.status === 'fulfilled' ? result.value.data : null));
|
||||
}
|
||||
|
||||
/** Loads a tile through the common client; the caller owns the returned object URL.
|
||||
* @param {string} path - Local imagery path.
|
||||
* @param {AbortSignal} signal - Request cancellation.
|
||||
* @returns {Promise<string>} Temporary URL, to revoke after image decoding.
|
||||
*/
|
||||
export async function loadTileUrl(path, signal) {
|
||||
const response = await apiClient.get(path, { responseType: 'blob', signal });
|
||||
return URL.createObjectURL(response.data);
|
||||
}
|
||||
Reference in New Issue
Block a user