feat: add place gate population scripts, map style assets, automated cron sync tasks, and expand dashboard and landing applications
This commit is contained in:
@@ -34,7 +34,7 @@ function App() {
|
||||
const [weatherAlerts, setWeatherAlerts] = useState<any[]>([]);
|
||||
|
||||
// Tourism & Heritage State
|
||||
const [tourismMode, setTourismMode] = useState(true);
|
||||
const [tourismMode, setTourismMode] = useState(false);
|
||||
const [heritageLandmarks, setHeritageLandmarks] = useState<HeritageLandmarkData[]>([]);
|
||||
const [selectedLandmark, setSelectedLandmark] = useState<HeritageLandmarkData | null>(null);
|
||||
const [showContributeModal, setShowContributeModal] = useState(false);
|
||||
@@ -67,8 +67,13 @@ function App() {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchHeritageLandmarks();
|
||||
}, []);
|
||||
if (tourismMode) {
|
||||
fetchHeritageLandmarks();
|
||||
} else {
|
||||
setHeritageLandmarks([]);
|
||||
setSelectedLandmark(null);
|
||||
}
|
||||
}, [tourismMode]);
|
||||
|
||||
const handleNavigateToGate = (lat: number, lng: number, name: string) => {
|
||||
setDestText(`${lat.toFixed(5)}, ${lng.toFixed(5)}`);
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,47 @@
|
||||
/** Shared behavior for the React view and the generated standalone document. */
|
||||
export function initializeLanding(root) {
|
||||
const controller = new AbortController();
|
||||
const { signal } = controller;
|
||||
const reduced = window.matchMedia('(prefers-reduced-motion: reduce)');
|
||||
root.addEventListener('click', async (event) => {
|
||||
const target = event.target.closest('a, button');
|
||||
if (!target || !root.contains(target)) return;
|
||||
if (target.dataset.scroll) {
|
||||
event.preventDefault();
|
||||
const section = root.querySelector(`#${target.dataset.scroll}`);
|
||||
section?.scrollIntoView({ behavior: reduced.matches ? 'instant' : 'smooth', block: 'start' });
|
||||
section?.setAttribute('tabindex', '-1');
|
||||
section?.focus({ preventScroll: true });
|
||||
}
|
||||
if (target.dataset.mode) {
|
||||
const mode = target.dataset.mode;
|
||||
root.querySelectorAll('[data-mode]').forEach(button => button.setAttribute('aria-pressed', String(button === target)));
|
||||
root.querySelector('[data-scene]').dataset.scene = mode;
|
||||
const modes = {
|
||||
routes: ['ROUTE LAYER', 'اربط نقطة الانطلاق بالوجهة داخل تجربة تطبيقك.', '/#map'],
|
||||
terrain: ['TERRAIN LAYER', 'أضف الارتفاعات وسياق التضاريس إلى المشهد المكاني.', '/#terrain3d'],
|
||||
data: ['DATA LAYER', 'ضع مواقعك وبياناتك فوق الخريطة لفهم توزيعها.', '/#map']
|
||||
};
|
||||
const [label, description, href] = modes[mode];
|
||||
root.querySelector('[data-mode-label]').textContent = label;
|
||||
root.querySelector('.sl-mode-description').textContent = description;
|
||||
root.querySelector('[data-mode-link]').href = href;
|
||||
}
|
||||
if (target.classList.contains('sl-motion')) {
|
||||
const paused = root.classList.toggle('sl-paused');
|
||||
target.setAttribute('aria-pressed', String(paused));
|
||||
target.setAttribute('aria-label', paused ? 'تشغيل الحركة' : 'إيقاف الحركة');
|
||||
target.textContent = paused ? '▷' : 'Ⅱ';
|
||||
}
|
||||
if (target.classList.contains('sl-copy')) {
|
||||
const status = root.querySelector('.sl-copy-status');
|
||||
try {
|
||||
await navigator.clipboard.writeText(root.querySelector('.sl-code code').textContent);
|
||||
if (!signal.aborted) status.textContent = 'تم نسخ الكود';
|
||||
} catch {
|
||||
if (!signal.aborted) status.textContent = 'تعذّر النسخ التلقائي. يمكنك تحديد الكود ونسخه يدويًا.';
|
||||
}
|
||||
}
|
||||
}, { signal });
|
||||
return () => controller.abort();
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -257,6 +257,9 @@ class ErrorBoundary extends React.Component<{ children: React.ReactNode }, { has
|
||||
|
||||
function Root() {
|
||||
const getInitialHash = () => {
|
||||
if (window.location.hash) {
|
||||
return window.location.hash;
|
||||
}
|
||||
const p = window.location.pathname.toLowerCase();
|
||||
if (p.includes('landing')) {
|
||||
return '#landing';
|
||||
@@ -267,7 +270,7 @@ function Root() {
|
||||
if (p.includes('tactical') || p.includes('military')) {
|
||||
return '#tactical';
|
||||
}
|
||||
return window.location.hash || '#map';
|
||||
return '#map';
|
||||
};
|
||||
|
||||
const [hash, setHash] = useState(getInitialHash);
|
||||
@@ -284,7 +287,7 @@ function Root() {
|
||||
return () => window.removeEventListener('hashchange', on);
|
||||
}, []);
|
||||
|
||||
const isLanding = hash === '#landing' || hash === '#portal' || window.location.pathname.toLowerCase().includes('landing');
|
||||
const isLanding = hash === '#landing' || hash === '#portal';
|
||||
const isTourism = hash === '#tourism' || hash === '#heritage' || hash === '#tourist';
|
||||
const isTactical = hash === '#tactical' || hash === '#military' || hash === '#siro';
|
||||
const isTerrain3D = hash === '#terrain3d' || hash === '#3d' || hash === '#terrain';
|
||||
@@ -295,16 +298,16 @@ function Root() {
|
||||
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<MasterHeader hash={hash} />
|
||||
{!isLanding && <MasterHeader hash={hash} />}
|
||||
|
||||
<main style={{
|
||||
position: 'absolute',
|
||||
top: 52,
|
||||
top: isLanding ? 0 : 52,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
width: '100vw',
|
||||
height: 'calc(100vh - 52px)',
|
||||
height: isLanding ? '100vh' : 'calc(100vh - 52px)',
|
||||
overflow: 'hidden'
|
||||
}}>
|
||||
{isLanding && (
|
||||
|
||||
+11
-1179
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user