feat: Update
This commit is contained in:
@@ -124,6 +124,7 @@ export const clearMapCaches = () => {
|
||||
interface ApiRoute {
|
||||
id: number;
|
||||
route_number: string;
|
||||
route_name: string;
|
||||
path: [number, number][];
|
||||
center_latitude: number;
|
||||
center_longitude: number;
|
||||
@@ -370,6 +371,7 @@ class MapStore {
|
||||
this.routes = routeResponses.map((res) => ({
|
||||
id: res.data.id,
|
||||
route_number: res.data.route_number,
|
||||
route_name: res.data.route_name || "",
|
||||
path: res.data.path,
|
||||
center_latitude: res.data.center_latitude,
|
||||
center_longitude: res.data.center_longitude,
|
||||
@@ -2302,6 +2304,8 @@ const MapSightbar: React.FC<MapSightbarProps> = observer(
|
||||
});
|
||||
feature.setId(`route-${route.id}`);
|
||||
feature.set("featureType", "route");
|
||||
feature.set("routeName", route.route_name);
|
||||
feature.set("routeNumber", route.route_number);
|
||||
return feature;
|
||||
});
|
||||
|
||||
@@ -2317,11 +2321,18 @@ const MapSightbar: React.FC<MapSightbarProps> = observer(
|
||||
|
||||
const filteredFeatures = useMemo(() => {
|
||||
if (!searchQuery.trim()) return allFeatures;
|
||||
return allFeatures.filter((f) =>
|
||||
((f.get("name") as string) || "")
|
||||
.toLowerCase()
|
||||
.includes(searchQuery.toLowerCase())
|
||||
const normalizedQuery = searchQuery.toLowerCase();
|
||||
return allFeatures.filter((f) => {
|
||||
const candidates = [
|
||||
(f.get("name") as string) || "",
|
||||
(f.get("description") as string) || "",
|
||||
(f.get("routeName") as string) || "",
|
||||
(f.get("routeNumber") as string) || "",
|
||||
];
|
||||
return candidates.some((value) =>
|
||||
value.toLowerCase().includes(normalizedQuery)
|
||||
);
|
||||
});
|
||||
}, [allFeatures, searchQuery]);
|
||||
|
||||
const handleFeatureClick = useCallback(
|
||||
@@ -2649,6 +2660,40 @@ const MapSightbar: React.FC<MapSightbarProps> = observer(
|
||||
featureType === "station" &&
|
||||
description &&
|
||||
description.trim() !== "";
|
||||
const routeName =
|
||||
featureType === "route"
|
||||
? ((feature.get("routeName") as string) || "")
|
||||
: "";
|
||||
const routeNumber =
|
||||
featureType === "route"
|
||||
? ((feature.get("routeNumber") as string) || fName)
|
||||
: "";
|
||||
const routeNumberTrimmed = routeNumber.trim();
|
||||
const routeNameTrimmed = routeName.trim();
|
||||
const displayName =
|
||||
featureType === "route"
|
||||
? routeNumberTrimmed || fName
|
||||
: fName;
|
||||
const showRouteName =
|
||||
featureType === "route" &&
|
||||
routeNameTrimmed !== "" &&
|
||||
routeNameTrimmed !== displayName;
|
||||
const titleParts: string[] = [];
|
||||
if (featureType === "route") {
|
||||
if (routeNumberTrimmed) {
|
||||
titleParts.push(routeNumberTrimmed);
|
||||
}
|
||||
if (routeNameTrimmed) {
|
||||
titleParts.push(routeNameTrimmed);
|
||||
}
|
||||
}
|
||||
const titleText =
|
||||
featureType === "route"
|
||||
? titleParts.join(" • ") ||
|
||||
routeNumberTrimmed ||
|
||||
routeNameTrimmed ||
|
||||
fName
|
||||
: fName;
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -2667,7 +2712,7 @@ const MapSightbar: React.FC<MapSightbarProps> = observer(
|
||||
checked={isChecked}
|
||||
onChange={() => handleCheckboxChange(fId)}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
aria-label={`Выбрать ${fName}`}
|
||||
aria-label={`Выбрать ${titleText}`}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
@@ -2692,9 +2737,9 @@ const MapSightbar: React.FC<MapSightbarProps> = observer(
|
||||
<span
|
||||
className={`font-medium whitespace-nowrap overflow-x-auto block
|
||||
scrollbar-visible`}
|
||||
title={fName}
|
||||
title={titleText}
|
||||
>
|
||||
{fName}
|
||||
{displayName}
|
||||
</span>
|
||||
</div>
|
||||
{showDescription && (
|
||||
@@ -2702,6 +2747,11 @@ const MapSightbar: React.FC<MapSightbarProps> = observer(
|
||||
{description}
|
||||
</div>
|
||||
)}
|
||||
{showRouteName && (
|
||||
<div className="mt-1 text-xs text-gray-600">
|
||||
{routeNameTrimmed}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-shrink-0 flex items-center space-x-1 opacity-60 group-hover:opacity-100">
|
||||
<button
|
||||
|
||||
@@ -2,7 +2,6 @@ import { useState, useEffect } from "react";
|
||||
import {
|
||||
Stack,
|
||||
Typography,
|
||||
Button,
|
||||
FormControl,
|
||||
Accordion,
|
||||
AccordionSummary,
|
||||
@@ -35,6 +34,7 @@ import {
|
||||
} from "@hello-pangea/dnd";
|
||||
|
||||
import {
|
||||
AnimatedCircleButton,
|
||||
authInstance,
|
||||
languageStore,
|
||||
routeStore,
|
||||
@@ -141,6 +141,9 @@ const LinkedItemsContentsInner = <
|
||||
const [selectedItems, setSelectedItems] = useState<Set<number>>(new Set());
|
||||
const [activeTab, setActiveTab] = useState(0);
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [isLinkingSingle, setIsLinkingSingle] = useState(false);
|
||||
const [isLinkingBulk, setIsLinkingBulk] = useState(false);
|
||||
const [detachingIds, setDetachingIds] = useState<Set<number>>(new Set());
|
||||
|
||||
useEffect(() => {}, [error]);
|
||||
|
||||
@@ -250,6 +253,7 @@ const LinkedItemsContentsInner = <
|
||||
),
|
||||
};
|
||||
|
||||
setIsLinkingSingle(true);
|
||||
authInstance
|
||||
.post(`/${parentResource}/${parentId}/${childResource}`, requestData)
|
||||
.then(() => {
|
||||
@@ -268,12 +272,20 @@ const LinkedItemsContentsInner = <
|
||||
.catch((error) => {
|
||||
console.error("Error linking item:", error);
|
||||
setError("Failed to link station");
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLinkingSingle(false);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const deleteItem = (itemId: number) => {
|
||||
setError(null);
|
||||
setDetachingIds((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.add(itemId);
|
||||
return next;
|
||||
});
|
||||
authInstance
|
||||
.delete(`/${parentResource}/${parentId}/${childResource}`, {
|
||||
data: { [`${childResource}_id`]: itemId },
|
||||
@@ -285,6 +297,13 @@ const LinkedItemsContentsInner = <
|
||||
.catch((error) => {
|
||||
console.error("Error deleting item:", error);
|
||||
setError("Failed to delete station");
|
||||
})
|
||||
.finally(() => {
|
||||
setDetachingIds((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.delete(itemId);
|
||||
return next;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -311,6 +330,7 @@ const LinkedItemsContentsInner = <
|
||||
if (selectedItems.size === 0) return;
|
||||
|
||||
setError(null);
|
||||
setIsLinkingBulk(true);
|
||||
const selectedStations = Array.from(selectedItems).map((id) => ({ id }));
|
||||
const requestData = {
|
||||
stations: [
|
||||
@@ -330,6 +350,9 @@ const LinkedItemsContentsInner = <
|
||||
.catch((error) => {
|
||||
console.error("Error linking items:", error);
|
||||
setError("Failed to link stations");
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLinkingBulk(false);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -399,7 +422,7 @@ const LinkedItemsContentsInner = <
|
||||
))}
|
||||
{type === "edit" && (
|
||||
<TableCell>
|
||||
<Button
|
||||
<AnimatedCircleButton
|
||||
variant="outlined"
|
||||
color="error"
|
||||
size="small"
|
||||
@@ -407,9 +430,11 @@ const LinkedItemsContentsInner = <
|
||||
e.stopPropagation();
|
||||
deleteItem(item.id);
|
||||
}}
|
||||
disabled={detachingIds.has(item.id)}
|
||||
loading={detachingIds.has(item.id)}
|
||||
>
|
||||
Отвязать
|
||||
</Button>
|
||||
</AnimatedCircleButton>
|
||||
</TableCell>
|
||||
)}
|
||||
</TableRow>
|
||||
@@ -520,14 +545,15 @@ const LinkedItemsContentsInner = <
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<Button
|
||||
<AnimatedCircleButton
|
||||
variant="contained"
|
||||
onClick={linkItem}
|
||||
disabled={!selectedItemId}
|
||||
disabled={!selectedItemId || isLinkingSingle}
|
||||
loading={isLinkingSingle}
|
||||
sx={{ alignSelf: "flex-start" }}
|
||||
>
|
||||
Добавить
|
||||
</Button>
|
||||
</AnimatedCircleButton>
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
@@ -587,14 +613,15 @@ const LinkedItemsContentsInner = <
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
<Button
|
||||
<AnimatedCircleButton
|
||||
variant="contained"
|
||||
onClick={handleBulkLink}
|
||||
disabled={selectedItems.size === 0}
|
||||
disabled={selectedItems.size === 0 || isLinkingBulk}
|
||||
loading={isLinkingBulk}
|
||||
sx={{ alignSelf: "flex-start" }}
|
||||
>
|
||||
Добавить выбранные ({selectedItems.size})
|
||||
</Button>
|
||||
</AnimatedCircleButton>
|
||||
</Stack>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
import { Stack, Typography, Button } from "@mui/material";
|
||||
import { Box, Stack, Typography, Button } from "@mui/material";
|
||||
import { useNavigate, useNavigationType } from "react-router";
|
||||
import { MediaViewer } from "@widgets";
|
||||
import { useMapData } from "./MapDataContext";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useEffect, useState } from "react";
|
||||
import { authInstance } from "@shared";
|
||||
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
|
||||
import LanguageSelector from "./web-gl/LanguageSelector";
|
||||
|
||||
export const LeftSidebar = observer(() => {
|
||||
type LeftSidebarProps = {
|
||||
open: boolean;
|
||||
onToggle: () => void;
|
||||
};
|
||||
|
||||
export const LeftSidebar = observer(({ open, onToggle }: LeftSidebarProps) => {
|
||||
const navigate = useNavigate();
|
||||
const navigationType = useNavigationType(); // PUSH, POP, REPLACE
|
||||
const { routeData } = useMapData();
|
||||
@@ -35,32 +42,57 @@ export const LeftSidebar = observer(() => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack direction="column" width="300px" p={2} bgcolor="primary.main">
|
||||
<button
|
||||
onClick={handleBack}
|
||||
type="button"
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
gap: 10,
|
||||
<Box
|
||||
sx={{
|
||||
position: "relative",
|
||||
height: "100%",
|
||||
color: "#fff",
|
||||
backgroundColor: "#222",
|
||||
borderRadius: 10,
|
||||
height: 40,
|
||||
width: "100%",
|
||||
border: "none",
|
||||
cursor: "pointer",
|
||||
transition: "padding 0.3s ease",
|
||||
p: open ? 2 : 0,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "stretch",
|
||||
justifyContent: "flex-start",
|
||||
}}
|
||||
>
|
||||
<p>Назад</p>
|
||||
</button>
|
||||
<Stack
|
||||
direction="column"
|
||||
height="100%"
|
||||
width="100%"
|
||||
spacing={4}
|
||||
alignItems="stretch"
|
||||
sx={{
|
||||
opacity: open ? 1 : 0,
|
||||
transition: "opacity 0.25s ease",
|
||||
pointerEvents: open ? "auto" : "none",
|
||||
display: open ? "flex" : "none",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
onClick={handleBack}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
sx={{
|
||||
backgroundColor: "#222",
|
||||
color: "#fff",
|
||||
borderRadius: 1.5,
|
||||
px: 2,
|
||||
py: 1,
|
||||
"&:hover": {
|
||||
backgroundColor: "#2d2d2d",
|
||||
},
|
||||
}}
|
||||
fullWidth
|
||||
startIcon={<ArrowBackIcon />}
|
||||
>
|
||||
Назад
|
||||
</Button>
|
||||
|
||||
<Stack
|
||||
direction="column"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
my={10}
|
||||
spacing={3}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
@@ -82,9 +114,9 @@ export const LeftSidebar = observer(() => {
|
||||
fullHeight
|
||||
/>
|
||||
)}
|
||||
<Typography sx={{ mb: 2, color: "#fff" }} textAlign="center">
|
||||
<Typography sx={{ color: "#fff" }} textAlign="center">
|
||||
При поддержке Правительства
|
||||
</Typography>{" "}
|
||||
</Typography>
|
||||
</div>
|
||||
</Stack>
|
||||
|
||||
@@ -92,7 +124,6 @@ export const LeftSidebar = observer(() => {
|
||||
direction="column"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
my={10}
|
||||
spacing={2}
|
||||
>
|
||||
<Button variant="outlined" color="warning" fullWidth>
|
||||
@@ -108,7 +139,7 @@ export const LeftSidebar = observer(() => {
|
||||
alignItems="center"
|
||||
maxHeight={150}
|
||||
justifyContent="center"
|
||||
my={10}
|
||||
flexGrow={1}
|
||||
>
|
||||
{carrierLogo && (
|
||||
<MediaViewer
|
||||
@@ -122,14 +153,35 @@ export const LeftSidebar = observer(() => {
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
<Typography
|
||||
variant="h6"
|
||||
textAlign="center"
|
||||
mt="auto"
|
||||
sx={{ color: "#fff" }}
|
||||
>
|
||||
<Typography variant="h6" textAlign="center" sx={{ color: "#fff" }}>
|
||||
#ВсемПоПути
|
||||
</Typography>
|
||||
</Stack>
|
||||
|
||||
{!open && (
|
||||
<Typography
|
||||
variant="caption"
|
||||
sx={{
|
||||
color: "rgba(255,255,255,0.6)",
|
||||
writingMode: "vertical-rl",
|
||||
transform: "rotate(180deg)",
|
||||
letterSpacing: 4,
|
||||
position: "absolute",
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
transformOrigin: "center",
|
||||
translate: "-50% -50%",
|
||||
opacity: 0.6,
|
||||
pointerEvents: "none",
|
||||
}}
|
||||
>
|
||||
#ВсемПоПути
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
<div className="absolute bottom-[20px] -right-[520px] z-10">
|
||||
<LanguageSelector onBack={onToggle} isSidebarOpen={open} />
|
||||
</div>
|
||||
</Box>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -41,6 +41,8 @@ const MapDataContext = createContext<{
|
||||
latitude: number,
|
||||
longitude: number
|
||||
) => void;
|
||||
setIconSize: (size: number) => void;
|
||||
setFontSize: (size: number) => void;
|
||||
saveChanges: () => void;
|
||||
}>({
|
||||
originalRouteData: undefined,
|
||||
@@ -61,6 +63,8 @@ const MapDataContext = createContext<{
|
||||
setStationOffset: () => {},
|
||||
setStationAlign: () => {},
|
||||
setSightCoordinates: () => {},
|
||||
setIconSize: () => {},
|
||||
setFontSize: () => {},
|
||||
saveChanges: () => {},
|
||||
});
|
||||
|
||||
@@ -164,9 +168,57 @@ export const MapDataProvider = observer(
|
||||
});
|
||||
}
|
||||
|
||||
function setMapCenter(x: number, y: number) {
|
||||
function setIconSize(size: number) {
|
||||
const clamped = Math.max(50, Math.min(300, size));
|
||||
setRouteChanges((prev) => {
|
||||
return { ...prev, center_latitude: x, center_longitude: y };
|
||||
if (prev.icon_size === clamped) {
|
||||
return prev;
|
||||
}
|
||||
return { ...prev, icon_size: clamped };
|
||||
});
|
||||
}
|
||||
|
||||
function setFontSize(size: number) {
|
||||
const clamped = Math.max(50, Math.min(300, size));
|
||||
setRouteChanges((prev) => {
|
||||
if (prev.font_size === clamped) {
|
||||
return prev;
|
||||
}
|
||||
return { ...prev, font_size: clamped };
|
||||
});
|
||||
}
|
||||
|
||||
function setMapCenter(latitude: number, longitude: number) {
|
||||
const epsilon = 1e-6;
|
||||
|
||||
setRouteChanges((prev) => {
|
||||
const prevLat = prev.center_latitude;
|
||||
const prevLon = prev.center_longitude;
|
||||
|
||||
if (
|
||||
prevLat !== undefined &&
|
||||
prevLon !== undefined &&
|
||||
Math.abs(prevLat - latitude) < epsilon &&
|
||||
Math.abs(prevLon - longitude) < epsilon
|
||||
) {
|
||||
return prev;
|
||||
}
|
||||
|
||||
return {
|
||||
...prev,
|
||||
center_latitude: latitude,
|
||||
center_longitude: longitude,
|
||||
};
|
||||
});
|
||||
|
||||
setRouteData((routePrev) => {
|
||||
if (!routePrev) return routePrev;
|
||||
|
||||
return {
|
||||
...routePrev,
|
||||
center_latitude: latitude,
|
||||
center_longitude: longitude,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -179,12 +231,42 @@ export const MapDataProvider = observer(
|
||||
async function saveStationChanges() {
|
||||
for (const station of stationChanges) {
|
||||
await authInstance.patch(`/route/${routeId}/station`, station);
|
||||
|
||||
setStationData((prev) => {
|
||||
const updated = { ...prev };
|
||||
Object.keys(updated).forEach((lang) => {
|
||||
updated[lang] = updated[lang].map((s) =>
|
||||
s.id === station.station_id
|
||||
? {
|
||||
...s,
|
||||
offset_x: station.offset_x,
|
||||
offset_y: station.offset_y,
|
||||
}
|
||||
: s
|
||||
);
|
||||
});
|
||||
return updated;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function saveSightChanges() {
|
||||
for (const sight of sightChanges) {
|
||||
await authInstance.patch(`/route/${routeId}/sight`, sight);
|
||||
|
||||
setSightData((prev) =>
|
||||
prev
|
||||
? prev.map((s) =>
|
||||
s.id === sight.sight_id
|
||||
? {
|
||||
...s,
|
||||
latitude: sight.latitude,
|
||||
longitude: sight.longitude,
|
||||
}
|
||||
: s
|
||||
)
|
||||
: prev
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,6 +402,14 @@ export const MapDataProvider = observer(
|
||||
latitude: number,
|
||||
longitude: number
|
||||
) {
|
||||
setSightData((prev) =>
|
||||
prev
|
||||
? prev.map((sight) =>
|
||||
sight.id === sightId ? { ...sight, latitude, longitude } : sight
|
||||
)
|
||||
: prev
|
||||
);
|
||||
|
||||
setSightChanges((prev) => {
|
||||
const existingIndex = prev.findIndex(
|
||||
(sight) => sight.sight_id === sightId
|
||||
@@ -375,6 +465,8 @@ export const MapDataProvider = observer(
|
||||
setStationOffset,
|
||||
setStationAlign,
|
||||
setSightCoordinates,
|
||||
setIconSize,
|
||||
setFontSize,
|
||||
}),
|
||||
[
|
||||
originalRouteData,
|
||||
@@ -387,6 +479,8 @@ export const MapDataProvider = observer(
|
||||
isStationLoading,
|
||||
isSightLoading,
|
||||
selectedSight,
|
||||
setIconSize,
|
||||
setFontSize,
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ import { Button, Stack, TextField, Typography, Slider } from "@mui/material";
|
||||
import { useMapData } from "./MapDataContext";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTransform } from "./TransformContext";
|
||||
import { coordinatesToLocal, localToCoordinates } from "./utils";
|
||||
import { SCALE_FACTOR } from "./Constants";
|
||||
import { toast } from "react-toastify";
|
||||
|
||||
@@ -13,18 +12,10 @@ export function RightSidebar() {
|
||||
saveChanges,
|
||||
originalRouteData,
|
||||
setMapRotation,
|
||||
setMapCenter,
|
||||
setIconSize: updateIconSize,
|
||||
setFontSize: updateFontSize,
|
||||
} = useMapData();
|
||||
const {
|
||||
rotation,
|
||||
position,
|
||||
screenToLocal,
|
||||
screenCenter,
|
||||
rotateToAngle,
|
||||
setTransform,
|
||||
scale,
|
||||
setScaleAtCenter,
|
||||
} = useTransform();
|
||||
const { rotation, rotateToAngle, scale, setScaleAtCenter } = useTransform();
|
||||
|
||||
const [minScale, setMinScale] = useState<number>(1);
|
||||
const [maxScale, setMaxScale] = useState<number>(5);
|
||||
@@ -34,6 +25,8 @@ export function RightSidebar() {
|
||||
});
|
||||
const [rotationDegrees, setRotationDegrees] = useState<number>(0);
|
||||
const [isUserEditing, setIsUserEditing] = useState<boolean>(false);
|
||||
const [iconSize, setIconSize] = useState<number>(100);
|
||||
const [fontSize, setFontSize] = useState<number>(100);
|
||||
|
||||
useEffect(() => {
|
||||
if (originalRouteData) {
|
||||
@@ -50,6 +43,8 @@ export function RightSidebar() {
|
||||
x: originalRouteData.center_latitude ?? 0,
|
||||
y: originalRouteData.center_longitude ?? 0,
|
||||
});
|
||||
setIconSize(originalRouteData.icon_size ?? 100);
|
||||
setFontSize(originalRouteData.font_size ?? 100);
|
||||
}
|
||||
}, [originalRouteData]);
|
||||
|
||||
@@ -70,33 +65,55 @@ export function RightSidebar() {
|
||||
}, [rotationDegrees]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isUserEditing) {
|
||||
const center = screenCenter ?? { x: 0, y: 0 };
|
||||
const localCenter = screenToLocal(center.x, center.y);
|
||||
const coordinates = localToCoordinates(localCenter.x, localCenter.y);
|
||||
setLocalCenter({ x: coordinates.latitude, y: coordinates.longitude });
|
||||
if (isUserEditing) {
|
||||
return;
|
||||
}
|
||||
}, [
|
||||
position,
|
||||
screenCenter,
|
||||
screenToLocal,
|
||||
localToCoordinates,
|
||||
setLocalCenter,
|
||||
isUserEditing,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
setMapCenter(localCenter.x, localCenter.y);
|
||||
}, [localCenter]);
|
||||
const latitude = routeData?.center_latitude ?? 0;
|
||||
const longitude = routeData?.center_longitude ?? 0;
|
||||
|
||||
setLocalCenter((prev) => {
|
||||
if (
|
||||
Math.abs(prev.x - latitude) < 1e-6 &&
|
||||
Math.abs(prev.y - longitude) < 1e-6
|
||||
) {
|
||||
return prev;
|
||||
}
|
||||
return { x: latitude, y: longitude };
|
||||
});
|
||||
}, [isUserEditing, routeData?.center_latitude, routeData?.center_longitude]);
|
||||
|
||||
function setRotationFromDegrees(degrees: number) {
|
||||
rotateToAngle((degrees * Math.PI) / 180);
|
||||
}
|
||||
|
||||
function pan({ x, y }: { x: number; y: number }) {
|
||||
const coordinates = coordinatesToLocal(x, y);
|
||||
setTransform(coordinates.x, coordinates.y);
|
||||
const handleIconSizeChange = (value: number) => {
|
||||
if (!Number.isFinite(value)) {
|
||||
return;
|
||||
}
|
||||
const clamped = Math.max(50, Math.min(300, Math.round(value)));
|
||||
setIconSize(clamped);
|
||||
updateIconSize(clamped);
|
||||
};
|
||||
|
||||
const handleFontSizeChange = (value: number) => {
|
||||
if (!Number.isFinite(value)) {
|
||||
return;
|
||||
}
|
||||
const clamped = Math.max(50, Math.min(300, Math.round(value)));
|
||||
setFontSize(clamped);
|
||||
updateFontSize(clamped);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const next = routeData?.icon_size ?? originalRouteData?.icon_size ?? 100;
|
||||
setIconSize((prev) => (Math.abs(prev - next) > 0.5 ? next : prev));
|
||||
}, [routeData?.icon_size, originalRouteData?.icon_size]);
|
||||
|
||||
useEffect(() => {
|
||||
const next = routeData?.font_size ?? originalRouteData?.font_size ?? 100;
|
||||
setFontSize((prev) => (Math.abs(prev - next) > 0.5 ? next : prev));
|
||||
}, [routeData?.font_size, originalRouteData?.font_size]);
|
||||
|
||||
if (!routeData) {
|
||||
return null;
|
||||
@@ -176,6 +193,10 @@ export function RightSidebar() {
|
||||
newMaxScale = 3;
|
||||
}
|
||||
|
||||
if (newMaxScale > 300) {
|
||||
newMaxScale = 300;
|
||||
}
|
||||
|
||||
setMaxScale(newMaxScale);
|
||||
|
||||
if (newMaxScale - minScale < 2) {
|
||||
@@ -204,7 +225,7 @@ export function RightSidebar() {
|
||||
slotProps={{
|
||||
input: {
|
||||
min: 3,
|
||||
max: 10,
|
||||
max: 300,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
@@ -268,6 +289,62 @@ export function RightSidebar() {
|
||||
}}
|
||||
/>
|
||||
|
||||
<Typography variant="body2" sx={{ color: "#fff", textAlign: "center" }}>
|
||||
Размер иконок: {iconSize}%
|
||||
</Typography>
|
||||
|
||||
<Slider
|
||||
value={iconSize}
|
||||
onChange={(_, value) => {
|
||||
if (typeof value === "number") {
|
||||
handleIconSizeChange(value);
|
||||
}
|
||||
}}
|
||||
min={50}
|
||||
max={300}
|
||||
step={1}
|
||||
sx={{
|
||||
color: "#fff",
|
||||
"& .MuiSlider-thumb": {
|
||||
backgroundColor: "#fff",
|
||||
},
|
||||
"& .MuiSlider-track": {
|
||||
backgroundColor: "#fff",
|
||||
},
|
||||
"& .MuiSlider-rail": {
|
||||
backgroundColor: "#666",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
<Typography variant="body2" sx={{ color: "#fff", textAlign: "center" }}>
|
||||
Размер шрифта: {fontSize}%
|
||||
</Typography>
|
||||
|
||||
<Slider
|
||||
value={fontSize}
|
||||
onChange={(_, value) => {
|
||||
if (typeof value === "number") {
|
||||
handleFontSizeChange(value);
|
||||
}
|
||||
}}
|
||||
min={50}
|
||||
max={300}
|
||||
step={1}
|
||||
sx={{
|
||||
color: "#fff",
|
||||
"& .MuiSlider-thumb": {
|
||||
backgroundColor: "#fff",
|
||||
},
|
||||
"& .MuiSlider-track": {
|
||||
backgroundColor: "#fff",
|
||||
},
|
||||
"& .MuiSlider-rail": {
|
||||
backgroundColor: "#666",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
type="number"
|
||||
label="Поворот (в градусах)"
|
||||
@@ -310,9 +387,10 @@ export function RightSidebar() {
|
||||
onChange={(e) => {
|
||||
setIsUserEditing(true);
|
||||
setLocalCenter((prev) => ({ ...prev, x: Number(e.target.value) }));
|
||||
pan({ x: Number(e.target.value), y: localCenter.y });
|
||||
}}
|
||||
onBlur={() => setIsUserEditing(false)}
|
||||
onBlur={() => {
|
||||
setIsUserEditing(false);
|
||||
}}
|
||||
style={{ backgroundColor: "#222", borderRadius: 4 }}
|
||||
sx={{
|
||||
"& .MuiInputLabel-root": {
|
||||
@@ -334,9 +412,10 @@ export function RightSidebar() {
|
||||
onChange={(e) => {
|
||||
setIsUserEditing(true);
|
||||
setLocalCenter((prev) => ({ ...prev, y: Number(e.target.value) }));
|
||||
pan({ x: localCenter.x, y: Number(e.target.value) });
|
||||
}}
|
||||
onBlur={() => setIsUserEditing(false)}
|
||||
onBlur={() => {
|
||||
setIsUserEditing(false);
|
||||
}}
|
||||
style={{ backgroundColor: "#222", borderRadius: 4 }}
|
||||
sx={{
|
||||
"& .MuiInputLabel-root": {
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
TilingSprite,
|
||||
Text,
|
||||
} from "pixi.js";
|
||||
import { Stack } from "@mui/material";
|
||||
import { Box, Stack } from "@mui/material";
|
||||
import { MapDataProvider, useMapData } from "./MapDataContext";
|
||||
import { TransformProvider, useTransform } from "./TransformContext";
|
||||
import { InfiniteCanvas } from "./InfiniteCanvas";
|
||||
@@ -26,6 +26,7 @@ import { Sight } from "./Sight";
|
||||
import { SightData } from "./types";
|
||||
import { Station } from "./Station";
|
||||
import { UP_SCALE } from "./Constants";
|
||||
import { WebGLRouteMapPrototype } from "./webgl-prototype/WebGLRouteMapPrototype";
|
||||
import CircularProgress from "@mui/material/CircularProgress";
|
||||
|
||||
extend({
|
||||
@@ -51,14 +52,33 @@ const Loading = () => {
|
||||
return null;
|
||||
};
|
||||
export const RoutePreview = () => {
|
||||
const { routeData, stationData, sightData } = useMapData();
|
||||
const [isLeftSidebarOpen, setIsLeftSidebarOpen] = useState(true);
|
||||
return (
|
||||
<MapDataProvider>
|
||||
<TransformProvider>
|
||||
<Stack direction="row" height="100vh" width="100vw" overflow="hidden">
|
||||
{routeData && stationData && sightData ? <LanguageSwitcher /> : null}
|
||||
<Loading />
|
||||
<LeftSidebar />
|
||||
<Box
|
||||
sx={{
|
||||
position: "relative",
|
||||
width: isLeftSidebarOpen ? 300 : 0,
|
||||
transition: "width 0.3s ease",
|
||||
overflow: "visible",
|
||||
height: "100%",
|
||||
bgcolor: "primary.main",
|
||||
borderRight: isLeftSidebarOpen
|
||||
? "1px solid rgba(255,255,255,0.08)"
|
||||
: "none",
|
||||
display: "flex",
|
||||
justifyContent: "flex-start",
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
<LeftSidebar
|
||||
open={isLeftSidebarOpen}
|
||||
onToggle={() => setIsLeftSidebarOpen((prev) => !prev)}
|
||||
/>
|
||||
</Box>
|
||||
<Stack direction="row" flex={1} position="relative" height="100%">
|
||||
<RouteMap />
|
||||
<Widgets />
|
||||
@@ -165,8 +185,7 @@ export const RouteMap = observer(() => {
|
||||
|
||||
return (
|
||||
<div style={{ width: "100%", height: "100%" }} ref={parentRef}>
|
||||
<LanguageSwitcher />
|
||||
<Application resizeTo={parentRef} background="#fff" preference="webgl">
|
||||
{/* <Application resizeTo={parentRef} background="#000" preference="webgl">
|
||||
<InfiniteCanvas>
|
||||
<TravelPath points={points} />
|
||||
{stationData[language].map((obj, index) => (
|
||||
@@ -184,7 +203,8 @@ export const RouteMap = observer(() => {
|
||||
return <Sight sight={sight} id={index} key={sight.id} />;
|
||||
})}
|
||||
</InfiniteCanvas>
|
||||
</Application>
|
||||
</Application> */}
|
||||
<WebGLRouteMapPrototype />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -3,6 +3,8 @@ export interface RouteData {
|
||||
carrier_id: number;
|
||||
center_latitude: number;
|
||||
center_longitude: number;
|
||||
icon_size: number;
|
||||
font_size: number;
|
||||
governor_appeal: number;
|
||||
id: number;
|
||||
path: [number, number][];
|
||||
|
||||
217
src/pages/Route/route-preview/web-gl/LanguageSelector.tsx
Normal file
217
src/pages/Route/route-preview/web-gl/LanguageSelector.tsx
Normal file
@@ -0,0 +1,217 @@
|
||||
import { useEffect, useMemo, useRef, useState, type ReactElement } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { languageStore } from "@shared";
|
||||
|
||||
const LANGUAGES = ["ru", "zh", "en"] as const;
|
||||
type Language = (typeof LANGUAGES)[number];
|
||||
|
||||
type LanguageSelectorProps = {
|
||||
onBack?: () => void;
|
||||
isSidebarOpen?: boolean;
|
||||
};
|
||||
|
||||
const renderLanguageIcon = (lang: Language): ReactElement => {
|
||||
switch (lang) {
|
||||
case "ru":
|
||||
return (
|
||||
<svg
|
||||
className="h-12 w-12 cursor-pointer"
|
||||
viewBox="0 0 48 48"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect x="3" y="3" width="42" height="42" rx="21" fill="black" />
|
||||
<path
|
||||
d="M24 0C10.75 0 0 10.75 0 24C0 37.25 10.75 48 24 48C37.25 48 48 37.25 48 24C48 10.75 37.25 0 24 0ZM24.2 33.55H19.92L16.29 26.46H13.11V33.55H9.12V14.18H16.32C18.61 14.18 20.37 14.69 21.62 15.71C22.87 16.73 23.48 18.17 23.48 20.03C23.48 21.35 23.19 22.45 22.62 23.34C22.05 24.22 21.18 24.93 20.02 25.45L24.21 33.37V33.56L24.2 33.55ZM40.3 26.94C40.3 29.06 39.64 30.74 38.31 31.97C36.98 33.2 35.17 33.82 32.87 33.82C30.57 33.82 28.81 33.22 27.48 32.02C26.15 30.82 25.47 29.18 25.44 27.08V14.18H29.43V26.97C29.43 28.24 29.73 29.16 30.34 29.74C30.95 30.32 31.79 30.61 32.86 30.61C35.1 30.61 36.24 29.43 36.28 27.07V14.18H40.28V26.94H40.3Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M16.3086 17.4099H13.0986V23.2199H16.3186C17.3186 23.2199 18.0986 22.9599 18.6486 22.4499C19.1986 21.9399 19.4686 21.2399 19.4686 20.3399C19.4686 19.4399 19.2086 18.7099 18.6886 18.1799C18.1686 17.6499 17.3686 17.3899 16.2986 17.3899L16.3086 17.4099Z"
|
||||
fill="white"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
case "zh":
|
||||
return (
|
||||
<svg
|
||||
className="h-12 w-12 cursor-pointer"
|
||||
viewBox="0 0 48 48"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect x="3" y="3" width="42" height="42" rx="21" fill="black" />
|
||||
<path d="M10.287 20.382H6.291V24.147H10.287V20.382Z" fill={"white"} />
|
||||
<path
|
||||
d="M13.704 24.147H17.721V20.382H13.704V24.147Z"
|
||||
fill={"white"}
|
||||
/>
|
||||
<path
|
||||
d="M36.1254 20.046H29.8575C30.6606 21.9406 31.7187 23.6442 33.0513 25.1217C34.3105 23.6927 35.3315 22.0126 36.1254 20.046Z"
|
||||
fill={"white"}
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M24 48C37.2548 48 48 37.2548 48 24C48 10.7452 37.2548 0 24 0C10.7452 0 0 10.7452 0 24C0 37.2548 10.7452 48 24 48ZM10.287 13.5H13.704V17.1541H21.117V28.446H17.721V27.375H13.704V33.969H10.287V27.375H6.291V28.5511H3V17.1541H10.287V13.5ZM31.35 13.5H34.704V16.8181H43.083V20.046H39.8887C38.804 22.9506 37.3746 25.3834 35.581 27.4065C37.6488 28.9237 40.1651 30.0542 43.1682 30.7291L43.8469 30.8817L43.3465 31.3649C42.7753 31.9162 41.9777 33.0771 41.5886 33.7939L41.4484 34.0521L41.1642 33.9778C37.8385 33.1088 35.1249 31.7521 32.8974 29.9253C30.6296 31.6954 27.9389 33.0335 24.802 34.015L24.4889 34.1129L24.3502 33.8156C24.0724 33.2203 23.2933 32.029 22.8051 31.439L22.4307 30.9868L22.9986 30.8373C25.936 30.0648 28.4025 28.9702 30.4373 27.4935C28.6775 25.4061 27.319 22.9142 26.2412 20.046H23.097V16.8181H31.35V13.5Z"
|
||||
fill={"white"}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
case "en":
|
||||
default:
|
||||
return (
|
||||
<svg
|
||||
className="h-12 w-12 cursor-pointer"
|
||||
viewBox="0 0 48 48"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect x="3" y="3" width="42" height="42" rx="21" fill="black" />
|
||||
<path
|
||||
d="M24 0C10.75 0 0 10.75 0 24C0 37.25 10.75 48 24 48C37.25 48 48 37.25 48 24C48 10.75 37.25 0 24 0ZM21.57 33.79H8.41V14.15H21.55V17.43H12.45V22.11H20.22V25.28H12.45V30.54H21.57V33.79ZM39.54 33.79H35.49L27.61 20.87V33.79H23.56V14.15H27.61L35.5 27.1V14.15H39.53V33.79H39.54Z"
|
||||
fill="white"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const CollapsedIcon = () => (
|
||||
<svg
|
||||
className="h-12 w-12 cursor-pointer"
|
||||
viewBox="0 0 48 48"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect x="4" y="3" width="39" height="42" rx="19.5" fill="black" />
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M0 24C0 10.75 10.75 0 24 0C37.25 0 48 10.75 48 24C48 37.25 37.25 48 24 48C10.75 48 0 37.25 0 24ZM36.05 19.41L38.27 25.47L40.47 19.39L39.17 19.77C39.16 19.75 39.16 19.35 39.16 19.32C38.58 12.41 32.99 7.15 25.94 7.15C25.42 7.15 24.99 7.58 24.99 8.1C24.99 8.62 25.42 9.05 25.94 9.05C31.92 9.05 36.68 13.47 37.26 19.31C37.26 19.325 37.2625 19.435 37.265 19.545C37.2675 19.655 37.27 19.765 37.27 19.78L36.05 19.41ZM8.90375 27.5369C11.3535 26.9568 13.6332 25.8438 15.5701 24.2838L15.5709 24.2846C17.2124 25.8526 19.2497 26.9784 21.4812 27.5521C21.5875 27.5649 21.6945 27.5649 21.8007 27.5521C22.3194 27.6074 22.8298 27.3911 23.1385 26.9848C23.448 26.5786 23.5086 26.0441 23.2986 25.5826C23.0879 25.1211 22.6389 24.803 22.1202 24.7477C20.4804 24.3182 18.9808 23.4937 17.7634 22.3495C20.2489 20.0131 22.1036 17.1245 23.1659 13.9363C23.2729 13.5077 23.165 13.0566 22.8754 12.716C22.6016 12.3627 22.1709 12.1552 21.7136 12.1552H16.6307V10.402C16.6307 9.90121 16.3535 9.43889 15.9045 9.1881C15.4556 8.9373 14.9012 8.9373 14.4522 9.1881C14.0033 9.43809 13.7261 9.90121 13.7261 10.402V12.1824H8.64317C8.12367 12.1824 7.64484 12.45 7.38509 12.8835C7.12534 13.317 7.12534 13.8522 7.38509 14.2857C7.64484 14.7192 8.1245 14.9868 8.64317 14.9868H19.6655C18.6804 16.9971 17.3443 18.828 15.7153 20.3993C14.8373 19.3977 14.0688 18.3128 13.4207 17.1598C13.2747 16.7896 12.9734 16.4972 12.5909 16.3529C12.2083 16.2087 11.7809 16.2279 11.4141 16.405C11.0473 16.5821 10.7751 16.901 10.6647 17.2824C10.5544 17.6638 10.6166 18.0724 10.8357 18.4074C11.6058 19.7423 12.5153 20.997 13.551 22.1516C12.0207 23.3728 10.2307 24.2533 8.30873 24.7317C7.92284 24.7709 7.56932 24.956 7.32617 25.2469C7.08219 25.5369 6.96767 25.9095 7.00833 26.2813C7.04899 26.6539 7.24069 26.9952 7.54193 27.23C7.84235 27.4656 8.22824 27.5761 8.61329 27.5369C8.70956 27.5513 8.80748 27.5513 8.90375 27.5369ZM34.9002 38.8803C35.2512 39.0301 35.6487 39.0397 36.0072 38.9067C36.3815 38.7865 36.6886 38.5237 36.8587 38.18C37.0288 37.8362 37.0462 37.4404 36.9077 37.0839L30.3434 20.7767C30.238 20.5131 30.0529 20.2863 29.8115 20.1261C29.57 19.9658 29.2845 19.8793 28.9924 19.8785C28.7019 19.8785 28.4173 19.9626 28.1766 20.1196C27.9359 20.2775 27.7501 20.501 27.6422 20.7623L21.136 36.523C20.9443 36.9885 21.024 37.5181 21.346 37.9116C21.668 38.305 22.1825 38.5029 22.6962 38.4308C23.2107 38.3579 23.6455 38.0269 23.8372 37.5606L25.4057 33.6489H32.3185L34.1342 38.1079C34.2737 38.4524 34.5492 38.7304 34.9002 38.8803ZM28.9052 25.1652L31.1857 30.8445H31.1849H26.5667L28.9052 25.1652Z"
|
||||
fill="#FFFFFF"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
const ArrowIcon = ({ rotation }: { rotation: number }) => (
|
||||
<svg
|
||||
style={{
|
||||
transform: `rotate(${rotation}deg)`,
|
||||
transition: "transform 0.15s ease",
|
||||
}}
|
||||
className="h-12 w-12"
|
||||
viewBox="0 0 48 48"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect x="8" y="7" width="31" height="33" fill="black" />
|
||||
<path
|
||||
d="M24.0001 0C10.7501 0 0.00012207 10.75 0.00012207 24C0.00012207 37.25 10.7501 48 24.0001 48C37.2501 48 48.0001 37.25 48.0001 24C48.0001 10.75 37.2501 0 24.0001 0ZM37.5401 25.84C37.5401 26.4 37.0901 26.85 36.5301 26.85H20.5901C20.1401 26.85 19.9201 27.39 20.2301 27.71L27.6801 35.16C28.0801 35.56 28.0801 36.2 27.6801 36.59L25.0801 39.19C24.6801 39.59 24.0401 39.59 23.6501 39.19L12.4901 28.03L9.17012 24.71C8.77012 24.31 8.77012 23.67 9.17012 23.28L12.4901 19.96L23.6501 8.8C24.0501 8.4 24.6901 8.4 25.0801 8.8L27.6801 11.4C28.0801 11.8 28.0801 12.44 27.6801 12.83L20.2301 20.28C19.9101 20.6 20.1401 21.14 20.5901 21.14H36.5301C37.0901 21.14 37.5401 21.59 37.5401 22.15V25.82V25.84Z"
|
||||
fill="white"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
const LanguageSelector = observer(
|
||||
({ onBack, isSidebarOpen = true }: LanguageSelectorProps) => {
|
||||
const { language, setLanguage } = languageStore;
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
return;
|
||||
}
|
||||
|
||||
const handleOutside = (event: PointerEvent) => {
|
||||
if (
|
||||
containerRef.current &&
|
||||
!containerRef.current.contains(event.target as Node)
|
||||
) {
|
||||
setIsOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("pointerdown", handleOutside);
|
||||
return () => {
|
||||
document.removeEventListener("pointerdown", handleOutside);
|
||||
};
|
||||
}, [isOpen]);
|
||||
|
||||
const handleSelect = (code: Language) => {
|
||||
setLanguage(code);
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
const toggle = () => setIsOpen((prev) => !prev);
|
||||
|
||||
const containerWidth = useMemo(() => {
|
||||
const BUTTON_SIZE = 56;
|
||||
const GAP = 8;
|
||||
const backWidth = onBack ? BUTTON_SIZE + GAP : 0;
|
||||
const toggleWidth = BUTTON_SIZE;
|
||||
const collapsedWidth = backWidth + toggleWidth + BUTTON_SIZE;
|
||||
const expandedWidth =
|
||||
backWidth +
|
||||
toggleWidth +
|
||||
LANGUAGES.length * BUTTON_SIZE +
|
||||
(LANGUAGES.length - 1) * GAP;
|
||||
return isOpen ? expandedWidth : collapsedWidth;
|
||||
}, [isOpen, onBack]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="pointer-events-auto"
|
||||
style={{
|
||||
width: "500px",
|
||||
transition: "width 0.25s ease",
|
||||
flex: "0 0 auto",
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex items-center gap-2 ">
|
||||
<button
|
||||
type="button"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onBack?.();
|
||||
}}
|
||||
className="flex h-12 w-12 items-center justify-center"
|
||||
aria-label={
|
||||
isOpen ? "Скрыть выбор языка" : "Показать выбор языка"
|
||||
}
|
||||
>
|
||||
<ArrowIcon rotation={isSidebarOpen ? 0 : 180} />
|
||||
</button>
|
||||
{isOpen ? (
|
||||
LANGUAGES.map((lang) => (
|
||||
<button
|
||||
key={lang}
|
||||
type="button"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
handleSelect(lang);
|
||||
}}
|
||||
className="flex h-12 w-12 items-center justify-center"
|
||||
aria-label={`Переключить язык на ${lang.toUpperCase()}`}
|
||||
>
|
||||
{renderLanguageIcon(lang)}
|
||||
</button>
|
||||
))
|
||||
) : (
|
||||
<div
|
||||
className="flex h-12 w-12 items-center justify-center"
|
||||
onClick={toggle}
|
||||
>
|
||||
<CollapsedIcon />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default LanguageSelector;
|
||||
@@ -1,3 +1,24 @@
|
||||
import React, { useEffect, useMemo, useRef, useCallback } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useMapData } from "./MapDataContext";
|
||||
import { useTransform } from "./transformContext";
|
||||
import { coordinatesToLocal } from "./utils";
|
||||
import {
|
||||
UP_SCALE,
|
||||
PATH_COLOR,
|
||||
BACKGROUND_COLOR,
|
||||
UNPASSED_STATION_COLOR,
|
||||
BUS_COLOR,
|
||||
} from "./Constants";
|
||||
import { SCALE_FACTOR } from "../../assets/Constants";
|
||||
import { apiStore } from "../../api/ApiStore/store";
|
||||
import { useGeolocationStore } from "../../stores/hooks/useGeolocationStore";
|
||||
import { useAnimatedPolarPosition } from "../../hooks/useAnimatedPosition";
|
||||
import { useCameraAnimationStore } from "../../stores";
|
||||
import { TramIconWebGL } from "./TramIconWebGL";
|
||||
const sightIcon = new URL("../../assets/images/sight.svg", import.meta.url)
|
||||
.href;
|
||||
|
||||
function initWebGLContext(
|
||||
canvas: HTMLCanvasElement
|
||||
): WebGLRenderingContext | null {
|
||||
@@ -53,11 +74,13 @@ export const WebGLMap = observer(() => {
|
||||
|
||||
const cameraAnimationStore = useCameraAnimationStore();
|
||||
|
||||
// Ref для хранения ограничений масштаба
|
||||
const scaleLimitsRef = useRef({
|
||||
min: null as number | null,
|
||||
max: null as number | null,
|
||||
});
|
||||
|
||||
// Обновляем ограничения масштаба при изменении routeData
|
||||
useEffect(() => {
|
||||
if (
|
||||
routeData?.scale_min !== undefined &&
|
||||
@@ -70,6 +93,7 @@ export const WebGLMap = observer(() => {
|
||||
}
|
||||
}, [routeData?.scale_min, routeData?.scale_max]);
|
||||
|
||||
// Функция для ограничения масштаба значениями с бекенда
|
||||
const clampScale = useCallback((value: number) => {
|
||||
const { min, max } = scaleLimitsRef.current;
|
||||
|
||||
@@ -87,6 +111,7 @@ export const WebGLMap = observer(() => {
|
||||
const setPositionRef = useRef(setPosition);
|
||||
const setScaleRef = useRef(setScale);
|
||||
|
||||
// Обновляем refs при изменении функций
|
||||
useEffect(() => {
|
||||
setPositionRef.current = setPosition;
|
||||
}, [setPosition]);
|
||||
@@ -95,6 +120,7 @@ export const WebGLMap = observer(() => {
|
||||
setScaleRef.current = setScale;
|
||||
}, [setScale]);
|
||||
|
||||
// Логирование данных маршрута для отладки
|
||||
useEffect(() => {
|
||||
if (routeData) {
|
||||
}
|
||||
@@ -119,6 +145,7 @@ export const WebGLMap = observer(() => {
|
||||
setPositionImmediate: setYellowDotPositionImmediate,
|
||||
} = useAnimatedPolarPosition(0, 0, 800);
|
||||
|
||||
// Build transformed route path (map coords)
|
||||
const routePath = useMemo(() => {
|
||||
if (!routeData?.path || routeData?.path.length === 0)
|
||||
return new Float32Array();
|
||||
@@ -174,6 +201,7 @@ export const WebGLMap = observer(() => {
|
||||
rotationAngle,
|
||||
]);
|
||||
|
||||
// Настройка CameraAnimationStore callback - только один раз при монтировании
|
||||
useEffect(() => {
|
||||
const callback = (newPos: { x: number; y: number }, newZoom: number) => {
|
||||
setPosition(newPos);
|
||||
@@ -182,13 +210,15 @@ export const WebGLMap = observer(() => {
|
||||
|
||||
cameraAnimationStore.setUpdateCallback(callback);
|
||||
|
||||
// Синхронизируем начальное состояние только один раз
|
||||
cameraAnimationStore.syncState(position, scale);
|
||||
|
||||
return () => {
|
||||
cameraAnimationStore.setUpdateCallback(null);
|
||||
};
|
||||
}, []);
|
||||
}, []); // Пустой массив - выполняется только при монтировании
|
||||
|
||||
// Установка границ зума
|
||||
useEffect(() => {
|
||||
if (
|
||||
routeData?.scale_min !== undefined &&
|
||||
@@ -199,23 +229,28 @@ export const WebGLMap = observer(() => {
|
||||
}
|
||||
}, [routeData?.scale_min, routeData?.scale_max, cameraAnimationStore]);
|
||||
|
||||
// Автоматический режим - таймер для включения через 5 секунд бездействия
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
const timeSinceActivity = Date.now() - userActivityTimestamp;
|
||||
if (timeSinceActivity >= 5000 && !isAutoMode) {
|
||||
// 5 секунд бездействия - включаем авто режим
|
||||
setIsAutoMode(true);
|
||||
}
|
||||
}, 1000);
|
||||
}, 1000); // Проверяем каждую секунду
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [userActivityTimestamp, isAutoMode, setIsAutoMode]);
|
||||
|
||||
// Следование за желтой точкой с зумом при включенном авто режиме
|
||||
useEffect(() => {
|
||||
// Пропускаем обновление если анимация уже идет
|
||||
if (cameraAnimationStore.isActivelyAnimating) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isAutoMode && transformedTramCoords && screenCenter) {
|
||||
// Преобразуем станции в формат для CameraAnimationStore
|
||||
const transformedStations = stationData
|
||||
? stationData
|
||||
.map((station: any) => {
|
||||
@@ -256,8 +291,10 @@ export const WebGLMap = observer(() => {
|
||||
cameraAnimationStore.setMaxZoom(scaleLimitsRef.current!.max);
|
||||
cameraAnimationStore.setMinZoom(scaleLimitsRef.current!.min);
|
||||
|
||||
// Синхронизируем текущее состояние камеры перед запуском анимации
|
||||
cameraAnimationStore.syncState(positionRef.current, scaleRef.current);
|
||||
|
||||
// Запускаем анимацию к желтой точке
|
||||
cameraAnimationStore.followTram(
|
||||
transformedTramCoords,
|
||||
screenCenter,
|
||||
@@ -277,6 +314,7 @@ export const WebGLMap = observer(() => {
|
||||
rotationAngle,
|
||||
]);
|
||||
|
||||
// Station label overlay positions (DOM overlay)
|
||||
const stationLabels = useMemo(() => {
|
||||
if (!stationData || !routeData)
|
||||
return [] as Array<{ x: number; y: number; name: string; sub?: string }>;
|
||||
@@ -339,6 +377,7 @@ export const WebGLMap = observer(() => {
|
||||
selectedLanguage as any,
|
||||
]);
|
||||
|
||||
// Build transformed stations (map coords)
|
||||
const stationPoints = useMemo(() => {
|
||||
if (!stationData || !routeData) return new Float32Array();
|
||||
const centerLat = routeData.center_latitude;
|
||||
@@ -368,6 +407,7 @@ export const WebGLMap = observer(() => {
|
||||
rotationAngle,
|
||||
]);
|
||||
|
||||
// Build transformed sights (map coords)
|
||||
const sightPoints = useMemo(() => {
|
||||
if (!sightData || !routeData) return new Float32Array();
|
||||
const centerLat = routeData.center_latitude;
|
||||
@@ -511,6 +551,8 @@ export const WebGLMap = observer(() => {
|
||||
const handleResize = () => {
|
||||
const changed = resizeCanvasToDisplaySize(canvas);
|
||||
if (!gl) return;
|
||||
// Update screen center when canvas size changes
|
||||
// Use physical pixels (canvas.width) instead of CSS pixels
|
||||
setScreenCenter({
|
||||
x: canvas.width / 2,
|
||||
y: canvas.height / 2,
|
||||
@@ -546,6 +588,7 @@ export const WebGLMap = observer(() => {
|
||||
const rx = x * cos - y * sin;
|
||||
const ry = x * sin + y * cos;
|
||||
|
||||
// В авторежиме используем анимацию, иначе мгновенное обновление
|
||||
if (isAutoMode) {
|
||||
animateYellowDotTo(rx, ry);
|
||||
} else {
|
||||
@@ -644,18 +687,21 @@ export const WebGLMap = observer(() => {
|
||||
|
||||
const vertexCount = routePath.length / 2;
|
||||
if (vertexCount > 1) {
|
||||
// Generate thick line geometry using triangles with proper joins
|
||||
const generateThickLine = (points: Float32Array, width: number) => {
|
||||
const vertices: number[] = [];
|
||||
const halfWidth = width / 2;
|
||||
|
||||
if (points.length < 4) return new Float32Array();
|
||||
|
||||
// Process each segment
|
||||
for (let i = 0; i < points.length - 2; i += 2) {
|
||||
const x1 = points[i];
|
||||
const y1 = points[i + 1];
|
||||
const x2 = points[i + 2];
|
||||
const y2 = points[i + 3];
|
||||
|
||||
// Calculate perpendicular vector
|
||||
const dx = x2 - x1;
|
||||
const dy = y2 - y1;
|
||||
const length = Math.sqrt(dx * dx + dy * dy);
|
||||
@@ -664,14 +710,18 @@ export const WebGLMap = observer(() => {
|
||||
const perpX = (-dy / length) * halfWidth;
|
||||
const perpY = (dx / length) * halfWidth;
|
||||
|
||||
// Create quad (two triangles) for this line segment
|
||||
// Triangle 1
|
||||
vertices.push(x1 + perpX, y1 + perpY);
|
||||
vertices.push(x1 - perpX, y1 - perpY);
|
||||
vertices.push(x2 + perpX, y2 + perpY);
|
||||
|
||||
// Triangle 2
|
||||
vertices.push(x1 - perpX, y1 - perpY);
|
||||
vertices.push(x2 - perpX, y2 - perpY);
|
||||
vertices.push(x2 + perpX, y2 + perpY);
|
||||
|
||||
// Add simple join triangles to fill gaps
|
||||
if (i < points.length - 4) {
|
||||
const x3 = points[i + 4];
|
||||
const y3 = points[i + 5];
|
||||
@@ -683,6 +733,7 @@ export const WebGLMap = observer(() => {
|
||||
const perpX2 = (-dy2 / length2) * halfWidth;
|
||||
const perpY2 = (dx2 / length2) * halfWidth;
|
||||
|
||||
// Simple join - just connect the endpoints
|
||||
vertices.push(x2 + perpX, y2 + perpY);
|
||||
vertices.push(x2 - perpX, y2 - perpY);
|
||||
vertices.push(x2 + perpX2, y2 + perpY2);
|
||||
@@ -704,18 +755,22 @@ export const WebGLMap = observer(() => {
|
||||
gl.uniform4f(uniforms.u_color, r1, g1, b1, 1);
|
||||
|
||||
if (tramSegIndex >= 0) {
|
||||
// Используем точную позицию желтой точки для определения конца красной линии
|
||||
const animatedPos = animatedYellowDotPosition;
|
||||
if (
|
||||
animatedPos &&
|
||||
animatedPos.x !== undefined &&
|
||||
animatedPos.y !== undefined
|
||||
) {
|
||||
// Создаем массив точек от начала маршрута до позиции желтой точки
|
||||
const passedPoints: number[] = [];
|
||||
|
||||
// Добавляем все точки до текущего сегмента
|
||||
for (let i = 0; i <= tramSegIndex; i++) {
|
||||
passedPoints.push(routePath[i * 2], routePath[i * 2 + 1]);
|
||||
}
|
||||
|
||||
// Добавляем точную позицию желтой точки как конечную точку
|
||||
passedPoints.push(animatedPos.x, animatedPos.y);
|
||||
|
||||
if (passedPoints.length >= 4) {
|
||||
@@ -734,6 +789,7 @@ export const WebGLMap = observer(() => {
|
||||
const b2 = (UNPASSED_STATION_COLOR & 0xff) / 255;
|
||||
gl.uniform4f(uniforms.u_color, r2, g2, b2, 1);
|
||||
|
||||
// Серая линия начинается точно от позиции желтой точки
|
||||
const animatedPos = animatedYellowDotPosition;
|
||||
if (
|
||||
animatedPos &&
|
||||
@@ -742,8 +798,10 @@ export const WebGLMap = observer(() => {
|
||||
) {
|
||||
const unpassedPoints: number[] = [];
|
||||
|
||||
// Добавляем позицию желтой точки как начальную точку серой линии
|
||||
unpassedPoints.push(animatedPos.x, animatedPos.y);
|
||||
|
||||
// Добавляем все точки после текущего сегмента
|
||||
for (let i = tramSegIndex + 1; i < vertexCount; i++) {
|
||||
unpassedPoints.push(routePath[i * 2], routePath[i * 2 + 1]);
|
||||
}
|
||||
@@ -759,6 +817,7 @@ export const WebGLMap = observer(() => {
|
||||
}
|
||||
}
|
||||
|
||||
// Draw stations
|
||||
if (stationPoints.length > 0) {
|
||||
gl.useProgram(pprog);
|
||||
const a_pos_pts = gl.getAttribLocation(pprog, "a_pos");
|
||||
@@ -776,6 +835,7 @@ export const WebGLMap = observer(() => {
|
||||
gl.enableVertexAttribArray(a_pos_pts);
|
||||
gl.vertexAttribPointer(a_pos_pts, 2, gl.FLOAT, false, 0, 0);
|
||||
|
||||
// Draw station outlines (black background)
|
||||
gl.uniform1f(u_pointSize, 10 * scale * 1.5);
|
||||
const r_outline = ((BACKGROUND_COLOR >> 16) & 0xff) / 255;
|
||||
const g_outline = ((BACKGROUND_COLOR >> 8) & 0xff) / 255;
|
||||
@@ -783,12 +843,15 @@ export const WebGLMap = observer(() => {
|
||||
gl.uniform4f(u_color_pts, r_outline, g_outline, b_outline, 1);
|
||||
gl.drawArrays(gl.POINTS, 0, stationPoints.length / 2);
|
||||
|
||||
// Draw station cores (colored based on passed/unpassed)
|
||||
gl.uniform1f(u_pointSize, 8.0 * scale * 1.5);
|
||||
|
||||
// Draw passed stations (red)
|
||||
if (tramSegIndex >= 0) {
|
||||
const passedStations = [];
|
||||
for (let i = 0; i < stationData.length; i++) {
|
||||
if (i <= tramSegIndex) {
|
||||
// @ts-ignore
|
||||
passedStations.push(stationPoints[i * 2], stationPoints[i * 2 + 1]);
|
||||
}
|
||||
}
|
||||
@@ -806,11 +869,13 @@ export const WebGLMap = observer(() => {
|
||||
}
|
||||
}
|
||||
|
||||
// Draw unpassed stations (gray)
|
||||
if (tramSegIndex >= 0) {
|
||||
const unpassedStations = [];
|
||||
for (let i = 0; i < stationData.length; i++) {
|
||||
if (i > tramSegIndex) {
|
||||
unpassedStations.push(
|
||||
// @ts-ignore
|
||||
stationPoints[i * 2],
|
||||
stationPoints[i * 2 + 1]
|
||||
);
|
||||
@@ -829,6 +894,7 @@ export const WebGLMap = observer(() => {
|
||||
gl.drawArrays(gl.POINTS, 0, unpassedStations.length / 2);
|
||||
}
|
||||
} else {
|
||||
// If no tram position, draw all stations as unpassed
|
||||
const r_unpassed = ((UNPASSED_STATION_COLOR >> 16) & 0xff) / 255;
|
||||
const g_unpassed = ((UNPASSED_STATION_COLOR >> 8) & 0xff) / 255;
|
||||
const b_unpassed = (UNPASSED_STATION_COLOR & 0xff) / 255;
|
||||
@@ -970,6 +1036,7 @@ export const WebGLMap = observer(() => {
|
||||
if (passedStations.length)
|
||||
gl.drawArrays(gl.POINTS, 0, passedStations.length / 2);
|
||||
|
||||
// Draw black dots with white outline for terminal stations (startStopId and endStopId) - 5x larger
|
||||
if (
|
||||
stationData &&
|
||||
stationData.length > 0 &&
|
||||
@@ -982,6 +1049,7 @@ export const WebGLMap = observer(() => {
|
||||
const cos = Math.cos(rotationAngle);
|
||||
const sin = Math.sin(rotationAngle);
|
||||
|
||||
// Find terminal stations using startStopId and endStopId from context
|
||||
const startStationData = stationData.find(
|
||||
(station) => station.id.toString() === apiStore.context?.startStopId
|
||||
);
|
||||
@@ -991,6 +1059,7 @@ export const WebGLMap = observer(() => {
|
||||
|
||||
const terminalStations: number[] = [];
|
||||
|
||||
// Transform start station coordinates if found
|
||||
if (startStationData) {
|
||||
const startLocal = coordinatesToLocal(
|
||||
startStationData.latitude - centerLat,
|
||||
@@ -1003,6 +1072,7 @@ export const WebGLMap = observer(() => {
|
||||
terminalStations.push(startRx, startRy);
|
||||
}
|
||||
|
||||
// Transform end station coordinates if found
|
||||
if (endStationData) {
|
||||
const endLocal = coordinatesToLocal(
|
||||
endStationData.latitude - centerLat,
|
||||
@@ -1016,10 +1086,12 @@ export const WebGLMap = observer(() => {
|
||||
}
|
||||
|
||||
if (terminalStations.length > 0) {
|
||||
// Determine if each terminal station is passed
|
||||
const terminalStationData: any[] = [];
|
||||
if (startStationData) terminalStationData.push(startStationData);
|
||||
if (endStationData) terminalStationData.push(endStationData);
|
||||
|
||||
// Get tram segment index for comparison
|
||||
let tramSegIndex = -1;
|
||||
const coords: any = apiStore?.context?.currentCoordinates;
|
||||
if (coords && centerLat !== undefined && centerLon !== undefined) {
|
||||
@@ -1034,6 +1106,7 @@ export const WebGLMap = observer(() => {
|
||||
const tx = wx * cosR - wy * sinR;
|
||||
const ty = wx * sinR + wy * cosR;
|
||||
|
||||
// Find closest segment to tram position
|
||||
let best = -1;
|
||||
let bestD = Infinity;
|
||||
for (let i = 0; i < routePath.length - 2; i += 2) {
|
||||
@@ -1058,6 +1131,7 @@ export const WebGLMap = observer(() => {
|
||||
tramSegIndex = best;
|
||||
}
|
||||
|
||||
// Check if each terminal station is passed
|
||||
const isStartPassed = startStationData
|
||||
? (() => {
|
||||
const sx = terminalStations[0];
|
||||
@@ -1133,41 +1207,46 @@ export const WebGLMap = observer(() => {
|
||||
gl.enableVertexAttribArray(a_pos_pts);
|
||||
gl.vertexAttribPointer(a_pos_pts, 2, gl.FLOAT, false, 0, 0);
|
||||
|
||||
// Draw colored outline based on passed status - 24 pixels (x2)
|
||||
gl.uniform1f(u_pointSize, 18.0 * scale);
|
||||
if (startStationData && endStationData) {
|
||||
// Both stations - draw each with its own color
|
||||
if (isStartPassed) {
|
||||
gl.uniform4f(u_color_pts, 1.0, 0.4, 0.4, 1.0);
|
||||
gl.uniform4f(u_color_pts, 1.0, 0.4, 0.4, 1.0); // Ярко-красный для пройденных
|
||||
} else {
|
||||
gl.uniform4f(u_color_pts, 0.7, 0.7, 0.7, 1.0);
|
||||
gl.uniform4f(u_color_pts, 0.7, 0.7, 0.7, 1.0); // Светло-серый для непройденных
|
||||
}
|
||||
gl.drawArrays(gl.POINTS, 0, 1);
|
||||
gl.drawArrays(gl.POINTS, 0, 1); // Draw start station
|
||||
|
||||
if (isEndPassed) {
|
||||
gl.uniform4f(u_color_pts, 1.0, 0.4, 0.4, 1.0);
|
||||
gl.uniform4f(u_color_pts, 1.0, 0.4, 0.4, 1.0); // Ярко-красный для пройденных
|
||||
} else {
|
||||
gl.uniform4f(u_color_pts, 0.7, 0.7, 0.7, 1.0);
|
||||
gl.uniform4f(u_color_pts, 0.7, 0.7, 0.7, 1.0); // Светло-серый для непройденных
|
||||
}
|
||||
gl.drawArrays(gl.POINTS, 1, 1);
|
||||
gl.drawArrays(gl.POINTS, 1, 1); // Draw end station
|
||||
} else {
|
||||
// Single station - use appropriate color
|
||||
const isPassed = startStationData ? isStartPassed : isEndPassed;
|
||||
if (isPassed) {
|
||||
gl.uniform4f(u_color_pts, 1.0, 0.4, 0.4, 1.0);
|
||||
gl.uniform4f(u_color_pts, 1.0, 0.4, 0.4, 1.0); // Ярко-красный для пройденных
|
||||
} else {
|
||||
gl.uniform4f(u_color_pts, 0.7, 0.7, 0.7, 1.0);
|
||||
gl.uniform4f(u_color_pts, 0.7, 0.7, 0.7, 1.0); // Светло-серый для непройденных
|
||||
}
|
||||
gl.drawArrays(gl.POINTS, 0, terminalStations.length / 2);
|
||||
}
|
||||
|
||||
// Draw dark center - 12 pixels (x2)
|
||||
gl.uniform1f(u_pointSize, 11.0 * scale);
|
||||
const r_center = ((BACKGROUND_COLOR >> 16) & 0xff) / 255;
|
||||
const g_center = ((BACKGROUND_COLOR >> 8) & 0xff) / 255;
|
||||
const b_center = (BACKGROUND_COLOR & 0xff) / 255;
|
||||
gl.uniform4f(u_color_pts, r_center, g_center, b_center, 1.0);
|
||||
gl.uniform4f(u_color_pts, r_center, g_center, b_center, 1.0); // Dark color
|
||||
gl.drawArrays(gl.POINTS, 0, terminalStations.length / 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw yellow dot for tram position
|
||||
if (animatedYellowDotPosition) {
|
||||
const rx = animatedYellowDotPosition.x;
|
||||
const ry = animatedYellowDotPosition.y;
|
||||
@@ -1269,6 +1348,7 @@ export const WebGLMap = observer(() => {
|
||||
});
|
||||
|
||||
const onPointerDown = (e: PointerEvent) => {
|
||||
// Отслеживаем активность пользователя
|
||||
updateUserActivity();
|
||||
if (isAutoMode) {
|
||||
setIsAutoMode(false);
|
||||
@@ -1301,6 +1381,7 @@ export const WebGLMap = observer(() => {
|
||||
const onPointerMove = (e: PointerEvent) => {
|
||||
if (!activePointers.has(e.pointerId)) return;
|
||||
|
||||
// Отслеживаем активность пользователя
|
||||
updateUserActivity();
|
||||
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
@@ -1326,6 +1407,7 @@ export const WebGLMap = observer(() => {
|
||||
};
|
||||
}
|
||||
|
||||
// Process the pinch gesture
|
||||
if (pinchStart) {
|
||||
const currentDistance = getDistance(p1, p2);
|
||||
const zoomFactor = currentDistance / pinchStart.distance;
|
||||
@@ -1344,6 +1426,7 @@ export const WebGLMap = observer(() => {
|
||||
} else if (isDragging && activePointers.size === 1) {
|
||||
const p = Array.from(activePointers.values())[0];
|
||||
|
||||
// Проверяем валидность значений
|
||||
if (
|
||||
!startMouse ||
|
||||
!startPos ||
|
||||
@@ -1371,6 +1454,7 @@ export const WebGLMap = observer(() => {
|
||||
};
|
||||
|
||||
const onPointerUp = (e: PointerEvent) => {
|
||||
// Отслеживаем активность пользователя
|
||||
updateUserActivity();
|
||||
|
||||
canvas.releasePointerCapture(e.pointerId);
|
||||
@@ -1390,6 +1474,7 @@ export const WebGLMap = observer(() => {
|
||||
};
|
||||
|
||||
const onPointerCancel = (e: PointerEvent) => {
|
||||
// Handle pointer cancellation (e.g., when touch is interrupted)
|
||||
updateUserActivity();
|
||||
canvas.releasePointerCapture(e.pointerId);
|
||||
activePointers.delete(e.pointerId);
|
||||
@@ -1403,6 +1488,7 @@ export const WebGLMap = observer(() => {
|
||||
const onWheel = (e: WheelEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
// Отслеживаем активность пользователя
|
||||
updateUserActivity();
|
||||
if (isAutoMode) {
|
||||
setIsAutoMode(false);
|
||||
@@ -1410,6 +1496,7 @@ export const WebGLMap = observer(() => {
|
||||
cameraAnimationStore.stopAnimation();
|
||||
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
// Convert mouse coordinates from CSS pixels to physical canvas pixels
|
||||
const mouseX =
|
||||
(e.clientX - rect.left) * (canvas.width / canvas.clientWidth);
|
||||
const mouseY =
|
||||
@@ -1516,6 +1603,7 @@ export const WebGLMap = observer(() => {
|
||||
const sy = (ry * scale + position.y) / dpr;
|
||||
const size = 30;
|
||||
|
||||
// Обработчик клика для выбора достопримечательности
|
||||
const handleSightClick = () => {
|
||||
const {
|
||||
setSelectedSightId,
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,6 @@ import { useState, useEffect } from "react";
|
||||
import {
|
||||
Stack,
|
||||
Typography,
|
||||
Button,
|
||||
Accordion,
|
||||
AccordionSummary,
|
||||
AccordionDetails,
|
||||
@@ -16,11 +15,21 @@ import {
|
||||
TableRow,
|
||||
Paper,
|
||||
TableBody,
|
||||
Checkbox,
|
||||
FormControlLabel,
|
||||
Tabs,
|
||||
Tab,
|
||||
Box,
|
||||
} from "@mui/material";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
||||
|
||||
import { authInstance, languageStore, selectedCityStore } from "@shared";
|
||||
import {
|
||||
AnimatedCircleButton,
|
||||
authInstance,
|
||||
languageStore,
|
||||
selectedCityStore,
|
||||
} from "@shared";
|
||||
|
||||
type Field<T> = {
|
||||
label: string;
|
||||
@@ -93,6 +102,16 @@ const LinkedStationsContentsInner = <
|
||||
const [selectedItemId, setSelectedItemId] = useState<number | null>(null);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [activeTab, setActiveTab] = useState(0);
|
||||
const [selectedItems, setSelectedItems] = useState<Set<number>>(new Set());
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [selectedToDetach, setSelectedToDetach] = useState<Set<number>>(
|
||||
new Set()
|
||||
);
|
||||
const [isLinkingSingle, setIsLinkingSingle] = useState(false);
|
||||
const [isLinkingBulk, setIsLinkingBulk] = useState(false);
|
||||
const [isBulkDetaching, setIsBulkDetaching] = useState(false);
|
||||
const [detachingIds, setDetachingIds] = useState<Set<number>>(new Set());
|
||||
|
||||
useEffect(() => {}, [error]);
|
||||
|
||||
@@ -110,6 +129,11 @@ const LinkedStationsContentsInner = <
|
||||
})
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
const filteredAvailableItems = availableItems.filter((item) => {
|
||||
if (!searchQuery.trim()) return true;
|
||||
return String(item.name).toLowerCase().includes(searchQuery.toLowerCase());
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (updatedLinkedItems) {
|
||||
setLinkedItems(updatedLinkedItems);
|
||||
@@ -120,6 +144,18 @@ const LinkedStationsContentsInner = <
|
||||
setItemsParent?.(linkedItems);
|
||||
}, [linkedItems, setItemsParent]);
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedToDetach((prev) => {
|
||||
const updated = new Set<number>();
|
||||
linkedItems.forEach((item) => {
|
||||
if (prev.has(item.id)) {
|
||||
updated.add(item.id);
|
||||
}
|
||||
});
|
||||
return updated;
|
||||
});
|
||||
}, [linkedItems]);
|
||||
|
||||
const linkItem = () => {
|
||||
if (selectedItemId !== null) {
|
||||
setError(null);
|
||||
@@ -127,6 +163,7 @@ const LinkedStationsContentsInner = <
|
||||
station_id: selectedItemId,
|
||||
};
|
||||
|
||||
setIsLinkingSingle(true);
|
||||
authInstance
|
||||
.post(`/${parentResource}/${parentId}/${childResource}`, requestData)
|
||||
.then(() => {
|
||||
@@ -140,12 +177,20 @@ const LinkedStationsContentsInner = <
|
||||
.catch((error) => {
|
||||
console.error("Error linking station:", error);
|
||||
setError("Failed to link station");
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLinkingSingle(false);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const deleteItem = (itemId: number) => {
|
||||
setError(null);
|
||||
setDetachingIds((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.add(itemId);
|
||||
return next;
|
||||
});
|
||||
authInstance
|
||||
.delete(`/${parentResource}/${parentId}/${childResource}`, {
|
||||
data: { [`${childResource}_id`]: itemId },
|
||||
@@ -157,9 +202,119 @@ const LinkedStationsContentsInner = <
|
||||
.catch((error) => {
|
||||
console.error("Error deleting station:", error);
|
||||
setError("Failed to delete station");
|
||||
})
|
||||
.finally(() => {
|
||||
setDetachingIds((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.delete(itemId);
|
||||
return next;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const handleCheckboxChange = (itemId: number) => {
|
||||
const updated = new Set(selectedItems);
|
||||
if (updated.has(itemId)) {
|
||||
updated.delete(itemId);
|
||||
} else {
|
||||
updated.add(itemId);
|
||||
}
|
||||
setSelectedItems(updated);
|
||||
};
|
||||
|
||||
const handleBulkLink = () => {
|
||||
if (selectedItems.size === 0) return;
|
||||
setError(null);
|
||||
|
||||
setIsLinkingBulk(true);
|
||||
Promise.all(
|
||||
Array.from(selectedItems).map((id) =>
|
||||
authInstance.post(`/${parentResource}/${parentId}/${childResource}`, {
|
||||
station_id: id,
|
||||
})
|
||||
)
|
||||
)
|
||||
.then(() => {
|
||||
const newItems = allItems.filter((item) =>
|
||||
selectedItems.has(item.id)
|
||||
);
|
||||
setLinkedItems([...linkedItems, ...newItems]);
|
||||
setSelectedItems(new Set());
|
||||
onUpdate?.();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error bulk linking stations:", error);
|
||||
setError("Failed to link stations");
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLinkingBulk(false);
|
||||
});
|
||||
};
|
||||
|
||||
const toggleDetachSelection = (itemId: number) => {
|
||||
const updated = new Set(selectedToDetach);
|
||||
if (updated.has(itemId)) {
|
||||
updated.delete(itemId);
|
||||
} else {
|
||||
updated.add(itemId);
|
||||
}
|
||||
setSelectedToDetach(updated);
|
||||
};
|
||||
|
||||
const handleToggleAllDetach = (checked: boolean) => {
|
||||
if (!checked) {
|
||||
setSelectedToDetach(new Set());
|
||||
return;
|
||||
}
|
||||
setSelectedToDetach(new Set(linkedItems.map((item) => item.id)));
|
||||
};
|
||||
|
||||
const handleBulkDetach = () => {
|
||||
const idsToDetach = Array.from(selectedToDetach);
|
||||
if (idsToDetach.length === 0) return;
|
||||
setError(null);
|
||||
|
||||
setIsBulkDetaching(true);
|
||||
setDetachingIds((prev) => {
|
||||
const next = new Set(prev);
|
||||
idsToDetach.forEach((id) => next.add(id));
|
||||
return next;
|
||||
});
|
||||
|
||||
Promise.all(
|
||||
idsToDetach.map((itemId) =>
|
||||
authInstance.delete(`/${parentResource}/${parentId}/${childResource}`, {
|
||||
data: { [`${childResource}_id`]: itemId },
|
||||
})
|
||||
)
|
||||
)
|
||||
.then(() => {
|
||||
setLinkedItems(
|
||||
linkedItems.filter((item) => !idsToDetach.includes(item.id))
|
||||
);
|
||||
setSelectedToDetach(new Set());
|
||||
onUpdate?.();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error bulk deleting stations:", error);
|
||||
setError("Failed to delete stations");
|
||||
})
|
||||
.finally(() => {
|
||||
setDetachingIds((prev) => {
|
||||
const next = new Set(prev);
|
||||
idsToDetach.forEach((id) => next.delete(id));
|
||||
return next;
|
||||
});
|
||||
setIsBulkDetaching(false);
|
||||
});
|
||||
};
|
||||
|
||||
const allSelectedForDetach =
|
||||
linkedItems.length > 0 &&
|
||||
linkedItems.every((item) => selectedToDetach.has(item.id));
|
||||
const isIndeterminateDetach =
|
||||
selectedToDetach.size > 0 && !allSelectedForDetach;
|
||||
|
||||
useEffect(() => {
|
||||
if (parentId) {
|
||||
setIsLoading(true);
|
||||
@@ -203,6 +358,16 @@ const LinkedStationsContentsInner = <
|
||||
<Table sx={{ width: "100%" }}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
{type === "edit" && (
|
||||
<TableCell width="50px">
|
||||
<Checkbox
|
||||
size="small"
|
||||
checked={allSelectedForDetach}
|
||||
indeterminate={isIndeterminateDetach}
|
||||
onChange={(e) => handleToggleAllDetach(e.target.checked)}
|
||||
/>
|
||||
</TableCell>
|
||||
)}
|
||||
<TableCell key="id" width="60px">
|
||||
№
|
||||
</TableCell>
|
||||
@@ -218,6 +383,15 @@ const LinkedStationsContentsInner = <
|
||||
<TableBody>
|
||||
{linkedItems.map((item, index) => (
|
||||
<TableRow key={item.id} hover>
|
||||
{type === "edit" && (
|
||||
<TableCell>
|
||||
<Checkbox
|
||||
size="small"
|
||||
checked={selectedToDetach.has(item.id)}
|
||||
onChange={() => toggleDetachSelection(item.id)}
|
||||
/>
|
||||
</TableCell>
|
||||
)}
|
||||
<TableCell>{index + 1}</TableCell>
|
||||
{fields.map((field, idx) => (
|
||||
<TableCell key={String(field.data) + String(idx)}>
|
||||
@@ -228,7 +402,7 @@ const LinkedStationsContentsInner = <
|
||||
))}
|
||||
{type === "edit" && (
|
||||
<TableCell>
|
||||
<Button
|
||||
<AnimatedCircleButton
|
||||
variant="outlined"
|
||||
color="error"
|
||||
size="small"
|
||||
@@ -236,9 +410,11 @@ const LinkedStationsContentsInner = <
|
||||
e.stopPropagation();
|
||||
deleteItem(item.id);
|
||||
}}
|
||||
disabled={detachingIds.has(item.id)}
|
||||
loading={detachingIds.has(item.id)}
|
||||
>
|
||||
Отвязать
|
||||
</Button>
|
||||
</AnimatedCircleButton>
|
||||
</TableCell>
|
||||
)}
|
||||
</TableRow>
|
||||
@@ -248,6 +424,20 @@ const LinkedStationsContentsInner = <
|
||||
</TableContainer>
|
||||
)}
|
||||
|
||||
{type === "edit" && linkedItems.length > 0 && (
|
||||
<Stack direction="row" gap={2} mt={2}>
|
||||
<AnimatedCircleButton
|
||||
variant="outlined"
|
||||
color="error"
|
||||
onClick={handleBulkDetach}
|
||||
disabled={selectedToDetach.size === 0 || isBulkDetaching}
|
||||
loading={isBulkDetaching}
|
||||
>
|
||||
Отвязать выбранные ({selectedToDetach.size})
|
||||
</AnimatedCircleButton>
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
{linkedItems.length === 0 && !isLoading && (
|
||||
<Typography color="textSecondary" textAlign="center" py={2}>
|
||||
Остановки не найдены
|
||||
@@ -256,19 +446,36 @@ const LinkedStationsContentsInner = <
|
||||
|
||||
{type === "edit" && !disableCreation && (
|
||||
<Stack gap={2} mt={2}>
|
||||
<Typography variant="subtitle1">Добавить остановку</Typography>
|
||||
<Typography variant="subtitle1">Добавить остановки</Typography>
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onChange={(_, value) => setActiveTab(value)}
|
||||
variant="fullWidth"
|
||||
>
|
||||
<Tab label="По одной" />
|
||||
<Tab label="Массово" />
|
||||
</Tabs>
|
||||
|
||||
<Box sx={{ mt: 1 }}>
|
||||
{activeTab === 0 && (
|
||||
<Stack gap={2}>
|
||||
<Autocomplete
|
||||
fullWidth
|
||||
value={
|
||||
availableItems?.find((item) => item.id === selectedItemId) || null
|
||||
availableItems?.find((item) => item.id === selectedItemId) ||
|
||||
null
|
||||
}
|
||||
onChange={(_, newValue) =>
|
||||
setSelectedItemId(newValue?.id || null)
|
||||
}
|
||||
onChange={(_, newValue) => setSelectedItemId(newValue?.id || null)}
|
||||
options={availableItems}
|
||||
getOptionLabel={(item) => String(item.name)}
|
||||
renderInput={(params) => (
|
||||
<TextField {...params} label="Выберите остановку" fullWidth />
|
||||
)}
|
||||
isOptionEqualToValue={(option, value) => option.id === value?.id}
|
||||
isOptionEqualToValue={(option, value) =>
|
||||
option.id === value?.id
|
||||
}
|
||||
filterOptions={(options, { inputValue }) => {
|
||||
const searchWords = inputValue
|
||||
.toLowerCase()
|
||||
@@ -290,14 +497,76 @@ const LinkedStationsContentsInner = <
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button
|
||||
<AnimatedCircleButton
|
||||
variant="contained"
|
||||
onClick={linkItem}
|
||||
disabled={!selectedItemId}
|
||||
disabled={!selectedItemId || isLinkingSingle}
|
||||
loading={isLinkingSingle}
|
||||
sx={{ alignSelf: "flex-start" }}
|
||||
>
|
||||
Добавить
|
||||
</Button>
|
||||
</AnimatedCircleButton>
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
{activeTab === 1 && (
|
||||
<Stack gap={2}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Поиск остановок"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
placeholder="Введите название остановки..."
|
||||
size="small"
|
||||
/>
|
||||
|
||||
<Paper sx={{ maxHeight: 300, overflow: "auto", p: 2 }}>
|
||||
<Stack gap={1}>
|
||||
{filteredAvailableItems.map((item) => (
|
||||
<FormControlLabel
|
||||
key={item.id}
|
||||
control={
|
||||
<Checkbox
|
||||
checked={selectedItems.has(item.id)}
|
||||
onChange={() => handleCheckboxChange(item.id)}
|
||||
size="small"
|
||||
/>
|
||||
}
|
||||
label={String(item.name)}
|
||||
sx={{
|
||||
margin: 0,
|
||||
"& .MuiFormControlLabel-label": {
|
||||
fontSize: "0.9rem",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
{filteredAvailableItems.length === 0 && (
|
||||
<Typography
|
||||
color="textSecondary"
|
||||
textAlign="center"
|
||||
py={1}
|
||||
>
|
||||
{searchQuery.trim()
|
||||
? "Остановки не найдены"
|
||||
: "Нет доступных остановок"}
|
||||
</Typography>
|
||||
)}
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
<AnimatedCircleButton
|
||||
variant="contained"
|
||||
onClick={handleBulkLink}
|
||||
disabled={selectedItems.size === 0 || isLinkingBulk}
|
||||
loading={isLinkingBulk}
|
||||
sx={{ alignSelf: "flex-start" }}
|
||||
>
|
||||
Добавить выбранные ({selectedItems.size})
|
||||
</AnimatedCircleButton>
|
||||
</Stack>
|
||||
)}
|
||||
</Box>
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ import { useState, useEffect } from "react";
|
||||
import {
|
||||
Stack,
|
||||
Typography,
|
||||
Button,
|
||||
Accordion,
|
||||
AccordionSummary,
|
||||
AccordionDetails,
|
||||
@@ -16,11 +15,21 @@ import {
|
||||
TableRow,
|
||||
Paper,
|
||||
TableBody,
|
||||
Checkbox,
|
||||
FormControlLabel,
|
||||
Tabs,
|
||||
Tab,
|
||||
Box,
|
||||
} from "@mui/material";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
||||
|
||||
import { authInstance, languageStore, selectedCityStore } from "@shared";
|
||||
import {
|
||||
AnimatedCircleButton,
|
||||
authInstance,
|
||||
languageStore,
|
||||
selectedCityStore,
|
||||
} from "@shared";
|
||||
|
||||
type Field<T> = {
|
||||
label: string;
|
||||
@@ -93,6 +102,16 @@ const LinkedSightsContentsInner = <
|
||||
const [selectedItemId, setSelectedItemId] = useState<number | null>(null);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [activeTab, setActiveTab] = useState(0);
|
||||
const [selectedItems, setSelectedItems] = useState<Set<number>>(new Set());
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [selectedToDetach, setSelectedToDetach] = useState<Set<number>>(
|
||||
new Set()
|
||||
);
|
||||
const [isLinkingSingle, setIsLinkingSingle] = useState(false);
|
||||
const [isLinkingBulk, setIsLinkingBulk] = useState(false);
|
||||
const [isBulkDetaching, setIsBulkDetaching] = useState(false);
|
||||
const [detachingIds, setDetachingIds] = useState<Set<number>>(new Set());
|
||||
|
||||
useEffect(() => {}, [error]);
|
||||
|
||||
@@ -111,6 +130,11 @@ const LinkedSightsContentsInner = <
|
||||
})
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
const filteredAvailableItems = availableItems.filter((item) => {
|
||||
if (!searchQuery.trim()) return true;
|
||||
return String(item.name).toLowerCase().includes(searchQuery.toLowerCase());
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (updatedLinkedItems) {
|
||||
setLinkedItems(updatedLinkedItems);
|
||||
@@ -121,6 +145,18 @@ const LinkedSightsContentsInner = <
|
||||
setItemsParent?.(linkedItems);
|
||||
}, [linkedItems, setItemsParent]);
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedToDetach((prev) => {
|
||||
const updated = new Set<number>();
|
||||
linkedItems.forEach((item) => {
|
||||
if (prev.has(item.id)) {
|
||||
updated.add(item.id);
|
||||
}
|
||||
});
|
||||
return updated;
|
||||
});
|
||||
}, [linkedItems]);
|
||||
|
||||
const linkItem = () => {
|
||||
if (selectedItemId !== null) {
|
||||
setError(null);
|
||||
@@ -128,6 +164,7 @@ const LinkedSightsContentsInner = <
|
||||
sight_id: selectedItemId,
|
||||
};
|
||||
|
||||
setIsLinkingSingle(true);
|
||||
authInstance
|
||||
.post(`/${parentResource}/${parentId}/${childResource}`, requestData)
|
||||
.then(() => {
|
||||
@@ -141,12 +178,20 @@ const LinkedSightsContentsInner = <
|
||||
.catch((error) => {
|
||||
console.error("Error linking sight:", error);
|
||||
setError("Failed to link sight");
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLinkingSingle(false);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const deleteItem = (itemId: number) => {
|
||||
setError(null);
|
||||
setDetachingIds((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.add(itemId);
|
||||
return next;
|
||||
});
|
||||
authInstance
|
||||
.delete(`/${parentResource}/${parentId}/${childResource}`, {
|
||||
data: { [`${childResource}_id`]: itemId },
|
||||
@@ -158,9 +203,119 @@ const LinkedSightsContentsInner = <
|
||||
.catch((error) => {
|
||||
console.error("Error deleting sight:", error);
|
||||
setError("Failed to delete sight");
|
||||
})
|
||||
.finally(() => {
|
||||
setDetachingIds((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.delete(itemId);
|
||||
return next;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const handleCheckboxChange = (itemId: number) => {
|
||||
const updated = new Set(selectedItems);
|
||||
if (updated.has(itemId)) {
|
||||
updated.delete(itemId);
|
||||
} else {
|
||||
updated.add(itemId);
|
||||
}
|
||||
setSelectedItems(updated);
|
||||
};
|
||||
|
||||
const handleBulkLink = () => {
|
||||
if (selectedItems.size === 0) return;
|
||||
setError(null);
|
||||
|
||||
setIsLinkingBulk(true);
|
||||
Promise.all(
|
||||
Array.from(selectedItems).map((id) =>
|
||||
authInstance.post(`/${parentResource}/${parentId}/${childResource}`, {
|
||||
sight_id: id,
|
||||
})
|
||||
)
|
||||
)
|
||||
.then(() => {
|
||||
const newItems = allItems.filter((item) =>
|
||||
selectedItems.has(item.id)
|
||||
);
|
||||
setLinkedItems([...linkedItems, ...newItems]);
|
||||
setSelectedItems(new Set());
|
||||
onUpdate?.();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error bulk linking sights:", error);
|
||||
setError("Failed to link sights");
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLinkingBulk(false);
|
||||
});
|
||||
};
|
||||
|
||||
const toggleDetachSelection = (itemId: number) => {
|
||||
const updated = new Set(selectedToDetach);
|
||||
if (updated.has(itemId)) {
|
||||
updated.delete(itemId);
|
||||
} else {
|
||||
updated.add(itemId);
|
||||
}
|
||||
setSelectedToDetach(updated);
|
||||
};
|
||||
|
||||
const handleToggleAllDetach = (checked: boolean) => {
|
||||
if (!checked) {
|
||||
setSelectedToDetach(new Set());
|
||||
return;
|
||||
}
|
||||
setSelectedToDetach(new Set(linkedItems.map((item) => item.id)));
|
||||
};
|
||||
|
||||
const handleBulkDetach = () => {
|
||||
const idsToDetach = Array.from(selectedToDetach);
|
||||
if (idsToDetach.length === 0) return;
|
||||
setError(null);
|
||||
|
||||
setIsBulkDetaching(true);
|
||||
setDetachingIds((prev) => {
|
||||
const next = new Set(prev);
|
||||
idsToDetach.forEach((id) => next.add(id));
|
||||
return next;
|
||||
});
|
||||
|
||||
Promise.all(
|
||||
idsToDetach.map((itemId) =>
|
||||
authInstance.delete(`/${parentResource}/${parentId}/${childResource}`, {
|
||||
data: { [`${childResource}_id`]: itemId },
|
||||
})
|
||||
)
|
||||
)
|
||||
.then(() => {
|
||||
setLinkedItems(
|
||||
linkedItems.filter((item) => !idsToDetach.includes(item.id))
|
||||
);
|
||||
setSelectedToDetach(new Set());
|
||||
onUpdate?.();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error bulk deleting sights:", error);
|
||||
setError("Failed to delete sights");
|
||||
})
|
||||
.finally(() => {
|
||||
setDetachingIds((prev) => {
|
||||
const next = new Set(prev);
|
||||
idsToDetach.forEach((id) => next.delete(id));
|
||||
return next;
|
||||
});
|
||||
setIsBulkDetaching(false);
|
||||
});
|
||||
};
|
||||
|
||||
const allSelectedForDetach =
|
||||
linkedItems.length > 0 &&
|
||||
linkedItems.every((item) => selectedToDetach.has(item.id));
|
||||
const isIndeterminateDetach =
|
||||
selectedToDetach.size > 0 && !allSelectedForDetach;
|
||||
|
||||
useEffect(() => {
|
||||
if (parentId) {
|
||||
setIsLoading(true);
|
||||
@@ -204,6 +359,16 @@ const LinkedSightsContentsInner = <
|
||||
<Table sx={{ width: "100%" }}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
{type === "edit" && (
|
||||
<TableCell width="50px">
|
||||
<Checkbox
|
||||
size="small"
|
||||
checked={allSelectedForDetach}
|
||||
indeterminate={isIndeterminateDetach}
|
||||
onChange={(e) => handleToggleAllDetach(e.target.checked)}
|
||||
/>
|
||||
</TableCell>
|
||||
)}
|
||||
<TableCell key="id" width="60px">
|
||||
№
|
||||
</TableCell>
|
||||
@@ -219,6 +384,15 @@ const LinkedSightsContentsInner = <
|
||||
<TableBody>
|
||||
{linkedItems.map((item, index) => (
|
||||
<TableRow key={item.id} hover>
|
||||
{type === "edit" && (
|
||||
<TableCell>
|
||||
<Checkbox
|
||||
size="small"
|
||||
checked={selectedToDetach.has(item.id)}
|
||||
onChange={() => toggleDetachSelection(item.id)}
|
||||
/>
|
||||
</TableCell>
|
||||
)}
|
||||
<TableCell>{index + 1}</TableCell>
|
||||
{fields.map((field, idx) => (
|
||||
<TableCell key={String(field.data) + String(idx)}>
|
||||
@@ -229,7 +403,7 @@ const LinkedSightsContentsInner = <
|
||||
))}
|
||||
{type === "edit" && (
|
||||
<TableCell>
|
||||
<Button
|
||||
<AnimatedCircleButton
|
||||
variant="outlined"
|
||||
color="error"
|
||||
size="small"
|
||||
@@ -237,9 +411,11 @@ const LinkedSightsContentsInner = <
|
||||
e.stopPropagation();
|
||||
deleteItem(item.id);
|
||||
}}
|
||||
disabled={detachingIds.has(item.id)}
|
||||
loading={detachingIds.has(item.id)}
|
||||
>
|
||||
Отвязать
|
||||
</Button>
|
||||
</AnimatedCircleButton>
|
||||
</TableCell>
|
||||
)}
|
||||
</TableRow>
|
||||
@@ -249,6 +425,20 @@ const LinkedSightsContentsInner = <
|
||||
</TableContainer>
|
||||
)}
|
||||
|
||||
{type === "edit" && linkedItems.length > 0 && (
|
||||
<Stack direction="row" gap={2} mt={2}>
|
||||
<AnimatedCircleButton
|
||||
variant="outlined"
|
||||
color="error"
|
||||
onClick={handleBulkDetach}
|
||||
disabled={selectedToDetach.size === 0 || isBulkDetaching}
|
||||
loading={isBulkDetaching}
|
||||
>
|
||||
Отвязать выбранные ({selectedToDetach.size})
|
||||
</AnimatedCircleButton>
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
{linkedItems.length === 0 && !isLoading && (
|
||||
<Typography color="textSecondary" textAlign="center" py={2}>
|
||||
Достопримечательности не найдены
|
||||
@@ -258,14 +448,29 @@ const LinkedSightsContentsInner = <
|
||||
{type === "edit" && !disableCreation && (
|
||||
<Stack gap={2} mt={2}>
|
||||
<Typography variant="subtitle1">
|
||||
Добавить достопримечательность
|
||||
Добавить достопримечательности
|
||||
</Typography>
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onChange={(_, value) => setActiveTab(value)}
|
||||
variant="fullWidth"
|
||||
>
|
||||
<Tab label="По одной" />
|
||||
<Tab label="Массово" />
|
||||
</Tabs>
|
||||
|
||||
<Box sx={{ mt: 1 }}>
|
||||
{activeTab === 0 && (
|
||||
<Stack gap={2}>
|
||||
<Autocomplete
|
||||
fullWidth
|
||||
value={
|
||||
availableItems?.find((item) => item.id === selectedItemId) || null
|
||||
availableItems?.find((item) => item.id === selectedItemId) ||
|
||||
null
|
||||
}
|
||||
onChange={(_, newValue) =>
|
||||
setSelectedItemId(newValue?.id || null)
|
||||
}
|
||||
onChange={(_, newValue) => setSelectedItemId(newValue?.id || null)}
|
||||
options={availableItems}
|
||||
getOptionLabel={(item) => String(item.name)}
|
||||
renderInput={(params) => (
|
||||
@@ -275,7 +480,9 @@ const LinkedSightsContentsInner = <
|
||||
fullWidth
|
||||
/>
|
||||
)}
|
||||
isOptionEqualToValue={(option, value) => option.id === value?.id}
|
||||
isOptionEqualToValue={(option, value) =>
|
||||
option.id === value?.id
|
||||
}
|
||||
filterOptions={(options, { inputValue }) => {
|
||||
const searchWords = inputValue
|
||||
.toLowerCase()
|
||||
@@ -297,14 +504,76 @@ const LinkedSightsContentsInner = <
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button
|
||||
<AnimatedCircleButton
|
||||
variant="contained"
|
||||
onClick={linkItem}
|
||||
disabled={!selectedItemId}
|
||||
disabled={!selectedItemId || isLinkingSingle}
|
||||
loading={isLinkingSingle}
|
||||
sx={{ alignSelf: "flex-start" }}
|
||||
>
|
||||
Добавить
|
||||
</Button>
|
||||
</AnimatedCircleButton>
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
{activeTab === 1 && (
|
||||
<Stack gap={2}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Поиск достопримечательностей"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
placeholder="Введите название..."
|
||||
size="small"
|
||||
/>
|
||||
|
||||
<Paper sx={{ maxHeight: 300, overflow: "auto", p: 2 }}>
|
||||
<Stack gap={1}>
|
||||
{filteredAvailableItems.map((item) => (
|
||||
<FormControlLabel
|
||||
key={item.id}
|
||||
control={
|
||||
<Checkbox
|
||||
checked={selectedItems.has(item.id)}
|
||||
onChange={() => handleCheckboxChange(item.id)}
|
||||
size="small"
|
||||
/>
|
||||
}
|
||||
label={String(item.name)}
|
||||
sx={{
|
||||
margin: 0,
|
||||
"& .MuiFormControlLabel-label": {
|
||||
fontSize: "0.9rem",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
{filteredAvailableItems.length === 0 && (
|
||||
<Typography
|
||||
color="textSecondary"
|
||||
textAlign="center"
|
||||
py={1}
|
||||
>
|
||||
{searchQuery.trim()
|
||||
? "Достопримечательности не найдены"
|
||||
: "Нет доступных достопримечательностей"}
|
||||
</Typography>
|
||||
)}
|
||||
</Stack>
|
||||
</Paper>
|
||||
|
||||
<AnimatedCircleButton
|
||||
variant="contained"
|
||||
onClick={handleBulkLink}
|
||||
disabled={selectedItems.size === 0 || isLinkingBulk}
|
||||
loading={isLinkingBulk}
|
||||
sx={{ alignSelf: "flex-start" }}
|
||||
>
|
||||
Добавить выбранные ({selectedItems.size})
|
||||
</AnimatedCircleButton>
|
||||
</Stack>
|
||||
)}
|
||||
</Box>
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
|
||||
@@ -6,10 +6,24 @@ class LanguageStore {
|
||||
|
||||
constructor() {
|
||||
makeAutoObservable(this);
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
const storedLanguage = window.localStorage.getItem("appLanguage");
|
||||
if (
|
||||
storedLanguage &&
|
||||
["ru", "en", "zh"].includes(storedLanguage.toLowerCase())
|
||||
) {
|
||||
this.language = storedLanguage.toLowerCase() as Language;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setLanguage = (language: Language) => {
|
||||
this.language = language;
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
window.localStorage.setItem("appLanguage", language);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
171
src/shared/ui/AnimatedCircleButton.tsx
Normal file
171
src/shared/ui/AnimatedCircleButton.tsx
Normal file
@@ -0,0 +1,171 @@
|
||||
import { forwardRef } from "react";
|
||||
import { Button, ButtonProps, CircularProgress } from "@mui/material";
|
||||
import { alpha, keyframes, styled } from "@mui/material/styles";
|
||||
import type { Theme } from "@mui/material/styles";
|
||||
|
||||
type AnimatedCircleButtonProps = ButtonProps & {
|
||||
disableAnimation?: boolean;
|
||||
loading?: boolean;
|
||||
};
|
||||
|
||||
type StyledButtonProps = AnimatedCircleButtonProps & { theme: Theme };
|
||||
|
||||
const loadingPulse = keyframes`
|
||||
0% {
|
||||
transform: translate(-50%, -50%) scale(0.6);
|
||||
opacity: 0.35;
|
||||
}
|
||||
50% {
|
||||
transform: translate(-50%, -50%) scale(1.45);
|
||||
opacity: 0.15;
|
||||
}
|
||||
100% {
|
||||
transform: translate(-50%, -50%) scale(0.6);
|
||||
opacity: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledButton = styled(Button, {
|
||||
shouldForwardProp: (prop) =>
|
||||
prop !== "disableAnimation" && prop !== "loading",
|
||||
})<AnimatedCircleButtonProps>((props: StyledButtonProps) => {
|
||||
const {
|
||||
theme,
|
||||
disableAnimation = false,
|
||||
color,
|
||||
variant = "text",
|
||||
disabled = false,
|
||||
loading = false,
|
||||
} = props;
|
||||
|
||||
const shouldAnimate = !disableAnimation && (!disabled || loading);
|
||||
const pointerBlocked = loading;
|
||||
|
||||
const paletteMainMap: Record<string, string> = {
|
||||
primary: theme.palette.primary.main,
|
||||
secondary: theme.palette.secondary.main,
|
||||
error: theme.palette.error.main,
|
||||
warning: theme.palette.warning.main,
|
||||
info: theme.palette.info.main,
|
||||
success: theme.palette.success.main,
|
||||
inherit: theme.palette.primary.main,
|
||||
};
|
||||
|
||||
const paletteMain =
|
||||
(color && paletteMainMap[String(color)]) ?? theme.palette.primary.main;
|
||||
|
||||
const pulseColor =
|
||||
variant === "outlined" || variant === "text"
|
||||
? alpha(paletteMain, 0.18)
|
||||
: alpha(paletteMain, 0.3);
|
||||
|
||||
return {
|
||||
position: "relative",
|
||||
overflow: "hidden",
|
||||
borderRadius: 5,
|
||||
zIndex: 0,
|
||||
transition: "transform 0.2s ease, box-shadow 0.2s ease",
|
||||
pointerEvents: pointerBlocked ? "none" : undefined,
|
||||
"&::after": shouldAnimate
|
||||
? {
|
||||
content: '""',
|
||||
position: "absolute",
|
||||
width: "12px",
|
||||
height: "12px",
|
||||
backgroundColor: pulseColor,
|
||||
borderRadius: "50%",
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
pointerEvents: "none",
|
||||
zIndex: 0,
|
||||
...(loading
|
||||
? {
|
||||
opacity: 0.35,
|
||||
transform: "translate(-50%, -50%) scale(0.6)",
|
||||
animation: `${loadingPulse} 1.2s ease-in-out infinite`,
|
||||
}
|
||||
: {
|
||||
opacity: 0,
|
||||
transform: "translate(-50%, -50%) scale(0)",
|
||||
transition: "transform 0.45s ease, opacity 0.45s ease",
|
||||
}),
|
||||
}
|
||||
: {},
|
||||
...(loading
|
||||
? {}
|
||||
: {
|
||||
"&:hover": {
|
||||
transform: "translateY(-1px)",
|
||||
boxShadow: theme.shadows[4],
|
||||
},
|
||||
"&:hover::after": shouldAnimate
|
||||
? {
|
||||
transform: "translate(-50%, -50%) scale(15)",
|
||||
opacity: 1,
|
||||
}
|
||||
: {},
|
||||
"&:active": {
|
||||
transform: "translateY(0)",
|
||||
boxShadow: theme.shadows[2],
|
||||
},
|
||||
"&:active::after": shouldAnimate
|
||||
? {
|
||||
transform: "translate(-50%, -50%) scale(18)",
|
||||
opacity: 0.4,
|
||||
}
|
||||
: {},
|
||||
}),
|
||||
"&.Mui-disabled": {
|
||||
boxShadow: "none",
|
||||
transform: "none",
|
||||
...(loading && shouldAnimate
|
||||
? {}
|
||||
: {
|
||||
"&::after": {
|
||||
opacity: 0,
|
||||
},
|
||||
}),
|
||||
},
|
||||
...(disabled && {
|
||||
boxShadow: "none",
|
||||
transform: "none",
|
||||
}),
|
||||
"& > *": {
|
||||
position: "relative",
|
||||
zIndex: 1,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const AnimatedCircleButton = forwardRef<
|
||||
HTMLButtonElement,
|
||||
AnimatedCircleButtonProps
|
||||
>((props, ref) => {
|
||||
const {
|
||||
loading = false,
|
||||
disabled,
|
||||
children,
|
||||
startIcon,
|
||||
endIcon,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
const effectiveStartIcon = loading ? (
|
||||
<CircularProgress size={16} color="inherit" />
|
||||
) : (
|
||||
startIcon
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledButton
|
||||
ref={ref}
|
||||
loading={loading}
|
||||
disabled={loading ? true : disabled}
|
||||
startIcon={effectiveStartIcon}
|
||||
endIcon={loading ? undefined : endIcon}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</StyledButton>
|
||||
);
|
||||
});
|
||||
@@ -2,3 +2,4 @@ export * from "./TabPanel";
|
||||
export * from "./BackButton";
|
||||
export * from "./Modal";
|
||||
export * from "./CoordinatesInput";
|
||||
export * from "./AnimatedCircleButton";
|
||||
|
||||
File diff suppressed because one or more lines are too long
474
yarn.lock
474
yarn.lock
@@ -16,7 +16,7 @@
|
||||
resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz"
|
||||
integrity sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==
|
||||
|
||||
"@babel/core@^7.0.0", "@babel/core@^7.0.0-0", "@babel/core@^7.21.3", "@babel/core@^7.28.0":
|
||||
"@babel/core@^7.21.3", "@babel/core@^7.28.0":
|
||||
version "7.28.5"
|
||||
resolved "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz"
|
||||
integrity sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==
|
||||
@@ -170,6 +170,28 @@
|
||||
resolved "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.12.0.tgz"
|
||||
integrity sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==
|
||||
|
||||
"@emnapi/core@^1.5.0":
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.7.0.tgz#135de4e8858763989112281bdf38ca02439db7c3"
|
||||
integrity sha512-pJdKGq/1iquWYtv1RRSljZklxHCOCAJFJrImO5ZLKPJVJlVUcs8yFwNQlqS0Lo8xT1VAXXTCZocF9n26FWEKsw==
|
||||
dependencies:
|
||||
"@emnapi/wasi-threads" "1.1.0"
|
||||
tslib "^2.4.0"
|
||||
|
||||
"@emnapi/runtime@^1.5.0":
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.7.0.tgz#d7ef3832df8564fe5903bf0567aedbd19538ecbe"
|
||||
integrity sha512-oAYoQnCYaQZKVS53Fq23ceWMRxq5EhQsE0x0RdQ55jT7wagMu5k+fS39v1fiSLrtrLQlXwVINenqhLMtTrV/1Q==
|
||||
dependencies:
|
||||
tslib "^2.4.0"
|
||||
|
||||
"@emnapi/wasi-threads@1.1.0", "@emnapi/wasi-threads@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz#60b2102fddc9ccb78607e4a3cf8403ea69be41bf"
|
||||
integrity sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==
|
||||
dependencies:
|
||||
tslib "^2.4.0"
|
||||
|
||||
"@emotion/babel-plugin@^11.13.5":
|
||||
version "11.13.5"
|
||||
resolved "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz"
|
||||
@@ -215,7 +237,7 @@
|
||||
resolved "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz"
|
||||
integrity sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==
|
||||
|
||||
"@emotion/react@^11.0.0-rc.0", "@emotion/react@^11.14.0", "@emotion/react@^11.4.1", "@emotion/react@^11.5.0", "@emotion/react@^11.9.0":
|
||||
"@emotion/react@^11.14.0":
|
||||
version "11.14.0"
|
||||
resolved "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz"
|
||||
integrity sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==
|
||||
@@ -245,7 +267,7 @@
|
||||
resolved "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz"
|
||||
integrity sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==
|
||||
|
||||
"@emotion/styled@^11.14.0", "@emotion/styled@^11.3.0", "@emotion/styled@^11.8.1":
|
||||
"@emotion/styled@^11.14.0":
|
||||
version "11.14.1"
|
||||
resolved "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz"
|
||||
integrity sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==
|
||||
@@ -277,11 +299,136 @@
|
||||
resolved "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz"
|
||||
integrity sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==
|
||||
|
||||
"@esbuild/aix-ppc64@0.25.11":
|
||||
version "0.25.11"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.11.tgz#2ae33300598132cc4cf580dbbb28d30fed3c5c49"
|
||||
integrity sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==
|
||||
|
||||
"@esbuild/android-arm64@0.25.11":
|
||||
version "0.25.11"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.25.11.tgz#927708b3db5d739d6cb7709136924cc81bec9b03"
|
||||
integrity sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==
|
||||
|
||||
"@esbuild/android-arm@0.25.11":
|
||||
version "0.25.11"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.25.11.tgz#571f94e7f4068957ec4c2cfb907deae3d01b55ae"
|
||||
integrity sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==
|
||||
|
||||
"@esbuild/android-x64@0.25.11":
|
||||
version "0.25.11"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.25.11.tgz#8a3bf5cae6c560c7ececa3150b2bde76e0fb81e6"
|
||||
integrity sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==
|
||||
|
||||
"@esbuild/darwin-arm64@0.25.11":
|
||||
version "0.25.11"
|
||||
resolved "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.11.tgz"
|
||||
integrity sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==
|
||||
|
||||
"@esbuild/darwin-x64@0.25.11":
|
||||
version "0.25.11"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.25.11.tgz#70f5e925a30c8309f1294d407a5e5e002e0315fe"
|
||||
integrity sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==
|
||||
|
||||
"@esbuild/freebsd-arm64@0.25.11":
|
||||
version "0.25.11"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.11.tgz#4ec1db687c5b2b78b44148025da9632397553e8a"
|
||||
integrity sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==
|
||||
|
||||
"@esbuild/freebsd-x64@0.25.11":
|
||||
version "0.25.11"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.11.tgz#4c81abd1b142f1e9acfef8c5153d438ca53f44bb"
|
||||
integrity sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==
|
||||
|
||||
"@esbuild/linux-arm64@0.25.11":
|
||||
version "0.25.11"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.25.11.tgz#69517a111acfc2b93aa0fb5eaeb834c0202ccda5"
|
||||
integrity sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==
|
||||
|
||||
"@esbuild/linux-arm@0.25.11":
|
||||
version "0.25.11"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.25.11.tgz#58dac26eae2dba0fac5405052b9002dac088d38f"
|
||||
integrity sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==
|
||||
|
||||
"@esbuild/linux-ia32@0.25.11":
|
||||
version "0.25.11"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.25.11.tgz#b89d4efe9bdad46ba944f0f3b8ddd40834268c2b"
|
||||
integrity sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==
|
||||
|
||||
"@esbuild/linux-loong64@0.25.11":
|
||||
version "0.25.11"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.25.11.tgz#11f603cb60ad14392c3f5c94d64b3cc8b630fbeb"
|
||||
integrity sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==
|
||||
|
||||
"@esbuild/linux-mips64el@0.25.11":
|
||||
version "0.25.11"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.11.tgz#b7d447ff0676b8ab247d69dac40a5cf08e5eeaf5"
|
||||
integrity sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==
|
||||
|
||||
"@esbuild/linux-ppc64@0.25.11":
|
||||
version "0.25.11"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.11.tgz#b3a28ed7cc252a61b07ff7c8fd8a984ffd3a2f74"
|
||||
integrity sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==
|
||||
|
||||
"@esbuild/linux-riscv64@0.25.11":
|
||||
version "0.25.11"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.11.tgz#ce75b08f7d871a75edcf4d2125f50b21dc9dc273"
|
||||
integrity sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==
|
||||
|
||||
"@esbuild/linux-s390x@0.25.11":
|
||||
version "0.25.11"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.25.11.tgz#cd08f6c73b6b6ff9ccdaabbd3ff6ad3dca99c263"
|
||||
integrity sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==
|
||||
|
||||
"@esbuild/linux-x64@0.25.11":
|
||||
version "0.25.11"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.25.11.tgz#3c3718af31a95d8946ebd3c32bb1e699bdf74910"
|
||||
integrity sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==
|
||||
|
||||
"@esbuild/netbsd-arm64@0.25.11":
|
||||
version "0.25.11"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.11.tgz#b4c767082401e3a4e8595fe53c47cd7f097c8077"
|
||||
integrity sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==
|
||||
|
||||
"@esbuild/netbsd-x64@0.25.11":
|
||||
version "0.25.11"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.11.tgz#f2a930458ed2941d1f11ebc34b9c7d61f7a4d034"
|
||||
integrity sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==
|
||||
|
||||
"@esbuild/openbsd-arm64@0.25.11":
|
||||
version "0.25.11"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.11.tgz#b4ae93c75aec48bc1e8a0154957a05f0641f2dad"
|
||||
integrity sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==
|
||||
|
||||
"@esbuild/openbsd-x64@0.25.11":
|
||||
version "0.25.11"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.11.tgz#b42863959c8dcf9b01581522e40012d2c70045e2"
|
||||
integrity sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==
|
||||
|
||||
"@esbuild/openharmony-arm64@0.25.11":
|
||||
version "0.25.11"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.11.tgz#b2e717141c8fdf6bddd4010f0912e6b39e1640f1"
|
||||
integrity sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==
|
||||
|
||||
"@esbuild/sunos-x64@0.25.11":
|
||||
version "0.25.11"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.25.11.tgz#9fbea1febe8778927804828883ec0f6dd80eb244"
|
||||
integrity sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==
|
||||
|
||||
"@esbuild/win32-arm64@0.25.11":
|
||||
version "0.25.11"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.25.11.tgz#501539cedb24468336073383989a7323005a8935"
|
||||
integrity sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==
|
||||
|
||||
"@esbuild/win32-ia32@0.25.11":
|
||||
version "0.25.11"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.25.11.tgz#8ac7229aa82cef8f16ffb58f1176a973a7a15343"
|
||||
integrity sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==
|
||||
|
||||
"@esbuild/win32-x64@0.25.11":
|
||||
version "0.25.11"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.25.11.tgz#5ecda6f3fe138b7e456f4e429edde33c823f392f"
|
||||
integrity sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==
|
||||
|
||||
"@eslint-community/eslint-utils@^4.7.0", "@eslint-community/eslint-utils@^4.8.0":
|
||||
version "4.9.0"
|
||||
resolved "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz"
|
||||
@@ -332,7 +479,7 @@
|
||||
minimatch "^3.1.2"
|
||||
strip-json-comments "^3.1.1"
|
||||
|
||||
"@eslint/js@^9.25.0", "@eslint/js@9.38.0":
|
||||
"@eslint/js@9.38.0", "@eslint/js@^9.25.0":
|
||||
version "9.38.0"
|
||||
resolved "https://registry.npmjs.org/@eslint/js/-/js-9.38.0.tgz"
|
||||
integrity sha512-UZ1VpFvXf9J06YG9xQBdnzU+kthors6KjhMAl6f4gH4usHyh31rUf2DLGInT8RFYIReYXNSydgPY0V2LuWgl7A==
|
||||
@@ -442,7 +589,7 @@
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.28.4"
|
||||
|
||||
"@mui/material@^5.15.14 || ^6.0.0 || ^7.0.0", "@mui/material@^7.1.0", "@mui/material@^7.3.4":
|
||||
"@mui/material@^7.1.0":
|
||||
version "7.3.4"
|
||||
resolved "https://registry.npmjs.org/@mui/material/-/material-7.3.4.tgz"
|
||||
integrity sha512-gEQL9pbJZZHT7lYJBKQCS723v1MGys2IFc94COXbUIyCTWa+qC77a7hUax4Yjd5ggEm35dk4AyYABpKKWC4MLw==
|
||||
@@ -481,7 +628,7 @@
|
||||
csstype "^3.1.3"
|
||||
prop-types "^15.8.1"
|
||||
|
||||
"@mui/system@^5.15.14 || ^6.0.0 || ^7.0.0", "@mui/system@^7.3.3":
|
||||
"@mui/system@^7.3.3":
|
||||
version "7.3.3"
|
||||
resolved "https://registry.npmjs.org/@mui/system/-/system-7.3.3.tgz"
|
||||
integrity sha512-Lqq3emZr5IzRLKaHPuMaLBDVaGvxoh6z7HMWd1RPKawBM5uMRaQ4ImsmmgXWtwJdfZux5eugfDhXJUo2mliS8Q==
|
||||
@@ -546,6 +693,15 @@
|
||||
"@mui/utils" "^7.3.3"
|
||||
"@mui/x-internals" "8.14.0"
|
||||
|
||||
"@napi-rs/wasm-runtime@^1.0.7":
|
||||
version "1.0.7"
|
||||
resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.7.tgz#dcfea99a75f06209a235f3d941e3460a51e9b14c"
|
||||
integrity sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==
|
||||
dependencies:
|
||||
"@emnapi/core" "^1.5.0"
|
||||
"@emnapi/runtime" "^1.5.0"
|
||||
"@tybys/wasm-util" "^0.10.1"
|
||||
|
||||
"@nodelib/fs.scandir@2.1.5":
|
||||
version "2.1.5"
|
||||
resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz"
|
||||
@@ -554,7 +710,7 @@
|
||||
"@nodelib/fs.stat" "2.0.5"
|
||||
run-parallel "^1.1.9"
|
||||
|
||||
"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5":
|
||||
"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
|
||||
version "2.0.5"
|
||||
resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz"
|
||||
integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
|
||||
@@ -572,7 +728,7 @@
|
||||
resolved "https://registry.npmjs.org/@petamoriken/float16/-/float16-3.9.3.tgz"
|
||||
integrity sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g==
|
||||
|
||||
"@photo-sphere-viewer/core@^5.13.2", "@photo-sphere-viewer/core@>=5.13.1":
|
||||
"@photo-sphere-viewer/core@^5.13.2":
|
||||
version "5.14.0"
|
||||
resolved "https://registry.npmjs.org/@photo-sphere-viewer/core/-/core-5.14.0.tgz"
|
||||
integrity sha512-V0JeDSB1D2Q60Zqn7+0FPjq8gqbKEwuxMzNdTLydefkQugVztLvdZykO+4k5XTpweZ2QAWPH/QOI1xZbsdvR9A==
|
||||
@@ -624,7 +780,7 @@
|
||||
utility-types "^3.11.0"
|
||||
zustand "^5.0.1"
|
||||
|
||||
"@react-three/fiber@^9.0.0", "@react-three/fiber@^9.1.2":
|
||||
"@react-three/fiber@^9.1.2":
|
||||
version "9.4.0"
|
||||
resolved "https://registry.npmjs.org/@react-three/fiber/-/fiber-9.4.0.tgz"
|
||||
integrity sha512-k4iu1R6e5D54918V4sqmISUkI5OgTw3v7/sDRKEC632Wd5g2WBtUS5gyG63X0GJO/HZUj1tsjSXfyzwrUHZl1g==
|
||||
@@ -656,11 +812,116 @@
|
||||
estree-walker "^2.0.2"
|
||||
picomatch "^4.0.2"
|
||||
|
||||
"@rollup/rollup-android-arm-eabi@4.52.5":
|
||||
version "4.52.5"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.5.tgz#0f44a2f8668ed87b040b6fe659358ac9239da4db"
|
||||
integrity sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==
|
||||
|
||||
"@rollup/rollup-android-arm64@4.52.5":
|
||||
version "4.52.5"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.5.tgz#25b9a01deef6518a948431564c987bcb205274f5"
|
||||
integrity sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==
|
||||
|
||||
"@rollup/rollup-darwin-arm64@4.52.5":
|
||||
version "4.52.5"
|
||||
resolved "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.5.tgz"
|
||||
integrity sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==
|
||||
|
||||
"@rollup/rollup-darwin-x64@4.52.5":
|
||||
version "4.52.5"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.5.tgz#8e526417cd6f54daf1d0c04cf361160216581956"
|
||||
integrity sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==
|
||||
|
||||
"@rollup/rollup-freebsd-arm64@4.52.5":
|
||||
version "4.52.5"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.5.tgz#0e7027054493f3409b1f219a3eac5efd128ef899"
|
||||
integrity sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==
|
||||
|
||||
"@rollup/rollup-freebsd-x64@4.52.5":
|
||||
version "4.52.5"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.5.tgz#72b204a920139e9ec3d331bd9cfd9a0c248ccb10"
|
||||
integrity sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==
|
||||
|
||||
"@rollup/rollup-linux-arm-gnueabihf@4.52.5":
|
||||
version "4.52.5"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.5.tgz#ab1b522ebe5b7e06c99504cc38f6cd8b808ba41c"
|
||||
integrity sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==
|
||||
|
||||
"@rollup/rollup-linux-arm-musleabihf@4.52.5":
|
||||
version "4.52.5"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.5.tgz#f8cc30b638f1ee7e3d18eac24af47ea29d9beb00"
|
||||
integrity sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==
|
||||
|
||||
"@rollup/rollup-linux-arm64-gnu@4.52.5":
|
||||
version "4.52.5"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.5.tgz#7af37a9e85f25db59dc8214172907b7e146c12cc"
|
||||
integrity sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==
|
||||
|
||||
"@rollup/rollup-linux-arm64-musl@4.52.5":
|
||||
version "4.52.5"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.5.tgz#a623eb0d3617c03b7a73716eb85c6e37b776f7e0"
|
||||
integrity sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==
|
||||
|
||||
"@rollup/rollup-linux-loong64-gnu@4.52.5":
|
||||
version "4.52.5"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.5.tgz#76ea038b549c5c6c5f0d062942627c4066642ee2"
|
||||
integrity sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==
|
||||
|
||||
"@rollup/rollup-linux-ppc64-gnu@4.52.5":
|
||||
version "4.52.5"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.5.tgz#d9a4c3f0a3492bc78f6fdfe8131ac61c7359ccd5"
|
||||
integrity sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==
|
||||
|
||||
"@rollup/rollup-linux-riscv64-gnu@4.52.5":
|
||||
version "4.52.5"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.5.tgz#87ab033eebd1a9a1dd7b60509f6333ec1f82d994"
|
||||
integrity sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==
|
||||
|
||||
"@rollup/rollup-linux-riscv64-musl@4.52.5":
|
||||
version "4.52.5"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.5.tgz#bda3eb67e1c993c1ba12bc9c2f694e7703958d9f"
|
||||
integrity sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==
|
||||
|
||||
"@rollup/rollup-linux-s390x-gnu@4.52.5":
|
||||
version "4.52.5"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.5.tgz#f7bc10fbe096ab44694233dc42a2291ed5453d4b"
|
||||
integrity sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==
|
||||
|
||||
"@rollup/rollup-linux-x64-gnu@4.52.5":
|
||||
version "4.52.5"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.5.tgz#a151cb1234cc9b2cf5e8cfc02aa91436b8f9e278"
|
||||
integrity sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==
|
||||
|
||||
"@rollup/rollup-linux-x64-musl@4.52.5":
|
||||
version "4.52.5"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.5.tgz#7859e196501cc3b3062d45d2776cfb4d2f3a9350"
|
||||
integrity sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==
|
||||
|
||||
"@rollup/rollup-openharmony-arm64@4.52.5":
|
||||
version "4.52.5"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.5.tgz#85d0df7233734df31e547c1e647d2a5300b3bf30"
|
||||
integrity sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==
|
||||
|
||||
"@rollup/rollup-win32-arm64-msvc@4.52.5":
|
||||
version "4.52.5"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.5.tgz#e62357d00458db17277b88adbf690bb855cac937"
|
||||
integrity sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==
|
||||
|
||||
"@rollup/rollup-win32-ia32-msvc@4.52.5":
|
||||
version "4.52.5"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.5.tgz#fc7cd40f44834a703c1f1c3fe8bcc27ce476cd50"
|
||||
integrity sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==
|
||||
|
||||
"@rollup/rollup-win32-x64-gnu@4.52.5":
|
||||
version "4.52.5"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.5.tgz#1a22acfc93c64a64a48c42672e857ee51774d0d3"
|
||||
integrity sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==
|
||||
|
||||
"@rollup/rollup-win32-x64-msvc@4.52.5":
|
||||
version "4.52.5"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.5.tgz#1657f56326bbe0ac80eedc9f9c18fc1ddd24e107"
|
||||
integrity sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==
|
||||
|
||||
"@svgr/babel-plugin-add-jsx-attribute@8.0.0":
|
||||
version "8.0.0"
|
||||
resolved "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz"
|
||||
@@ -715,7 +976,7 @@
|
||||
"@svgr/babel-plugin-transform-react-native-svg" "8.1.0"
|
||||
"@svgr/babel-plugin-transform-svg-component" "8.0.0"
|
||||
|
||||
"@svgr/core@*", "@svgr/core@^8.1.0":
|
||||
"@svgr/core@^8.1.0":
|
||||
version "8.1.0"
|
||||
resolved "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz"
|
||||
integrity sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==
|
||||
@@ -757,11 +1018,73 @@
|
||||
source-map-js "^1.2.1"
|
||||
tailwindcss "4.1.16"
|
||||
|
||||
"@tailwindcss/oxide-android-arm64@4.1.16":
|
||||
version "4.1.16"
|
||||
resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.16.tgz#9bd16c0a08db20d7c93907a9bd1564e0255307eb"
|
||||
integrity sha512-8+ctzkjHgwDJ5caq9IqRSgsP70xhdhJvm+oueS/yhD5ixLhqTw9fSL1OurzMUhBwE5zK26FXLCz2f/RtkISqHA==
|
||||
|
||||
"@tailwindcss/oxide-darwin-arm64@4.1.16":
|
||||
version "4.1.16"
|
||||
resolved "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.16.tgz"
|
||||
integrity sha512-C3oZy5042v2FOALBZtY0JTDnGNdS6w7DxL/odvSny17ORUnaRKhyTse8xYi3yKGyfnTUOdavRCdmc8QqJYwFKA==
|
||||
|
||||
"@tailwindcss/oxide-darwin-x64@4.1.16":
|
||||
version "4.1.16"
|
||||
resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.16.tgz#6193bafbb1a885795702f12bbef9cc5eb4cc550b"
|
||||
integrity sha512-vjrl/1Ub9+JwU6BP0emgipGjowzYZMjbWCDqwA2Z4vCa+HBSpP4v6U2ddejcHsolsYxwL5r4bPNoamlV0xDdLg==
|
||||
|
||||
"@tailwindcss/oxide-freebsd-x64@4.1.16":
|
||||
version "4.1.16"
|
||||
resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.16.tgz#0e2b064d71ba87a9001ac963be2752a8ddb64349"
|
||||
integrity sha512-TSMpPYpQLm+aR1wW5rKuUuEruc/oOX3C7H0BTnPDn7W/eMw8W+MRMpiypKMkXZfwH8wqPIRKppuZoedTtNj2tg==
|
||||
|
||||
"@tailwindcss/oxide-linux-arm-gnueabihf@4.1.16":
|
||||
version "4.1.16"
|
||||
resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.16.tgz#8e80c959eeda81a08ed955e23eb6d228287b9672"
|
||||
integrity sha512-p0GGfRg/w0sdsFKBjMYvvKIiKy/LNWLWgV/plR4lUgrsxFAoQBFrXkZ4C0w8IOXfslB9vHK/JGASWD2IefIpvw==
|
||||
|
||||
"@tailwindcss/oxide-linux-arm64-gnu@4.1.16":
|
||||
version "4.1.16"
|
||||
resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.16.tgz#d5f54910920fc5808122515f5208c5ecc1a40545"
|
||||
integrity sha512-DoixyMmTNO19rwRPdqviTrG1rYzpxgyYJl8RgQvdAQUzxC1ToLRqtNJpU/ATURSKgIg6uerPw2feW0aS8SNr/w==
|
||||
|
||||
"@tailwindcss/oxide-linux-arm64-musl@4.1.16":
|
||||
version "4.1.16"
|
||||
resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.16.tgz#67cdb932230ac47bf3bf5415ccc92417b27020ee"
|
||||
integrity sha512-H81UXMa9hJhWhaAUca6bU2wm5RRFpuHImrwXBUvPbYb+3jo32I9VIwpOX6hms0fPmA6f2pGVlybO6qU8pF4fzQ==
|
||||
|
||||
"@tailwindcss/oxide-linux-x64-gnu@4.1.16":
|
||||
version "4.1.16"
|
||||
resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.16.tgz#80ae0cfd8ebc970f239060ecdfdd07f6f6b14dce"
|
||||
integrity sha512-ZGHQxDtFC2/ruo7t99Qo2TTIvOERULPl5l0K1g0oK6b5PGqjYMga+FcY1wIUnrUxY56h28FxybtDEla+ICOyew==
|
||||
|
||||
"@tailwindcss/oxide-linux-x64-musl@4.1.16":
|
||||
version "4.1.16"
|
||||
resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.16.tgz#524e5b87e8e79a712de3d9bbb94d2fc2fa44391c"
|
||||
integrity sha512-Oi1tAaa0rcKf1Og9MzKeINZzMLPbhxvm7rno5/zuP1WYmpiG0bEHq4AcRUiG2165/WUzvxkW4XDYCscZWbTLZw==
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi@4.1.16":
|
||||
version "4.1.16"
|
||||
resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.16.tgz#dc31d6bc1f6c1e8119a335ae3f28deb4d7c560f2"
|
||||
integrity sha512-B01u/b8LteGRwucIBmCQ07FVXLzImWESAIMcUU6nvFt/tYsQ6IHz8DmZ5KtvmwxD+iTYBtM1xwoGXswnlu9v0Q==
|
||||
dependencies:
|
||||
"@emnapi/core" "^1.5.0"
|
||||
"@emnapi/runtime" "^1.5.0"
|
||||
"@emnapi/wasi-threads" "^1.1.0"
|
||||
"@napi-rs/wasm-runtime" "^1.0.7"
|
||||
"@tybys/wasm-util" "^0.10.1"
|
||||
tslib "^2.4.0"
|
||||
|
||||
"@tailwindcss/oxide-win32-arm64-msvc@4.1.16":
|
||||
version "4.1.16"
|
||||
resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.16.tgz#f1f810cdb49dae8071d5edf0db5cc0da2ec6a7e8"
|
||||
integrity sha512-zX+Q8sSkGj6HKRTMJXuPvOcP8XfYON24zJBRPlszcH1Np7xuHXhWn8qfFjIujVzvH3BHU+16jBXwgpl20i+v9A==
|
||||
|
||||
"@tailwindcss/oxide-win32-x64-msvc@4.1.16":
|
||||
version "4.1.16"
|
||||
resolved "https://registry.yarnpkg.com/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.16.tgz#76dcda613578f06569c0a6015f39f12746a24dce"
|
||||
integrity sha512-m5dDFJUEejbFqP+UXVstd4W/wnxA4F61q8SoL+mqTypId2T2ZpuxosNSgowiCnLp2+Z+rivdU0AqpfgiD7yCBg==
|
||||
|
||||
"@tailwindcss/oxide@4.1.16":
|
||||
version "4.1.16"
|
||||
resolved "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.16.tgz"
|
||||
@@ -801,6 +1124,13 @@
|
||||
resolved "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz"
|
||||
integrity sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==
|
||||
|
||||
"@tybys/wasm-util@^0.10.1":
|
||||
version "0.10.1"
|
||||
resolved "https://registry.yarnpkg.com/@tybys/wasm-util/-/wasm-util-0.10.1.tgz#ecddd3205cf1e2d5274649ff0eedd2991ed7f414"
|
||||
integrity sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==
|
||||
dependencies:
|
||||
tslib "^2.4.0"
|
||||
|
||||
"@types/babel__core@^7.20.5":
|
||||
version "7.20.5"
|
||||
resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz"
|
||||
@@ -870,7 +1200,7 @@
|
||||
dependencies:
|
||||
"@types/estree" "*"
|
||||
|
||||
"@types/estree@*", "@types/estree@^1.0.0", "@types/estree@^1.0.6", "@types/estree@1.0.8":
|
||||
"@types/estree@*", "@types/estree@1.0.8", "@types/estree@^1.0.0", "@types/estree@^1.0.6":
|
||||
version "1.0.8"
|
||||
resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz"
|
||||
integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==
|
||||
@@ -904,7 +1234,7 @@
|
||||
resolved "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz"
|
||||
integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==
|
||||
|
||||
"@types/node@^18.0.0 || ^20.0.0 || >=22.0.0", "@types/node@^22.15.24":
|
||||
"@types/node@^22.15.24":
|
||||
version "22.18.13"
|
||||
resolved "https://registry.npmjs.org/@types/node/-/node-22.18.13.tgz"
|
||||
integrity sha512-Bo45YKIjnmFtv6I1TuC8AaHBbqXtIo+Om5fE4QiU1Tj8QR/qt+8O3BAtOimG5IFmwaWiPmB3Mv3jtYzBA4Us2A==
|
||||
@@ -951,7 +1281,7 @@
|
||||
resolved "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz"
|
||||
integrity sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==
|
||||
|
||||
"@types/react@*", "@types/react@^17.0.0 || ^18.0.0 || ^19.0.0", "@types/react@^18.2.25 || ^19", "@types/react@^19.1.2", "@types/react@^19.2.0", "@types/react@>=16.8", "@types/react@>=18", "@types/react@>=18.0.0":
|
||||
"@types/react@^19.1.2":
|
||||
version "19.2.2"
|
||||
resolved "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz"
|
||||
integrity sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==
|
||||
@@ -970,7 +1300,7 @@
|
||||
dependencies:
|
||||
"@types/estree" "*"
|
||||
|
||||
"@types/three@*", "@types/three@>=0.134.0":
|
||||
"@types/three@*":
|
||||
version "0.180.0"
|
||||
resolved "https://registry.npmjs.org/@types/three/-/three-0.180.0.tgz"
|
||||
integrity sha512-ykFtgCqNnY0IPvDro7h+9ZeLY+qjgUWv+qEvUt84grhenO60Hqd4hScHE7VTB9nOQ/3QM8lkbNE+4vKjEpUxKg==
|
||||
@@ -1018,7 +1348,7 @@
|
||||
natural-compare "^1.4.0"
|
||||
ts-api-utils "^2.1.0"
|
||||
|
||||
"@typescript-eslint/parser@^8.46.2", "@typescript-eslint/parser@8.46.2":
|
||||
"@typescript-eslint/parser@8.46.2":
|
||||
version "8.46.2"
|
||||
resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.2.tgz"
|
||||
integrity sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g==
|
||||
@@ -1046,7 +1376,7 @@
|
||||
"@typescript-eslint/types" "8.46.2"
|
||||
"@typescript-eslint/visitor-keys" "8.46.2"
|
||||
|
||||
"@typescript-eslint/tsconfig-utils@^8.46.2", "@typescript-eslint/tsconfig-utils@8.46.2":
|
||||
"@typescript-eslint/tsconfig-utils@8.46.2", "@typescript-eslint/tsconfig-utils@^8.46.2":
|
||||
version "8.46.2"
|
||||
resolved "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.2.tgz"
|
||||
integrity sha512-a7QH6fw4S57+F5y2FIxxSDyi5M4UfGF+Jl1bCGd7+L4KsaUY80GsiF/t0UoRFDHAguKlBaACWJRmdrc6Xfkkag==
|
||||
@@ -1062,7 +1392,7 @@
|
||||
debug "^4.3.4"
|
||||
ts-api-utils "^2.1.0"
|
||||
|
||||
"@typescript-eslint/types@^8.46.2", "@typescript-eslint/types@8.46.2":
|
||||
"@typescript-eslint/types@8.46.2", "@typescript-eslint/types@^8.46.2":
|
||||
version "8.46.2"
|
||||
resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.2.tgz"
|
||||
integrity sha512-lNCWCbq7rpg7qDsQrd3D6NyWYu+gkTENkG5IKYhUIcxSb59SQC/hEQ+MrG4sTgBVghTonNWq42bA/d4yYumldQ==
|
||||
@@ -1145,7 +1475,7 @@ acorn-jsx@^5.3.2:
|
||||
resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz"
|
||||
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
|
||||
|
||||
"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.15.0:
|
||||
acorn@^8.15.0:
|
||||
version "8.15.0"
|
||||
resolved "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz"
|
||||
integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==
|
||||
@@ -1249,7 +1579,7 @@ braces@^3.0.3:
|
||||
dependencies:
|
||||
fill-range "^7.1.1"
|
||||
|
||||
browserslist@^4.24.0, "browserslist@>= 4.21.0":
|
||||
browserslist@^4.24.0:
|
||||
version "4.27.0"
|
||||
resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.27.0.tgz"
|
||||
integrity sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==
|
||||
@@ -1546,7 +1876,7 @@ earcut@^3.0.0, earcut@^3.0.2:
|
||||
resolved "https://registry.npmjs.org/earcut/-/earcut-3.0.2.tgz"
|
||||
integrity sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ==
|
||||
|
||||
easymde@^2.20.0, "easymde@>= 2.0.0 < 3.0.0":
|
||||
easymde@^2.20.0:
|
||||
version "2.20.0"
|
||||
resolved "https://registry.npmjs.org/easymde/-/easymde-2.20.0.tgz"
|
||||
integrity sha512-V1Z5f92TfR42Na852OWnIZMbM7zotWQYTddNaLYZFVKj7APBbyZ3FYJ27gBw2grMW3R6Qdv9J8n5Ij7XRSIgXQ==
|
||||
@@ -1689,7 +2019,7 @@ eslint-visitor-keys@^4.2.1:
|
||||
resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz"
|
||||
integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==
|
||||
|
||||
"eslint@^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0", "eslint@^6.0.0 || ^7.0.0 || >=8.0.0", "eslint@^8.57.0 || ^9.0.0", eslint@^9.25.0, eslint@>=8.40:
|
||||
eslint@^9.25.0:
|
||||
version "9.38.0"
|
||||
resolved "https://registry.npmjs.org/eslint/-/eslint-9.38.0.tgz"
|
||||
integrity sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==
|
||||
@@ -1815,12 +2145,7 @@ fastq@^1.6.0:
|
||||
dependencies:
|
||||
reusify "^1.0.4"
|
||||
|
||||
fdir@^6.4.4:
|
||||
version "6.5.0"
|
||||
resolved "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz"
|
||||
integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==
|
||||
|
||||
fdir@^6.5.0:
|
||||
fdir@^6.4.4, fdir@^6.5.0:
|
||||
version "6.5.0"
|
||||
resolved "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz"
|
||||
integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==
|
||||
@@ -2284,7 +2609,7 @@ its-fine@^2.0.0:
|
||||
dependencies:
|
||||
"@types/react-reconciler" "^0.28.9"
|
||||
|
||||
jiti@*, jiti@^2.6.1, jiti@>=1.21.0:
|
||||
jiti@^2.6.1:
|
||||
version "2.6.1"
|
||||
resolved "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz"
|
||||
integrity sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==
|
||||
@@ -2363,12 +2688,62 @@ lie@^3.0.2:
|
||||
dependencies:
|
||||
immediate "~3.0.5"
|
||||
|
||||
lightningcss-android-arm64@1.30.2:
|
||||
version "1.30.2"
|
||||
resolved "https://registry.yarnpkg.com/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz#6966b7024d39c94994008b548b71ab360eb3a307"
|
||||
integrity sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==
|
||||
|
||||
lightningcss-darwin-arm64@1.30.2:
|
||||
version "1.30.2"
|
||||
resolved "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz"
|
||||
integrity sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==
|
||||
|
||||
lightningcss@^1.21.0, lightningcss@1.30.2:
|
||||
lightningcss-darwin-x64@1.30.2:
|
||||
version "1.30.2"
|
||||
resolved "https://registry.yarnpkg.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz#5ce87e9cd7c4f2dcc1b713f5e8ee185c88d9b7cd"
|
||||
integrity sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==
|
||||
|
||||
lightningcss-freebsd-x64@1.30.2:
|
||||
version "1.30.2"
|
||||
resolved "https://registry.yarnpkg.com/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz#6ae1d5e773c97961df5cff57b851807ef33692a5"
|
||||
integrity sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==
|
||||
|
||||
lightningcss-linux-arm-gnueabihf@1.30.2:
|
||||
version "1.30.2"
|
||||
resolved "https://registry.yarnpkg.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz#62c489610c0424151a6121fa99d77731536cdaeb"
|
||||
integrity sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==
|
||||
|
||||
lightningcss-linux-arm64-gnu@1.30.2:
|
||||
version "1.30.2"
|
||||
resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz#2a3661b56fe95a0cafae90be026fe0590d089298"
|
||||
integrity sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==
|
||||
|
||||
lightningcss-linux-arm64-musl@1.30.2:
|
||||
version "1.30.2"
|
||||
resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz#d7ddd6b26959245e026bc1ad9eb6aa983aa90e6b"
|
||||
integrity sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==
|
||||
|
||||
lightningcss-linux-x64-gnu@1.30.2:
|
||||
version "1.30.2"
|
||||
resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz#5a89814c8e63213a5965c3d166dff83c36152b1a"
|
||||
integrity sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==
|
||||
|
||||
lightningcss-linux-x64-musl@1.30.2:
|
||||
version "1.30.2"
|
||||
resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz#808c2e91ce0bf5d0af0e867c6152e5378c049728"
|
||||
integrity sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==
|
||||
|
||||
lightningcss-win32-arm64-msvc@1.30.2:
|
||||
version "1.30.2"
|
||||
resolved "https://registry.yarnpkg.com/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz#ab4a8a8a2e6a82a4531e8bbb6bf0ff161ee6625a"
|
||||
integrity sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==
|
||||
|
||||
lightningcss-win32-x64-msvc@1.30.2:
|
||||
version "1.30.2"
|
||||
resolved "https://registry.yarnpkg.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz#f01f382c8e0a27e1c018b0bee316d210eac43b6e"
|
||||
integrity sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==
|
||||
|
||||
lightningcss@1.30.2:
|
||||
version "1.30.2"
|
||||
resolved "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz"
|
||||
integrity sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==
|
||||
@@ -2812,7 +3187,7 @@ mobx-react-lite@^4.1.0:
|
||||
dependencies:
|
||||
use-sync-external-store "^1.4.0"
|
||||
|
||||
mobx@^6.13.7, mobx@^6.9.0:
|
||||
mobx@^6.13.7:
|
||||
version "6.15.0"
|
||||
resolved "https://registry.npmjs.org/mobx/-/mobx-6.15.0.tgz"
|
||||
integrity sha512-UczzB+0nnwGotYSgllfARAqWCJ5e/skuV2K/l+Zyck/H6pJIhLXuBnz+6vn2i211o7DtbE78HQtsYEKICHGI+g==
|
||||
@@ -2993,12 +3368,12 @@ picomatch@^2.3.1:
|
||||
resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz"
|
||||
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
|
||||
|
||||
"picomatch@^3 || ^4", picomatch@^4.0.2, picomatch@^4.0.3:
|
||||
picomatch@^4.0.2, picomatch@^4.0.3:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz"
|
||||
integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==
|
||||
|
||||
pixi.js@^8.10.1, pixi.js@^8.2.6:
|
||||
pixi.js@^8.10.1:
|
||||
version "8.14.0"
|
||||
resolved "https://registry.npmjs.org/pixi.js/-/pixi.js-8.14.0.tgz"
|
||||
integrity sha512-ituDiEBb1Oqx56RYwTtC6MjPUhPfF/i15fpUv5oEqmzC/ce3SaSumulJcOjKG7+y0J0Ekl9Rl4XTxaUw+MVFZw==
|
||||
@@ -3055,7 +3430,7 @@ promise-worker-transferable@^1.0.4:
|
||||
is-promise "^2.1.0"
|
||||
lie "^3.0.2"
|
||||
|
||||
prop-types@^15.5.4, prop-types@^15.6.2, prop-types@^15.8.1:
|
||||
prop-types@^15.6.2, prop-types@^15.8.1:
|
||||
version "15.8.1"
|
||||
resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz"
|
||||
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
|
||||
@@ -3116,19 +3491,14 @@ rbush@^4.0.0:
|
||||
dependencies:
|
||||
quickselect "^3.0.0"
|
||||
|
||||
"react-dom@^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom@^18 || ^19", "react-dom@^18.0.0 || ^19.0.0", react-dom@^19, react-dom@^19.0.0, react-dom@^19.1.0, react-dom@>=16.0.0, react-dom@>=16.13, react-dom@>=16.6.0, react-dom@>=16.8.2, react-dom@>=18:
|
||||
react-dom@^19.1.0:
|
||||
version "19.2.0"
|
||||
resolved "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz"
|
||||
integrity sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==
|
||||
dependencies:
|
||||
scheduler "^0.27.0"
|
||||
|
||||
react-is@^16.13.1:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
|
||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||
|
||||
react-is@^16.7.0:
|
||||
react-is@^16.13.1, react-is@^16.7.0:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
|
||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||
@@ -3162,7 +3532,7 @@ react-photo-sphere-viewer@^6.2.3:
|
||||
dependencies:
|
||||
eventemitter3 "^5.0.1"
|
||||
|
||||
react-reconciler@^0.31.0, react-reconciler@0.31.0:
|
||||
react-reconciler@0.31.0, react-reconciler@^0.31.0:
|
||||
version "0.31.0"
|
||||
resolved "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.31.0.tgz"
|
||||
integrity sha512-7Ob7Z+URmesIsIVRjnLoDGwBEG/tVitidU0nMsqX/eeJaLY89RISO/10ERe0MqmzuKUUB1rmY+h1itMbUHg9BQ==
|
||||
@@ -3189,7 +3559,7 @@ react-router-dom@^7.6.1:
|
||||
dependencies:
|
||||
react-router "7.9.4"
|
||||
|
||||
react-router@^7.9.4, react-router@7.9.4:
|
||||
react-router@7.9.4, react-router@^7.9.4:
|
||||
version "7.9.4"
|
||||
resolved "https://registry.npmjs.org/react-router/-/react-router-7.9.4.tgz"
|
||||
integrity sha512-SD3G8HKviFHg9xj7dNODUKDFgpG4xqD5nhyd0mYoB5iISepuZAvzSr8ywxgxKJ52yRzf/HWtVHc9AWwoTbljvA==
|
||||
@@ -3226,12 +3596,12 @@ react-use-measure@^2.1.7:
|
||||
resolved "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.7.tgz"
|
||||
integrity sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==
|
||||
|
||||
"react@^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.8.0 || ^17 || ^18 || ^19", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^17.0.0 || ^18.0.0 || ^19.0.0", "react@^18 || ^19", "react@^18.0 || ^19", "react@^18.0.0 || ^19.0.0", react@^19, react@^19.0.0, react@^19.1.0, react@^19.2.0, "react@>= 16.8.0", react@>=16.0.0, react@>=16.13, react@>=16.6.0, react@>=16.8, react@>=16.8.0, react@>=16.8.2, react@>=17.0, react@>=18, react@>=18.0.0, react@>=19.0.0:
|
||||
react@^19.1.0:
|
||||
version "19.2.0"
|
||||
resolved "https://registry.npmjs.org/react/-/react-19.2.0.tgz"
|
||||
integrity sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==
|
||||
|
||||
redux@^5.0.0, redux@^5.0.1:
|
||||
redux@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz"
|
||||
integrity sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==
|
||||
@@ -3317,7 +3687,7 @@ rollup-plugin-visualizer@^6.0.5:
|
||||
source-map "^0.7.4"
|
||||
yargs "^17.5.1"
|
||||
|
||||
rollup@^1.20.0||^2.0.0||^3.0.0||^4.0.0, rollup@^4.34.9, "rollup@2.x || 3.x || 4.x":
|
||||
rollup@^4.34.9:
|
||||
version "4.52.5"
|
||||
resolved "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz"
|
||||
integrity sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==
|
||||
@@ -3503,7 +3873,7 @@ svg-parser@^2.0.4:
|
||||
resolved "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz"
|
||||
integrity sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==
|
||||
|
||||
tailwindcss@^4.1.8, "tailwindcss@>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1", tailwindcss@4.1.16:
|
||||
tailwindcss@4.1.16, tailwindcss@^4.1.8:
|
||||
version "4.1.16"
|
||||
resolved "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.16.tgz"
|
||||
integrity sha512-pONL5awpaQX4LN5eiv7moSiSPd/DLDzKVRJz8Q9PgzmAdd1R4307GQS2ZpfiN7ZmekdQrfhZZiSE5jkLR4WNaA==
|
||||
@@ -3535,7 +3905,7 @@ three@^0.170.0:
|
||||
resolved "https://registry.npmjs.org/three/-/three-0.170.0.tgz"
|
||||
integrity sha512-FQK+LEpYc0fBD+J8g6oSEyyNzjp+Q7Ks1C568WWaoMRLW+TkNNWmenWeGgJjV105Gd+p/2ql1ZcjYvNiPZBhuQ==
|
||||
|
||||
three@^0.177.0, "three@>= 0.159.0", three@>=0.125.0, three@>=0.126.1, three@>=0.128.0, three@>=0.134.0, three@>=0.137, three@>=0.156, three@>=0.159:
|
||||
three@^0.177.0:
|
||||
version "0.177.0"
|
||||
resolved "https://registry.npmjs.org/three/-/three-0.177.0.tgz"
|
||||
integrity sha512-EiXv5/qWAaGI+Vz2A+JfavwYCMdGjxVsrn3oBwllUoqYeaBO75J63ZfyaQKoiLrqNHoTlUc6PFgMXnS0kI45zg==
|
||||
@@ -3605,9 +3975,9 @@ ts-api-utils@^2.1.0:
|
||||
resolved "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz"
|
||||
integrity sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==
|
||||
|
||||
tslib@^2.0.3:
|
||||
tslib@^2.0.3, tslib@^2.4.0:
|
||||
version "2.8.1"
|
||||
resolved "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
|
||||
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
|
||||
|
||||
tunnel-rat@^0.1.2:
|
||||
@@ -3634,7 +4004,7 @@ typescript-eslint@^8.30.1:
|
||||
"@typescript-eslint/typescript-estree" "8.46.2"
|
||||
"@typescript-eslint/utils" "8.46.2"
|
||||
|
||||
typescript@>=4.8.4, "typescript@>=4.8.4 <6.0.0", typescript@>=4.9.5, typescript@~5.8.3:
|
||||
typescript@~5.8.3:
|
||||
version "5.8.3"
|
||||
resolved "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz"
|
||||
integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==
|
||||
@@ -3715,7 +4085,7 @@ uri-js@^4.2.2:
|
||||
dependencies:
|
||||
punycode "^2.1.0"
|
||||
|
||||
use-sync-external-store@^1.2.2, use-sync-external-store@^1.4.0, use-sync-external-store@^1.6.0, use-sync-external-store@>=1.2.0:
|
||||
use-sync-external-store@^1.2.2, use-sync-external-store@^1.4.0, use-sync-external-store@^1.6.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz"
|
||||
integrity sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==
|
||||
@@ -3770,7 +4140,7 @@ vite-plugin-svgr@^4.5.0:
|
||||
"@svgr/core" "^8.1.0"
|
||||
"@svgr/plugin-jsx" "^8.1.0"
|
||||
|
||||
"vite@^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", "vite@^5.2.0 || ^6 || ^7", vite@^6.3.5, vite@>=2.6.0:
|
||||
vite@^6.3.5:
|
||||
version "6.4.1"
|
||||
resolved "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz"
|
||||
integrity sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==
|
||||
|
||||
Reference in New Issue
Block a user