feat(map): زوماوت آزاد تا دید کشوری، قاب نخست روی همهٔ پروژهها با fitBounds، و تصاویر دیتابیس در پاپآپ نقشه
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { createPortal } from 'react-dom'
|
import { createPortal } from 'react-dom'
|
||||||
import { useMemo } from 'react'
|
import { useEffect, useMemo } from 'react'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import { MdArrowBack, MdOpenInNew } from 'react-icons/md'
|
import { MdArrowBack, MdOpenInNew } from 'react-icons/md'
|
||||||
import {
|
import {
|
||||||
@@ -32,8 +32,8 @@ export type ProjectsMapAllProps = {
|
|||||||
/**
|
/**
|
||||||
* نمای «همهی پروژهها» — همهی مختصات شیت روی یک نقشهی تمامعرض با
|
* نمای «همهی پروژهها» — همهی مختصات شیت روی یک نقشهی تمامعرض با
|
||||||
* سنجاقهای مسی، پاپآپ کارتی و دو اکشن عمودی: صفحهی پروژه و آدرس نشان.
|
* سنجاقهای مسی، پاپآپ کارتی و دو اکشن عمودی: صفحهی پروژه و آدرس نشان.
|
||||||
* پاپآپ فقط با کلیک کاربر باز میشود و جابهجایی نقشه به محدودهٔ پروژهها
|
* پاپآپ فقط با کلیک کاربر باز میشود؛ نمای نخست همهی سنجاقها را در قاب
|
||||||
* بسته است.
|
* دارد و زوماوت تا دید کشوری آزاد است.
|
||||||
*/
|
*/
|
||||||
export function ProjectsMapAll({
|
export function ProjectsMapAll({
|
||||||
className = '',
|
className = '',
|
||||||
@@ -43,9 +43,21 @@ export function ProjectsMapAll({
|
|||||||
}: ProjectsMapAllProps) {
|
}: ProjectsMapAllProps) {
|
||||||
const { t, href } = useLang()
|
const { t, href } = useLang()
|
||||||
const bounds = useMemo(() => boundsForProjects(projects), [projects])
|
const bounds = useMemo(() => boundsForProjects(projects), [projects])
|
||||||
const { containerRef, map, status, retry } = useMapLibre({ center: ISFAHAN_CENTER, zoom: 12, maxBounds: bounds })
|
// بدون maxBounds — زوماوت آزاد تا کف زوم ۵ (دید کشوری)؛ نمای نخست با fitBounds
|
||||||
|
// همهٔ سنجاقها را در یک قاب مینشیند.
|
||||||
|
const { containerRef, map, status, retry } = useMapLibre({
|
||||||
|
center: ISFAHAN_CENTER,
|
||||||
|
zoom: 12,
|
||||||
|
minZoom: 5,
|
||||||
|
})
|
||||||
const { popupHost, activeProject, closePopup } = useProjectMarkers(map, projects)
|
const { popupHost, activeProject, closePopup } = useProjectMarkers(map, projects)
|
||||||
|
|
||||||
|
// نمای نخست: قاب همهٔ پروژهها — بعد از آمادهشدن نقشه یکبار اجرا میشود
|
||||||
|
useEffect(() => {
|
||||||
|
if (!map || !bounds) return
|
||||||
|
map.fitBounds(bounds, { padding: 48, maxZoom: 14, duration: 0 })
|
||||||
|
}, [map, bounds])
|
||||||
|
|
||||||
const projectHref = href((activeProject && resolveProjectHref?.(activeProject)) || '/projects')
|
const projectHref = href((activeProject && resolveProjectHref?.(activeProject)) || '/projects')
|
||||||
const images = activeProject ? (imagesFor?.(activeProject) ?? []) : []
|
const images = activeProject ? (imagesFor?.(activeProject) ?? []) : []
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ type UseMapLibreOptions = {
|
|||||||
scrollZoom?: boolean
|
scrollZoom?: boolean
|
||||||
/** محدودهٔ مجاز جابهجایی نقشه — جلوگیری از رفتن به بیرون محدودهٔ پروژهها */
|
/** محدودهٔ مجاز جابهجایی نقشه — جلوگیری از رفتن به بیرون محدودهٔ پروژهها */
|
||||||
maxBounds?: maplibregl.LngLatBoundsLike
|
maxBounds?: maplibregl.LngLatBoundsLike
|
||||||
|
/** کف بزرگنمایی — زوماوت بیمعنا روی نقشهٔ خالی را میبندد */
|
||||||
|
minZoom?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
/** سقف انتظار سبک نقشه — سرور کاشی بیپاسخ یعنی خطای قابل تلاش دوباره، نه اسپینر بیپایان */
|
/** سقف انتظار سبک نقشه — سرور کاشی بیپاسخ یعنی خطای قابل تلاش دوباره، نه اسپینر بیپایان */
|
||||||
@@ -36,7 +38,7 @@ const ABSOLUTE_MAX_ZOOM = 22
|
|||||||
/** سقف جایگزین وقتی maxzoom منبع در دسترس نیست — برابر آرشیو فعلی (z14 + ۳) */
|
/** سقف جایگزین وقتی maxzoom منبع در دسترس نیست — برابر آرشیو فعلی (z14 + ۳) */
|
||||||
const FALLBACK_MAX_ZOOM = 17
|
const FALLBACK_MAX_ZOOM = 17
|
||||||
|
|
||||||
export function useMapLibre({ center, zoom, scrollZoom = true, maxBounds }: UseMapLibreOptions) {
|
export function useMapLibre({ center, zoom, scrollZoom = true, maxBounds, minZoom }: UseMapLibreOptions) {
|
||||||
const containerRef = useRef<HTMLDivElement | null>(null)
|
const containerRef = useRef<HTMLDivElement | null>(null)
|
||||||
const [map, setMap] = useState<maplibregl.Map | null>(null)
|
const [map, setMap] = useState<maplibregl.Map | null>(null)
|
||||||
const [status, setStatus] = useState<MapStatus>('loading')
|
const [status, setStatus] = useState<MapStatus>('loading')
|
||||||
@@ -68,6 +70,7 @@ export function useMapLibre({ center, zoom, scrollZoom = true, maxBounds }: UseM
|
|||||||
center,
|
center,
|
||||||
zoom,
|
zoom,
|
||||||
maxBounds,
|
maxBounds,
|
||||||
|
minZoom,
|
||||||
// سقف اولیه تا رسیدن maxzoom واقعی منبع؛ سپس applyZoomLimit دقیق میکند
|
// سقف اولیه تا رسیدن maxzoom واقعی منبع؛ سپس applyZoomLimit دقیق میکند
|
||||||
maxZoom: FALLBACK_MAX_ZOOM,
|
maxZoom: FALLBACK_MAX_ZOOM,
|
||||||
renderWorldCopies: false,
|
renderWorldCopies: false,
|
||||||
|
|||||||
@@ -81,6 +81,17 @@ export function Projects() {
|
|||||||
return dbMatch ? `/projects/${dbMatch.slug}` : undefined;
|
return dbMatch ? `/projects/${dbMatch.slug}` : undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// تصاویر پاپآپ نقشه — تصویر شاخص + گالری پروژهٔ متناظر در دیتابیس؛
|
||||||
|
// پروژهٔ بدون تصویر یا بدون متناظر، جاینگهدار میگیرد.
|
||||||
|
const imagesFor = (mapProject: MapProject): string[] => {
|
||||||
|
const dbMatch = dbProjects.find(
|
||||||
|
(p) => p.title === mapProject.name || p.title_en === mapProject.name,
|
||||||
|
);
|
||||||
|
if (!dbMatch) return [];
|
||||||
|
const gallery = Array.isArray(dbMatch.gallery) ? dbMatch.gallery : [];
|
||||||
|
return [dbMatch.image, ...gallery].filter((src): src is string => !!src);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageHero pageKey="projects" />
|
<PageHero pageKey="projects" />
|
||||||
@@ -92,6 +103,7 @@ export function Projects() {
|
|||||||
<LazyProjectsMapAll
|
<LazyProjectsMapAll
|
||||||
projects={MAP_PROJECTS}
|
projects={MAP_PROJECTS}
|
||||||
resolveProjectHref={resolveProjectHref}
|
resolveProjectHref={resolveProjectHref}
|
||||||
|
imagesFor={imagesFor}
|
||||||
className="h-[350px] md:h-[450px] lg:h-[55vh] max-h-[600px] w-full"
|
className="h-[350px] md:h-[450px] lg:h-[55vh] max-h-[600px] w-full"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user