Skip to content

Commit fc2cbbf

Browse files
authored
Merge pull request #283 from commonknowledge/feat/inspect-cluster
feat: add clusters to inspector panel
2 parents 9d46b56 + ac29838 commit fc2cbbf

File tree

6 files changed

+117
-33
lines changed

6 files changed

+117
-33
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { useInspector } from "@/app/map/[id]/hooks/useInspector";
2+
import { DataSourceRecordType } from "@/server/models/DataSource";
3+
import { MarkersList, MembersList } from "./MarkersLists";
4+
import type { MarkerFeature } from "@/types";
5+
6+
export default function ClusterMarkersList() {
7+
const { selectedRecords, inspectorContent } = useInspector();
8+
const recordType = inspectorContent?.dataSource?.recordType;
9+
const markerFeatures = selectedRecords
10+
.map((r): MarkerFeature | null => {
11+
if (!r.dataSourceId || !r.geocodePoint) {
12+
// Should never happen for markers in a cluster
13+
return null;
14+
}
15+
return {
16+
type: "Feature",
17+
geometry: {
18+
// [0, 0] should never happen because these records are in a cluster on the map
19+
coordinates: [r.geocodePoint?.lng || 0, r.geocodePoint?.lat || 0],
20+
type: "Point",
21+
},
22+
properties: {
23+
id: r.id,
24+
name: r.name,
25+
dataSourceId: r.dataSourceId,
26+
matched: true,
27+
},
28+
};
29+
})
30+
.filter((r) => r !== null);
31+
32+
return (
33+
<div className="flex flex-col gap-6">
34+
{recordType === DataSourceRecordType.Members ? (
35+
<MembersList
36+
dataSource={inspectorContent?.dataSource}
37+
markers={markerFeatures}
38+
areaType="cluster"
39+
/>
40+
) : (
41+
<MarkersList
42+
dataSource={inspectorContent?.dataSource}
43+
markers={markerFeatures}
44+
/>
45+
)}
46+
</div>
47+
);
48+
}

src/app/map/[id]/components/inspector/InspectorMarkersTab.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { LayerType } from "@/types";
22
import BoundaryMarkersList from "./BoundaryMarkersList";
3+
import ClusterMarkersList from "./ClusterMarkersList";
34
import TurfMarkersList from "./TurfMarkersList";
45

