Skip to content

Commit

Permalink
feat(maps): add button to copy url to the area
Browse files Browse the repository at this point in the history
  • Loading branch information
ArtemSBulgakov committed Dec 19, 2024
1 parent 1e0cb58 commit 5927f6c
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 21 deletions.
15 changes: 10 additions & 5 deletions src/app/routes/_with_menu/maps.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,20 @@ import { Helmet } from "react-helmet-async";
export const Route = createFileRoute("/_with_menu/maps")({
validateSearch: (
search: Record<string, unknown>,
): { sceneId?: string; q?: string } => {
): { scene?: string; q?: string; area?: string } => {
return {
sceneId: (search.sceneId as string) ?? undefined,
q: (search.q as string) ?? undefined,
scene: search.scene
? search.scene.toString()
: search.sceneId // Backward compatibility with 'sceneId' query parameter
? search.sceneId.toString()
: undefined,
area: search.area ? search.area.toString() : undefined,
q: search.q ? search.q.toString() : undefined,
};
},

component: function RouteComponent() {
const { sceneId, q } = Route.useSearch();
const { scene, area, q } = Route.useSearch();
return (
<>
<Helmet>
Expand All @@ -28,7 +33,7 @@ export const Route = createFileRoute("/_with_menu/maps")({

<Topbar title="Maps" />
<MapsPageTabs />
<MapsPage sceneId={sceneId} q={q} />
<MapsPage sceneId={scene} areaId={area} q={q} />
</>
);
},
Expand Down
19 changes: 16 additions & 3 deletions src/components/maps/MapsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import { useEffect } from "react";

export function MapsPage({
sceneId,
areaId,
q,
}: {
sceneId: string | undefined;
areaId: string | undefined;
q: string | undefined;
}) {
const navigate = useNavigate();
Expand All @@ -23,13 +25,19 @@ export function MapsPage({
},
);

const requestedArea = scenes
?.flatMap((scene) => scene.areas)
.find((area) => area.svg_polygon_id === areaId);
const requestedAreas = requestedArea ? [requestedArea] : undefined;

useEffect(() => {
if (!searchResult || searchResult.length === 0) return;
// Show correct floor by navigating to URL with first sceneId
const firstSceneId = searchResult?.[0]?.scene_id;
const firstSceneId = searchResult[0].scene_id;
if (firstSceneId !== undefined && firstSceneId !== sceneId) {
navigate({
to: "/maps",
search: { sceneId: firstSceneId, q: q },
search: { scene: firstSceneId, q: q },
replace: true, // Do not add useless history entries not to break the back button
});
}
Expand All @@ -46,7 +54,12 @@ export function MapsPage({
return (
<div className="mt-1 flex grow flex-col gap-4 @3xl/content:flex-row">
<div className="min-h-[600px] grow">
<MapView scene={currentScene} highlightAreas={searchResult ?? []} />
<MapView
scene={currentScene}
highlightAreas={
searchResult?.map((res) => res.area) ?? requestedAreas ?? []
}
/>
</div>

<div className="flex w-full shrink-0 flex-col gap-2 px-2 @3xl/content:w-64">
Expand Down
6 changes: 3 additions & 3 deletions src/components/maps/MapsPageTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useEffect, useRef, useState } from "react";
export function MapsPageTabs() {
const navigate = useNavigate();
const { data: scenes } = $maps.useQuery("get", "/scenes/");
const { sceneId, q } = useLocation({ select: ({ search }) => search });
const { scene: sceneId, q } = useLocation({ select: ({ search }) => search });
const [searchText, setSearchText] = useState(q ?? "");
const inputRef = useRef<HTMLInputElement>(null);

Expand All @@ -20,7 +20,7 @@ export function MapsPageTabs() {
inputRef.current?.blur();
// Set search query in URL
if (searchText) {
navigate({ to: "/maps", search: { q: searchText, sceneId } });
navigate({ to: "/maps", search: { q: searchText, scene: sceneId } });
}
};

Expand Down Expand Up @@ -50,7 +50,7 @@ export function MapsPageTabs() {
<Link
key={scene.scene_id}
to="/maps"
search={{ sceneId: scene.scene_id }}
search={{ scene: scene.scene_id }}
className={clsx(
"px-2 py-1",
scene.scene_id === sceneId || (sceneId === undefined && i === 0)
Expand Down
63 changes: 60 additions & 3 deletions src/components/maps/viewer/DetailsPopup.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { mapsTypes } from "@/api/maps";
import Tooltip from "@/components/common/Tooltip.tsx";
import { getMapAreaUrl } from "@/lib/maps/links.ts";
import {
arrow,
autoUpdate,
Expand All @@ -15,15 +17,19 @@ import {
useTransitionStyles,
} from "@floating-ui/react";
import { Link } from "@tanstack/react-router";
import React, { useEffect, useRef } from "react";
import clsx from "clsx";
import { useEffect, useRef, useState } from "react";
import { useCopyToClipboard } from "usehooks-ts";

export function DetailsPopup({
elementRef,
scene,
area,
isOpen,
setIsOpen,
}: {
elementRef: Element | null;
scene: mapsTypes.SchemaScene;
area: mapsTypes.SchemaArea | undefined;
isOpen: boolean;
setIsOpen: (open: boolean) => void;
Expand Down Expand Up @@ -84,10 +90,12 @@ export function DetailsPopup({
{...getFloatingProps()}
className="z-10 flex max-w-md flex-col gap-2 rounded-2xl bg-primary p-4 text-sm text-contrast drop-shadow-md"
>
<div className="flex flex-row gap-2">
<div className="flex flex-row justify-between gap-2">
<div className="text-bold flex whitespace-pre-wrap text-xl [overflow-wrap:anywhere]">
Room: <span className="font-medium">{area.title}</span>
<span className="font-medium">{area.title}</span>
</div>

<ShareButton scene={scene} area={area} />
</div>
{area.description && (
<div className="flex flex-row gap-2">
Expand Down Expand Up @@ -133,3 +141,52 @@ export function DetailsPopup({
</FloatingPortal>
);
}

function ShareButton({
scene,
area,
}: {
scene: mapsTypes.SchemaScene;
area: mapsTypes.SchemaArea;
}) {
const [_, _copy] = useCopyToClipboard();
const [copied, setCopied] = useState(false);
const [timer, setTimer] = useState<any>();

const copy = () => {
const url = getMapAreaUrl(scene, area);

_copy(url).then((ok) => {
if (timer !== undefined) {
clearTimeout(timer);
}
if (ok) {
setCopied(true);
setTimer(setTimeout(() => setCopied(false), 1500));
} else {
setCopied(false);
}
});
};

return (
<Tooltip
content={
<div className={copied ? "text-green-700 dark:text-green-500" : ""}>
{!copied ? "Share link to this room" : "Link copied!"}
</div>
}
>
<button
type="button"
className={clsx(
"flex items-center justify-center rounded-full hover:bg-secondary-hover",
copied && "text-green-700 dark:text-green-500",
)}
onClick={() => copy()}
>
<span className="icon-[material-symbols--share-outline] text-2xl" />
</button>
</Tooltip>
);
}
2 changes: 1 addition & 1 deletion src/components/maps/viewer/MapView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export function MapView({
highlightAreas,
}: {
scene: mapsTypes.SchemaScene;
highlightAreas: mapsTypes.SchemaSearchResult[];
highlightAreas: mapsTypes.SchemaArea[];
}) {
const [fullscreen, setFullscreen] = useState(false);
const switchFullscreen = useCallback(() => setFullscreen((v) => !v), []);
Expand Down
11 changes: 5 additions & 6 deletions src/components/maps/viewer/MapViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const MapViewer = memo(function MapViewer({
highlightAreas,
}: {
scene: mapsTypes.SchemaScene;
highlightAreas: mapsTypes.SchemaSearchResult[];
highlightAreas: mapsTypes.SchemaArea[];
}) {
const containerRef = useRef<HTMLDivElement>(null);
const imageRef = useRef<HTMLDivElement>(null);
Expand Down Expand Up @@ -277,9 +277,7 @@ export const MapViewer = memo(function MapViewer({
const rect = containerRef.current.getBoundingClientRect();
const imageRect = imageRef.current.getBoundingClientRect();

const areaIds = highlightAreas.map(
(s) => s.area.svg_polygon_id ?? undefined,
);
const areaIds = highlightAreas.map((s) => s.svg_polygon_id ?? undefined);
const areas = areaIds.map((id) =>
imageRef.current?.querySelector(`[id="${id}"]`),
);
Expand Down Expand Up @@ -322,7 +320,7 @@ export const MapViewer = memo(function MapViewer({

// Show popup
if (highlightAreas.length === 1) {
const area = highlightAreas[0].area;
const area = highlightAreas[0];
const el = imageRef.current?.querySelector(
`[id="${area.svg_polygon_id}"]`,
);
Expand All @@ -348,7 +346,7 @@ export const MapViewer = memo(function MapViewer({
0%, 100% { opacity: 0.2; }
50% { opacity: 0.5; }
}
${highlightAreas.map((s) => `[id="${s.area.svg_polygon_id}"]`).join(",")} {
${highlightAreas.map((s) => `[id="${s.svg_polygon_id}"]`).join(",")} {
fill: violet !important;
animation: pulse 1.5s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
Expand All @@ -364,6 +362,7 @@ export const MapViewer = memo(function MapViewer({
)}
<DetailsPopup
elementRef={popupElement}
scene={scene}
area={popupArea}
isOpen={popupIsOpen}
setIsOpen={setPopupIsOpen}
Expand Down
15 changes: 15 additions & 0 deletions src/lib/maps/links.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { mapsTypes } from "@/api/maps";

export function getMapAreaUrl(
scene: mapsTypes.SchemaScene,
area: mapsTypes.SchemaArea,
) {
const sceneUrl = `${window.location.origin}/maps?scene=${encodeURIComponent(scene.scene_id)}`;
if (area.svg_polygon_id) {
return `${sceneUrl}&area=${encodeURIComponent(area.svg_polygon_id)}`;
} else if (area.title) {
return `${sceneUrl}&q=${encodeURIComponent(area.title)}`;
} else {
return sceneUrl;
}
}

0 comments on commit 5927f6c

Please sign in to comment.