added geolocation
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { API_URL, getImageUrl } from "../config/authConstants";
|
import { API_URL, getImageUrl } from "../config/authConstants";
|
||||||
|
import { useGeolocation, GEO_STATUS } from "../hooks/useGeolocation";
|
||||||
|
|
||||||
const MARKER_COLORS = {
|
const MARKER_COLORS = {
|
||||||
LOST: "#ff3b30",
|
LOST: "#ff3b30",
|
||||||
@@ -42,9 +43,12 @@ const COORD_MATCH_TOLERANCE = 0.005;
|
|||||||
export default function MapView({ focusPostId, focusLat, focusLng, onPostClick }) {
|
export default function MapView({ focusPostId, focusLat, focusLng, onPostClick }) {
|
||||||
const mapRef = useRef(null);
|
const mapRef = useRef(null);
|
||||||
const ymapRef = useRef(null);
|
const ymapRef = useRef(null);
|
||||||
|
const userMarkerRef = useRef(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
|
|
||||||
|
const { coords: geoCoords, status: geoStatus } = useGeolocation({ onMount: true });
|
||||||
|
|
||||||
const onPostClickRef = useRef(onPostClick);
|
const onPostClickRef = useRef(onPostClick);
|
||||||
onPostClickRef.current = onPostClick;
|
onPostClickRef.current = onPostClick;
|
||||||
|
|
||||||
@@ -157,9 +161,35 @@ export default function MapView({ focusPostId, focusLat, focusLng, onPostClick }
|
|||||||
destroyed.current = true;
|
destroyed.current = true;
|
||||||
ymapRef.current?.destroy();
|
ymapRef.current?.destroy();
|
||||||
ymapRef.current = null;
|
ymapRef.current = null;
|
||||||
|
userMarkerRef.current = null;
|
||||||
};
|
};
|
||||||
}, [focusPostId, focusLat, focusLng]);
|
}, [focusPostId, focusLat, focusLng]);
|
||||||
|
|
||||||
|
// Геолокация: если юзер разрешил и карта уже создана — центр на его месте.
|
||||||
|
// Не трогаем, если карта была открыта с фокусом на конкретном объявлении.
|
||||||
|
const hasFocus = focusPostId != null || (Number.isFinite(focusLat) && Number.isFinite(focusLng));
|
||||||
|
useEffect(() => {
|
||||||
|
if (hasFocus) return;
|
||||||
|
if (geoStatus !== GEO_STATUS.SUCCESS || !geoCoords) return;
|
||||||
|
const map = ymapRef.current;
|
||||||
|
if (!map) return;
|
||||||
|
|
||||||
|
const [lat, lng] = [geoCoords.latitude, geoCoords.longitude];
|
||||||
|
map.setCenter([lat, lng], 14);
|
||||||
|
|
||||||
|
if (userMarkerRef.current) {
|
||||||
|
userMarkerRef.current.geometry.setCoordinates([lat, lng]);
|
||||||
|
} else {
|
||||||
|
const pm = new window.ymaps.Placemark(
|
||||||
|
[lat, lng],
|
||||||
|
{},
|
||||||
|
{ preset: "islands#blueCircleIcon" }
|
||||||
|
);
|
||||||
|
map.geoObjects.add(pm);
|
||||||
|
userMarkerRef.current = pm;
|
||||||
|
}
|
||||||
|
}, [hasFocus, geoStatus, geoCoords, loading]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="f-mapview">
|
<div className="f-mapview">
|
||||||
{loading && (
|
{loading && (
|
||||||
@@ -173,6 +203,18 @@ export default function MapView({ focusPostId, focusLat, focusLng, onPostClick }
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div ref={mapRef} className="f-mapview-canvas" />
|
<div ref={mapRef} className="f-mapview-canvas" />
|
||||||
|
{geoStatus === GEO_STATUS.LOADING && !hasFocus && (
|
||||||
|
<div className="f-mapview-geo f-mapview-geo-info">Определение местоположения…</div>
|
||||||
|
)}
|
||||||
|
{geoStatus === GEO_STATUS.DENIED && (
|
||||||
|
<div className="f-mapview-geo f-mapview-geo-warn">Доступ к геолокации запрещён</div>
|
||||||
|
)}
|
||||||
|
{geoStatus === GEO_STATUS.TIMEOUT && (
|
||||||
|
<div className="f-mapview-geo f-mapview-geo-warn">Геолокация не определилась</div>
|
||||||
|
)}
|
||||||
|
{geoStatus === GEO_STATUS.UNSUPPORTED && (
|
||||||
|
<div className="f-mapview-geo f-mapview-geo-warn">Геолокация недоступна</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -2,12 +2,15 @@ import { useCallback, useEffect, useRef, useState } from "react";
|
|||||||
|
|
||||||
const YANDEX_GEOCODER_API_KEY = process.env.REACT_APP_YANDEX_GEOCODER_KEY;
|
const YANDEX_GEOCODER_API_KEY = process.env.REACT_APP_YANDEX_GEOCODER_KEY;
|
||||||
|
|
||||||
|
const DEFAULT_CENTER = [55.751574, 37.573856];
|
||||||
|
|
||||||
export function useYandexMap(enabled, initialCoords = null, initialAddress = "") {
|
export function useYandexMap(enabled, initialCoords = null, initialAddress = "") {
|
||||||
const mapRef = useRef(null);
|
const mapRef = useRef(null);
|
||||||
const ymapRef = useRef(null);
|
const ymapRef = useRef(null);
|
||||||
const placemarkRef = useRef(null);
|
const placemarkRef = useRef(null);
|
||||||
const initializedRef = useRef(false); // защита от двойного вызова StrictMode
|
const initializedRef = useRef(false); // защита от двойного вызова StrictMode
|
||||||
const requestIdRef = useRef(0); // защита от гонки при быстрых кликах/драгах
|
const requestIdRef = useRef(0); // защита от гонки при быстрых кликах/драгах
|
||||||
|
const pendingFocusRef = useRef(null); // фокус, который ждёт, пока карта инициализируется
|
||||||
|
|
||||||
const [coords, setCoords] = useState(initialCoords);
|
const [coords, setCoords] = useState(initialCoords);
|
||||||
const [address, setAddress] = useState(initialAddress ?? "");
|
const [address, setAddress] = useState(initialAddress ?? "");
|
||||||
@@ -48,18 +51,49 @@ export function useYandexMap(enabled, initialCoords = null, initialAddress = "")
|
|||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
const attachPlacemarkDrag = useCallback((map, pm) => {
|
||||||
if (!enabled || !mapRef.current) return;
|
pm.events.add("dragend", () => {
|
||||||
if (initializedRef.current) return; // уже инициализировали
|
const [lat, lng] = pm.geometry.getCoordinates();
|
||||||
initializedRef.current = true;
|
setCoords({ latitude: lat, longitude: lng });
|
||||||
|
resolveAddress(lat, lng);
|
||||||
|
});
|
||||||
|
}, [resolveAddress]);
|
||||||
|
|
||||||
const init = () => {
|
/**
|
||||||
|
* Устанавливает маркер и центр карты на указанные координаты.
|
||||||
|
* Если карта ещё не инициализирована — откладывает фокус до её создания.
|
||||||
|
*/
|
||||||
|
const moveTo = useCallback((lat, lng) => {
|
||||||
|
const map = ymapRef.current;
|
||||||
|
|
||||||
|
if (!map) {
|
||||||
|
pendingFocusRef.current = { lat, lng };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pendingFocusRef.current = null;
|
||||||
|
setCoords({ latitude: lat, longitude: lng });
|
||||||
|
|
||||||
|
if (placemarkRef.current) {
|
||||||
|
placemarkRef.current.geometry.setCoordinates([lat, lng]);
|
||||||
|
} else {
|
||||||
|
const pm = new window.ymaps.Placemark([lat, lng], {}, { draggable: true });
|
||||||
|
map.geoObjects.add(pm);
|
||||||
|
placemarkRef.current = pm;
|
||||||
|
attachPlacemarkDrag(map, pm);
|
||||||
|
}
|
||||||
|
|
||||||
|
map.setCenter([lat, lng], 14);
|
||||||
|
resolveAddress(lat, lng);
|
||||||
|
}, [attachPlacemarkDrag, resolveAddress]);
|
||||||
|
|
||||||
|
const init = useCallback(() => {
|
||||||
if (ymapRef.current) return;
|
if (ymapRef.current) return;
|
||||||
|
|
||||||
const startCoords = coordsRef.current;
|
const startCoords = coordsRef.current;
|
||||||
const center = startCoords
|
const center = startCoords
|
||||||
? [startCoords.latitude, startCoords.longitude]
|
? [startCoords.latitude, startCoords.longitude]
|
||||||
: [55.751574, 37.573856];
|
: DEFAULT_CENTER;
|
||||||
|
|
||||||
const map = new window.ymaps.Map(mapRef.current, {
|
const map = new window.ymaps.Map(mapRef.current, {
|
||||||
center,
|
center,
|
||||||
@@ -72,35 +106,28 @@ export function useYandexMap(enabled, initialCoords = null, initialAddress = "")
|
|||||||
const pm = new window.ymaps.Placemark(center, {}, { draggable: true });
|
const pm = new window.ymaps.Placemark(center, {}, { draggable: true });
|
||||||
map.geoObjects.add(pm);
|
map.geoObjects.add(pm);
|
||||||
placemarkRef.current = pm;
|
placemarkRef.current = pm;
|
||||||
|
attachPlacemarkDrag(map, pm);
|
||||||
resolveAddress(startCoords.latitude, startCoords.longitude);
|
resolveAddress(startCoords.latitude, startCoords.longitude);
|
||||||
|
|
||||||
pm.events.add("dragend", () => {
|
|
||||||
const [lat, lng] = pm.geometry.getCoordinates();
|
|
||||||
setCoords({ latitude: lat, longitude: lng });
|
|
||||||
resolveAddress(lat, lng);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
map.events.add("click", e => {
|
map.events.add("click", e => {
|
||||||
const [lat, lng] = e.get("coords");
|
const [lat, lng] = e.get("coords");
|
||||||
setCoords({ latitude: lat, longitude: lng });
|
moveTo(lat, lng);
|
||||||
resolveAddress(lat, lng);
|
|
||||||
|
|
||||||
if (placemarkRef.current) {
|
|
||||||
placemarkRef.current.geometry.setCoordinates([lat, lng]);
|
|
||||||
} else {
|
|
||||||
const pm = new window.ymaps.Placemark([lat, lng], {}, { draggable: true });
|
|
||||||
map.geoObjects.add(pm);
|
|
||||||
placemarkRef.current = pm;
|
|
||||||
|
|
||||||
pm.events.add("dragend", () => {
|
|
||||||
const [la, ln] = pm.geometry.getCoordinates();
|
|
||||||
setCoords({ latitude: la, longitude: ln });
|
|
||||||
resolveAddress(la, ln);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Если геолокация успела вернуть координаты раньше, чем создалась карта,
|
||||||
|
// применяем отложенный фокус сразу после инициализации.
|
||||||
|
if (pendingFocusRef.current) {
|
||||||
|
const pending = pendingFocusRef.current;
|
||||||
|
pendingFocusRef.current = null;
|
||||||
|
moveTo(pending.lat, pending.lng);
|
||||||
}
|
}
|
||||||
});
|
}, [attachPlacemarkDrag, resolveAddress, moveTo]);
|
||||||
};
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!enabled || !mapRef.current) return;
|
||||||
|
if (initializedRef.current) return; // уже инициализировали
|
||||||
|
initializedRef.current = true;
|
||||||
|
|
||||||
if (window.ymaps?.ready) {
|
if (window.ymaps?.ready) {
|
||||||
window.ymaps.ready(init);
|
window.ymaps.ready(init);
|
||||||
@@ -111,12 +138,13 @@ export function useYandexMap(enabled, initialCoords = null, initialAddress = "")
|
|||||||
ymapRef.current = null;
|
ymapRef.current = null;
|
||||||
placemarkRef.current = null;
|
placemarkRef.current = null;
|
||||||
initializedRef.current = false;
|
initializedRef.current = false;
|
||||||
|
pendingFocusRef.current = null;
|
||||||
};
|
};
|
||||||
// enabled намеренно в deps — карта инициализируется/разрушается при открытии
|
// enabled намеренно в deps — карта инициализируется/разрушается при открытии
|
||||||
// и закрытии модалки. Начальные координаты читаются из coordsRef, чтобы
|
// и закрытии модалки. Начальные координаты читаются из coordsRef, чтобы
|
||||||
// при повторном открытии маркер вставал на последний выбранный адрес.
|
// при повторном открытии маркер вставал на последний выбранный адрес.
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [enabled, resolveAddress]);
|
}, [enabled, init]);
|
||||||
|
|
||||||
return { mapRef, coords, address, addressRef, loading };
|
return { mapRef, coords, address, addressRef, loading, moveTo };
|
||||||
}
|
}
|
||||||
@@ -106,6 +106,11 @@
|
|||||||
"mapEmpty": "Location not selected yet",
|
"mapEmpty": "Location not selected yet",
|
||||||
"selectedAddress": "Selected address",
|
"selectedAddress": "Selected address",
|
||||||
"saveLocation": "Save location",
|
"saveLocation": "Save location",
|
||||||
|
"geoLoading": "Determining location…",
|
||||||
|
"geoSuccess": "Your location has been determined",
|
||||||
|
"geoDenied": "Location access denied",
|
||||||
|
"geoUnsupported": "Geolocation is not available in this browser",
|
||||||
|
"geoTimeout": "Unable to determine your location",
|
||||||
"addressLabel": "Address",
|
"addressLabel": "Address",
|
||||||
"addressPlaceholder": "E.g. 123 Main St, New York",
|
"addressPlaceholder": "E.g. 123 Main St, New York",
|
||||||
"phoneLabel": "Phone number",
|
"phoneLabel": "Phone number",
|
||||||
|
|||||||
@@ -106,6 +106,11 @@
|
|||||||
"mapEmpty": "Место ещё не выбрано",
|
"mapEmpty": "Место ещё не выбрано",
|
||||||
"selectedAddress": "Выбранный адрес",
|
"selectedAddress": "Выбранный адрес",
|
||||||
"saveLocation": "Сохранить местоположение",
|
"saveLocation": "Сохранить местоположение",
|
||||||
|
"geoLoading": "Определение местоположения…",
|
||||||
|
"geoSuccess": "Ваше местоположение определено",
|
||||||
|
"geoDenied": "Доступ к геолокации запрещён",
|
||||||
|
"geoUnsupported": "Геолокация недоступна в этом браузере",
|
||||||
|
"geoTimeout": "Не удалось определить местоположение",
|
||||||
"addressLabel": "Адрес",
|
"addressLabel": "Адрес",
|
||||||
"addressPlaceholder": "Например: Москва, ул. Ленина, 5",
|
"addressPlaceholder": "Например: Москва, ул. Ленина, 5",
|
||||||
"phoneLabel": "Номер телефона",
|
"phoneLabel": "Номер телефона",
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { useImageUpload, ACCEPT, MAX_SIZE_MB } from "../hooks/useImageUpload";
|
|||||||
import { validatePostForm } from "../utils/validatePostForm";
|
import { validatePostForm } from "../utils/validatePostForm";
|
||||||
import { formatPhone } from "../utils/formatPhone";
|
import { formatPhone } from "../utils/formatPhone";
|
||||||
import { useYandexMap } from "../hooks/useYandexMap";
|
import { useYandexMap } from "../hooks/useYandexMap";
|
||||||
|
import { useGeolocation, GEO_STATUS } from "../hooks/useGeolocation";
|
||||||
import MapModal from "./MapModal";
|
import MapModal from "./MapModal";
|
||||||
import ClearableField from "../components/ClearableField";
|
import ClearableField from "../components/ClearableField";
|
||||||
|
|
||||||
@@ -62,12 +63,29 @@ export default function CreateUpdatePost({ onClose, onSuccess, initialData }) {
|
|||||||
? { latitude: initialData.latitude, longitude: initialData.longitude }
|
? { latitude: initialData.latitude, longitude: initialData.longitude }
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const { mapRef, coords, address, addressRef, loading: mapLoading } = useYandexMap(
|
const { mapRef, coords, address, addressRef, loading: mapLoading, moveTo } = useYandexMap(
|
||||||
mapModalOpen,
|
mapModalOpen,
|
||||||
initialCoords,
|
initialCoords,
|
||||||
isEdit ? (initialData.location || "") : ""
|
isEdit ? (initialData.location || "") : ""
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { coords: geoCoords, status: geoStatus, requestLocation: requestGeoLocation } =
|
||||||
|
useGeolocation();
|
||||||
|
// При открытии окна карты запрашиваем геолокацию; если пользователь дал
|
||||||
|
// разрешение — автоматически центрируем карту и ставим маркер на его место.
|
||||||
|
// При отказе/недоступности API ничего не делаем — остаётся дефолтный центр.
|
||||||
|
useEffect(() => {
|
||||||
|
if (mapModalOpen) {
|
||||||
|
requestGeoLocation();
|
||||||
|
}
|
||||||
|
}, [mapModalOpen, requestGeoLocation]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (mapModalOpen && geoStatus === GEO_STATUS.SUCCESS && geoCoords) {
|
||||||
|
moveTo(geoCoords.latitude, geoCoords.longitude);
|
||||||
|
}
|
||||||
|
}, [mapModalOpen, geoStatus, geoCoords, moveTo]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (address) {
|
if (address) {
|
||||||
setForm(prev => ({ ...prev, address }));
|
setForm(prev => ({ ...prev, address }));
|
||||||
@@ -394,6 +412,7 @@ export default function CreateUpdatePost({ onClose, onSuccess, initialData }) {
|
|||||||
mapRef={mapRef}
|
mapRef={mapRef}
|
||||||
address={address}
|
address={address}
|
||||||
mapLoading={mapLoading}
|
mapLoading={mapLoading}
|
||||||
|
geoStatus={geoStatus}
|
||||||
onClose={() => setMapModalOpen(false)}
|
onClose={() => setMapModalOpen(false)}
|
||||||
onSave={() => setMapModalOpen(false)}
|
onSave={() => setMapModalOpen(false)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import CloseIcon from "../components/icons/CloseIcon";
|
import CloseIcon from "../components/icons/CloseIcon";
|
||||||
|
import { GEO_STATUS } from "../hooks/useGeolocation";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MapModal — модальное окно с картой для выбора местоположения.
|
* MapModal — модальное окно с картой для выбора местоположения.
|
||||||
@@ -8,10 +9,11 @@ import CloseIcon from "../components/icons/CloseIcon";
|
|||||||
* @param {object} mapRef — ref, в который монтируется Яндекс.Карта
|
* @param {object} mapRef — ref, в который монтируется Яндекс.Карта
|
||||||
* @param {string} address — текущий (выбранный) адрес
|
* @param {string} address — текущий (выбранный) адрес
|
||||||
* @param {boolean} mapLoading — идёт ли подгрузка адреса по координатам
|
* @param {boolean} mapLoading — идёт ли подгрузка адреса по координатам
|
||||||
|
* @param {string} geoStatus — статус геолокации (GEO_STATUS.*)
|
||||||
* @param {function} onClose — закрыть окно без сохранения
|
* @param {function} onClose — закрыть окно без сохранения
|
||||||
* @param {function} onSave — сохранить выбранное местоположение
|
* @param {function} onSave — сохранить выбранное местоположение
|
||||||
*/
|
*/
|
||||||
export default function MapModal({ mapRef, address, mapLoading, onClose, onSave }) {
|
export default function MapModal({ mapRef, address, mapLoading, geoStatus, onClose, onSave }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -20,6 +22,14 @@ export default function MapModal({ mapRef, address, mapLoading, onClose, onSave
|
|||||||
return () => { document.body.style.overflow = prev; };
|
return () => { document.body.style.overflow = prev; };
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const geoHint =
|
||||||
|
geoStatus === GEO_STATUS.SUCCESS ? t('post.geoSuccess')
|
||||||
|
: geoStatus === GEO_STATUS.LOADING ? t('post.geoLoading')
|
||||||
|
: geoStatus === GEO_STATUS.DENIED ? t('post.geoDenied')
|
||||||
|
: geoStatus === GEO_STATUS.UNSUPPORTED ? t('post.geoUnsupported')
|
||||||
|
: geoStatus === GEO_STATUS.TIMEOUT ? t('post.geoTimeout')
|
||||||
|
: null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="f-modal-bg" onClick={e => e.target === e.currentTarget && onClose()}>
|
<div className="f-modal-bg" onClick={e => e.target === e.currentTarget && onClose()}>
|
||||||
<div className="f-modal f-map-modal">
|
<div className="f-modal f-map-modal">
|
||||||
@@ -36,6 +46,10 @@ export default function MapModal({ mapRef, address, mapLoading, onClose, onSave
|
|||||||
<div ref={mapRef} className="f-map-inner" />
|
<div ref={mapRef} className="f-map-inner" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{geoHint && (
|
||||||
|
<div className="f-map-geo">{geoHint}</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Selected address */}
|
{/* Selected address */}
|
||||||
<div className="f-map-selected">
|
<div className="f-map-selected">
|
||||||
<span className="f-map-selected-label">{t('post.selectedAddress')}</span>
|
<span className="f-map-selected-label">{t('post.selectedAddress')}</span>
|
||||||
@@ -51,3 +65,4 @@ export default function MapModal({ mapRef, address, mapLoading, onClose, onSave
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -440,6 +440,22 @@
|
|||||||
font-size: .9rem; color: var(--err);
|
font-size: .9rem; color: var(--err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Geolocation hint on map view */
|
||||||
|
.f-mapview-geo {
|
||||||
|
position: absolute;
|
||||||
|
top: 12px; left: 50%; transform: translateX(-50%);
|
||||||
|
z-index: 6;
|
||||||
|
padding: 8px 14px; border-radius: 999px;
|
||||||
|
font-size: .8rem; font-weight: 600;
|
||||||
|
background: var(--bg-surface-solid);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
color: var(--text-primary);
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,.12);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.f-mapview-geo-info { color: var(--muted); }
|
||||||
|
.f-mapview-geo-warn { color: var(--err); border-color: rgba(237,73,86,.35); }
|
||||||
|
|
||||||
/* ── Feed Wrap + Scroll ── */
|
/* ── Feed Wrap + Scroll ── */
|
||||||
.f-wrap { display: flex; flex: 1; overflow: hidden; min-height: 0; }
|
.f-wrap { display: flex; flex: 1; overflow: hidden; min-height: 0; }
|
||||||
|
|
||||||
|
|||||||
@@ -463,6 +463,18 @@ select.f-m-field::selection { background: transparent; }
|
|||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Geolocation hint inside map modal */
|
||||||
|
.f-map-geo {
|
||||||
|
margin-top: 10px; padding: 8px 12px;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: .78rem; font-weight: 700;
|
||||||
|
background: rgba(var(--teal-rgb), .08);
|
||||||
|
border: 1px solid rgba(var(--teal-rgb), .25);
|
||||||
|
color: var(--teal);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.f-modal-sm {
|
.f-modal-sm {
|
||||||
width: 420px;
|
width: 420px;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user