Browse Source

Added map filter indication

Added map filter indication
+ some more type fixes...
pull/585/head
Jeremy Gallant 1 year ago
parent
commit
f399d17721
  1. 33
      src/core/hooks/useNodeFilters.ts
  2. 12
      src/pages/Map/FilterControl.tsx
  3. 15
      src/pages/Map/index.tsx

33
src/core/hooks/useNodeFilters.ts

@ -6,7 +6,7 @@ interface BooleanFilter {
key: string; key: string;
label: string; label: string;
type: "boolean"; type: "boolean";
predicate: (node: Node, value: boolean) => boolean; predicate: (node: Protobuf.Mesh.NodeInfo, value: boolean) => boolean;
} }
interface RangeFilter { interface RangeFilter {
@ -14,14 +14,14 @@ interface RangeFilter {
label: string; label: string;
type: "range"; type: "range";
bounds: [number, number]; bounds: [number, number];
predicate: (node: Node, value: [number, number]) => boolean; predicate: (node: Protobuf.Mesh.NodeInfo, value: [number, number]) => boolean;
} }
interface SearchFilter { interface SearchFilter {
key: string; key: string;
label: string; label: string;
type: "search"; type: "search";
predicate: (node: Node, value: string) => boolean; predicate: (node: Protobuf.Mesh.NodeInfo, value: string) => boolean;
} }
export type FilterConfig = BooleanFilter | RangeFilter | SearchFilter; export type FilterConfig = BooleanFilter | RangeFilter | SearchFilter;
@ -113,7 +113,7 @@ export function useNodeFilters(nodes: Protobuf.Mesh.NodeInfo[]) {
acc[cfg.key] = false; acc[cfg.key] = false;
break; break;
case "range": case "range":
acc[cfg.key] = cfg.bounds!; acc[cfg.key] = cfg.bounds;
break; break;
case "search": case "search":
acc[cfg.key] = ""; acc[cfg.key] = "";
@ -141,13 +141,36 @@ export function useNodeFilters(nodes: Protobuf.Mesh.NodeInfo[]) {
const filteredNodes = useMemo( const filteredNodes = useMemo(
() => () =>
nodes.filter((node) => nodes.filter((node) =>
filterConfigs.every((cfg) => cfg.predicate(node, filters[cfg.key])) filterConfigs.every((cfg) => {
const val = filters[cfg.key];
switch (cfg.type) {
case "boolean":
if (typeof val !== "boolean") return true;
return cfg.predicate(node, val);
case "range":
if (
!Array.isArray(val) ||
val.length !== 2 ||
typeof val[0] !== "number" ||
typeof val[1] !== "number"
) {
return true;
}
return cfg.predicate(node, val);
case "search":
if (typeof val !== "string") return true;
return cfg.predicate(node, val);
}
})
), ),
[nodes, filters], [nodes, filters],
); );
return { return {
filters, filters,
defaultState,
onFilterChange, onFilterChange,
resetFilters, resetFilters,
filteredNodes, filteredNodes,

12
src/pages/Map/FilterControl.tsx

@ -10,6 +10,7 @@ import type {
FilterConfig, FilterConfig,
FilterValueMap, FilterValueMap,
} from "@core/hooks/useNodeFilters.ts"; } from "@core/hooks/useNodeFilters.ts";
import { cn } from "@core/utils/cn.ts";
interface FilterControlProps { interface FilterControlProps {
configs: FilterConfig[]; configs: FilterConfig[];
@ -19,18 +20,25 @@ interface FilterControlProps {
value: FilterValueMap[K], value: FilterValueMap[K],
) => void; ) => void;
resetFilters: () => void; resetFilters: () => void;
isDirty: boolean;
children?: React.ReactNode; children?: React.ReactNode;
} }
export function FilterControl( export function FilterControl(
{ configs, values, onChange, resetFilters, children }: FilterControlProps, { configs, values, onChange, resetFilters, isDirty, children }:
FilterControlProps,
) { ) {
return ( return (
<Popover> <Popover>
<PopoverTrigger asChild> <PopoverTrigger asChild>
<button <button
type="button" type="button"
className="fixed bottom-17 right-2 px-1 py-1 bg-slate-100 text-slate-600 rounded shadow-md" className={cn(
"fixed bottom-17 right-2 px-1 py-1 rounded shadow-md",
isDirty
? " text-slate-100 bg-green-600"
: "text-slate-600 bg-slate-100",
)}
aria-label="Filter" aria-label="Filter"
> >
<FunnelIcon /> <FunnelIcon />

15
src/pages/Map/index.tsx

@ -56,13 +56,25 @@ const MapPage = () => {
); );
const { const {
filteredNodes,
filters, filters,
defaultState,
onFilterChange, onFilterChange,
resetFilters, resetFilters,
filteredNodes,
filterConfigs, filterConfigs,
} = useNodeFilters(validNodes); } = useNodeFilters(validNodes);
const isDirty = useMemo(() => {
return Object.keys(filters).some((key) => {
const a = filters[key];
const b = defaultState[key];
// simple deep‐equal for primitives and [number,number]
return Array.isArray(a) && Array.isArray(b)
? a[0] !== b[0] || a[1] !== b[1]
: a !== b;
});
}, [filters, defaultState]);
const handleMarkerClick = useCallback( const handleMarkerClick = useCallback(
(node: Protobuf.Mesh.NodeInfo, event: { originalEvent: MouseEvent }) => { (node: Protobuf.Mesh.NodeInfo, event: { originalEvent: MouseEvent }) => {
event?.originalEvent?.stopPropagation(); event?.originalEvent?.stopPropagation();
@ -213,6 +225,7 @@ const MapPage = () => {
values={filters} values={filters}
onChange={onFilterChange} onChange={onFilterChange}
resetFilters={resetFilters} resetFilters={resetFilters}
isDirty={isDirty}
/> />
</PageLayout> </PageLayout>
</> </>

Loading…
Cancel
Save