56
interface InspectorMarkersTabProps {
@@ -11,6 +12,7 @@ export default function InspectorMarkersTab({
1112
}: InspectorMarkersTabProps) {
1213
return (
1314
<div className="flex flex-col gap-4">
15+
{type === LayerType.Cluster && <ClusterMarkersList />}
1416
{type === LayerType.Turf && <TurfMarkersList />}
1517
{type === LayerType.Boundary && <BoundaryMarkersList />}
1618
</div>

src/app/map/[id]/components/inspector/InspectorPanel.tsx

Lines changed: 50 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ArrowLeftIcon, SettingsIcon, XIcon } from "lucide-react";
2-
import { useState } from "react";
2+
import { useMemo, useState } from "react";
33

44
import { useInspector } from "@/app/map/[id]/hooks/useInspector";
55
import { cn } from "@/shadcn/utils";
@@ -26,12 +26,26 @@ export default function InspectorPanel() {
2626
setFocusedRecord,
2727
selectedRecords,
2828
} = useInspector();
29+
const { dataSource, properties, type } = inspectorContent ?? {};
30+
31+
const safeActiveTab = useMemo(() => {
32+
if (activeTab === "data" && type === LayerType.Cluster) {
33+
return "markers";
34+
}
35+
const isMarkers = type === LayerType.Marker || type === LayerType.Member;
36+
if (activeTab === "markers" && isMarkers) {
37+
return "data";
38+
}
39+
if (activeTab === "config" && type !== LayerType.Boundary) {
40+
return type === LayerType.Cluster ? "markers" : "data";
41+
}
42+
return activeTab;
43+
}, [activeTab, type]);
2944

3045
if (!Boolean(inspectorContent)) {
3146
return <></>;
3247
}
3348

34-
const { dataSource, properties, type } = inspectorContent ?? {};
3549
const isDetailsView = Boolean(
3650
(selectedTurf && type !== LayerType.Turf) ||
3751
(selectedBoundary && type !== LayerType.Boundary),
@@ -49,17 +63,17 @@ export default function InspectorPanel() {
4963
className={cn(
5064
"absolute top-0 bottom-0 right-4 / flex flex-col gap-6 py-5",
5165
"bottom-24", // to avoid clash with bug report button
52-
activeTab === "config" ? "h-full" : "h-fit max-h-full",
66+
safeActiveTab === "config" ? "h-full" : "h-fit max-h-full",
5367
)}
5468
style={{
55-
minWidth: activeTab === "config" ? "400px" : "250px",
69+
minWidth: safeActiveTab === "config" ? "400px" : "250px",
5670
maxWidth: "450px",
5771
}}
5872
>
5973
<div
6074
className={cn(
6175
"relative z-50 w-full overflow-auto / flex flex-col / rounded shadow-lg bg-white / text-sm font-sans",
62-
activeTab === "config" ? "h-full" : "max-h-full",
76+
safeActiveTab === "config" ? "h-full" : "max-h-full",
6377
)}
6478
>
6579
<div className="flex justify-between items-center gap-4 p-3">
@@ -98,12 +112,14 @@ export default function InspectorPanel() {
98112

99113
<UnderlineTabs
100114
defaultValue="data"
101-
value={activeTab}
115+
value={safeActiveTab}
102116
onValueChange={setActiveTab}
103117
className="flex flex-col overflow-hidden h-full"
104118
>
105119
<UnderlineTabsList className="w-full flex gap-6 border-t px-3">
106-
<UnderlineTabsTrigger value="data">Data</UnderlineTabsTrigger>
120+
{type !== LayerType.Cluster && (
121+
<UnderlineTabsTrigger value="data">Data</UnderlineTabsTrigger>
122+
)}
107123
<UnderlineTabsTrigger
108124
value="markers"
109125
className={cn(
@@ -116,20 +132,27 @@ export default function InspectorPanel() {
116132
<UnderlineTabsTrigger value="notes" className="hidden">
117133
Notes 0
118134
</UnderlineTabsTrigger>
119-
<UnderlineTabsTrigger value="config" className="px-2">
120-
<SettingsIcon size={16} />
121-
</UnderlineTabsTrigger>
135+
{type === LayerType.Boundary && (
136+
<UnderlineTabsTrigger value="config" className="px-2">
137+
<SettingsIcon size={16} />
138+
</UnderlineTabsTrigger>
139+
)}
122140
</UnderlineTabsList>
123141

124-
<UnderlineTabsContent value="data" className="grow overflow-auto p-3">
125-
<InspectorDataTab
126-
dataSource={dataSource}
127-
properties={properties}
128-
isDetailsView={isDetailsView}
129-
focusedRecord={focusedRecord}
130-
type={type}
131-
/>
132-
</UnderlineTabsContent>
142+
{type !== LayerType.Cluster && (
143+
<UnderlineTabsContent
144+
value="data"
145+
className="grow overflow-auto p-3"
146+
>
147+
<InspectorDataTab
148+
dataSource={dataSource}
149+
properties={properties}
150+
isDetailsView={isDetailsView}
151+
focusedRecord={focusedRecord}
152+
type={type}
153+
/>
154+
</UnderlineTabsContent>
155+
)}
133156

134157
<UnderlineTabsContent
135158
value="markers"
@@ -145,12 +168,14 @@ export default function InspectorPanel() {
145168
<InspectorNotesTab />
146169
</UnderlineTabsContent>
147170

148-
<UnderlineTabsContent
149-
value="config"
150-
className="grow overflow-auto p-3 h-full"
151-
>
152-
<InspectorConfigTab />
153-
</UnderlineTabsContent>
171+
{type === LayerType.Boundary && (
172+
<UnderlineTabsContent
173+
value="config"
174+
className="grow overflow-auto p-3 h-full"
175+
>
176+
<InspectorConfigTab />
177+
</UnderlineTabsContent>
178+
)}
154179
</UnderlineTabs>
155180
</div>
156181
</div>

src/app/map/[id]/components/inspector/MarkersLists.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ export const MembersList = ({
1313
areaType,
1414
}: {
1515
markers: MarkerFeature[];
16-
dataSource: DataSource | null;
17-
areaType: "area" | "boundary";
16+
dataSource: DataSource | undefined | null;
17+
areaType: "area" | "boundary" | "cluster";
1818
}) => {
1919
const { setSelectedRecords } = useInspector();
2020

@@ -68,7 +68,7 @@ export const MarkersList = ({
6868
dataSource,
6969
}: {
7070
markers: MarkerFeature[];
71-
dataSource: DataSource | null;
71+
dataSource: DataSource | undefined | null;
7272
}) => {
7373
const { setSelectedRecords } = useInspector();
7474
const total = markers.length;

src/app/map/[id]/hooks/useInspector.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,26 +71,34 @@ export function useInspector() {
7171
}
7272

7373
const dataSourceId = focusedRecord.dataSourceId;
74-
7574
const dataSource = dataSourceId ? getDataSourceById(dataSourceId) : null;
75+
if (selectedRecords.length > 1) {
76+
return {
77+
type: LayerType.Cluster,
78+
name: "Cluster",
79+
properties: null,
80+
dataSource,
81+
};
82+
}
83+
7684
const type =
7785
dataSourceId === mapConfig.membersDataSourceId
7886
? LayerType.Member
7987
: LayerType.Marker;
8088

8189
return {
82-
type: type,
90+
type,
8391
name: focusedRecord.name,
8492
properties: null,
85-
dataSource: dataSource,
93+
dataSource,
8694
};
8795
}, [
8896
focusedRecord,
8997
getDataSourceById,
9098
mapConfig.membersDataSourceId,
9199
selectedBoundary,
92-
selectedTurf?.id,
93-
selectedTurf?.name,
100+
selectedRecords.length,
101+
selectedTurf,
94102
]);
95103

96104
const resetInspector = useCallback(() => {

src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ export interface FilterField {
8686

8787
export enum LayerType {
8888
Boundary = "Boundary",
89+
Cluster = "Cluster",
8990
Member = "Member",
9091
Marker = "Marker",
9192
Turf = "Turf",

0 commit comments

Comments
 (0)