Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 43 additions & 21 deletions src/AppLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { api } from './services/api';
import { useShallow } from 'zustand/react/shallow';
import { Toaster, toast } from 'react-hot-toast';
import DistanceWorker from './workers/distance.worker.js?worker';
import { useMeta } from './contexts';

const CURRENT_VERSION = meta["currentVersion"];

Expand Down Expand Up @@ -64,6 +65,7 @@ export const AppLayout: React.FC = () => {
const { loadUserData, saveData } = useUserData();
const [stationMenu, setStationMenu] = useState<any>(null);
const [isExportingKML, setIsExportingKML] = useState(false);
const { devMode } = useMeta() as any;
const isDraggingRef = useRef(false);
const workerRef = useRef<Worker | null>(null);
const distanceWorkerRef = useRef<Worker | null>(null);
Expand Down Expand Up @@ -376,22 +378,26 @@ export const AppLayout: React.FC = () => {
// Attempt to read fully precompiled geoData & railwayData structures directly (FAST PATH)
let precompiledGeoData: any = null;
let precompiledRailwayData: any = null;
try {
const txGeo = dbInstance.transaction(db.STORE_FILES, 'readonly');
const storeGeo = txGeo.objectStore(db.STORE_FILES);

const reqGeo = storeGeo.get('__precompiled_geodata');
precompiledGeoData = await new Promise((resolve) => {
reqGeo.onsuccess = () => resolve(reqGeo.result || null);
reqGeo.onerror = () => resolve(null);
});
if (!devMode) {
try {
const txGeo = dbInstance.transaction(db.STORE_FILES, 'readonly');
const storeGeo = txGeo.objectStore(db.STORE_FILES);

const reqRail = storeGeo.get('__precompiled_railwaydata');
precompiledRailwayData = await new Promise((resolve) => {
reqRail.onsuccess = () => resolve(reqRail.result || null);
reqRail.onerror = () => resolve(null);
});
} catch(e) {}
const reqGeo = storeGeo.get('__precompiled_geodata');
precompiledGeoData = await new Promise((resolve) => {
reqGeo.onsuccess = () => resolve(reqGeo.result || null);
reqGeo.onerror = () => resolve(null);
});

const reqRail = storeGeo.get('__precompiled_railwaydata');
precompiledRailwayData = await new Promise((resolve) => {
reqRail.onsuccess = () => resolve(reqRail.result || null);
reqRail.onerror = () => resolve(null);
});
} catch(e) {}
} else {
console.log('[Autoload] Dev mode enabled: skipping __precompiled_geodata cache.');
}

// Exclude caches from cachedFiles list used for manifest comparison
realFiles = cachedFiles.filter(f => f.fileName && !f.fileName.startsWith('__precompiled_') && !f.fileName.startsWith('zustand_'));
Expand Down Expand Up @@ -501,15 +507,31 @@ export const AppLayout: React.FC = () => {
const manifestRes = await fetch(`/geojson_manifest.json?v=${Date.now()}`).catch(() => null);
if (manifestRes && manifestRes.ok) {
const manifest = await manifestRes.json();
const geojsonFiles = manifest.files || [];

const cachedFileNames = new Set(realFiles.map(f => f.fileName));
const missingFiles = geojsonFiles.filter((f: string) => !cachedFileNames.has(f.replace(/\.(geojson|json)$/i, '')));
let missingFiles: { fileName: string; hash?: string }[] = [];

if (Array.isArray(manifest.files)) {
// Legacy array format: fallback to checking existence only
const cachedFileNames = new Set(realFiles.map(f => f.fileName));
missingFiles = manifest.files
.filter((f: string) => !cachedFileNames.has(f.replace(/\.(geojson|json)$/i, '')))
.map((f: string) => ({ fileName: f }));
} else if (manifest.files && typeof manifest.files === 'object') {
// New hash-based format: { "JR-East.geojson": "hash123", ... }
const cachedFilesMap = new Map(realFiles.map(f => [f.fileName, f.hash]));
missingFiles = Object.entries(manifest.files)
.filter(([fileName, hash]) => {
const localFileName = fileName.replace(/\.(geojson|json)$/i, '');
const localHash = cachedFilesMap.get(localFileName);
return localHash !== hash; // Also covers missing files (localHash is undefined)
})
.map(([fileName, hash]) => ({ fileName, hash: hash as string }));
}

if (missingFiles.length > 0) {
let downloadedCount = 0;
const totalToDownload = missingFiles.length;
const downloadTasks = missingFiles.map(async (fileName: string) => {
const downloadTasks = missingFiles.map(async (fileInfo: { fileName: string, hash?: string }) => {
const { fileName, hash } = fileInfo;
try {
const res = await fetch(`/geojson/${fileName.includes('.geojson') ? fileName : `${fileName}.geojson`}?v=${Date.now()}`);
downloadedCount++;
Expand All @@ -531,7 +553,7 @@ export const AppLayout: React.FC = () => {
const json = await res.json();
const rawCompanyName = fileName.replace(/\.(geojson|json)$/i, '');
const matchedCompany = findBestCompanyKey(rawCompanyName, companyIndex);
const dataItem = { json, company: matchedCompany, fileName: rawCompanyName };
const dataItem = { json, company: matchedCompany, fileName: rawCompanyName, hash };
db.set(db.STORE_FILES, rawCompanyName, dataItem).catch(e => console.warn('Cache write failed', e));
return dataItem;
} catch (e: any) { return null; }
Expand Down
16 changes: 15 additions & 1 deletion src/GlobalProvider.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,23 @@ export const GlobalProvider = ({ children }) => {
return () => clearInterval(timer);
}, []);

const [devMode, setDevMode] = useState(false);

useEffect(() => {
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.get('dev') === '1' || urlParams.get('dev') === 'true') {
setDevMode(true);
}
}, []);

const metaContextValue = {
...meta,
devMode
};

return (
<VersionContext value={versionInfo}>
<MetaContext value={meta}>
<MetaContext value={metaContextValue}>
{children}
</MetaContext>
</VersionContext>
Expand Down
8 changes: 5 additions & 3 deletions src/components/Tutorial.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useState, useEffect, useRef, useLayoutEffect, useCallback } from
import { createPortal } from 'react-dom';
import { X, ChevronRight, CheckCircle2, ArrowRight } from 'lucide-react';
import { DefaultCitySelector } from './modals/DefaultCitySelector';
import { useMeta } from '../contexts';

const STEPS = [
{
Expand Down Expand Up @@ -146,6 +147,7 @@ const Tutorial = ({
pinMode,
editorMode
}) => {
const { devMode } = useMeta();
const [step, setStep] = useState(-1); // -1: Loading/Check, 0+: Steps, -2: Skipped, -3: City Selector
const [rect, setRect] = useState(null);
const [isVisible, setIsVisible] = useState(false);
Expand Down Expand Up @@ -201,8 +203,8 @@ const Tutorial = ({
const skipped = localStorage.getItem('rail_tutorial_skipped');
const citySelectorDone = localStorage.getItem('rail_city_selector_done');

if (skipped === 'true' || user) {
if (!citySelectorDone) {
if (devMode || skipped === 'true' || user) {
if (!citySelectorDone && !devMode) {
setStep(-3); // Show City Selector if skipped/logged in but not done
} else {
setStep(-2); // Completely skipped
Expand All @@ -211,7 +213,7 @@ const Tutorial = ({
}
setStep(0);
setIsVisible(true);
}, [user]);
}, [user, devMode]);

const handleCitySelectorComplete = useCallback(() => {
localStorage.setItem('rail_city_selector_done', 'true');
Expand Down
1 change: 1 addition & 0 deletions src/contexts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const MetaContext = createContext({
thememode: 'light',
area: 'JP',
locale: 'zh-CN',
devMode: false,
});
export const useMeta = () => useContext(MetaContext);

Expand Down