Skip to content

Commit

Permalink
Merge branch 'shattering' into mobile-ui
Browse files Browse the repository at this point in the history
  • Loading branch information
nofurtherinformation committed Oct 15, 2024
2 parents c1c92f2 + a1c7de2 commit 2a4cbe0
Show file tree
Hide file tree
Showing 43 changed files with 1,107 additions and 569 deletions.
7 changes: 1 addition & 6 deletions .github/workflows/test-backend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
container-job:
runs-on: ubuntu-latest

container: python:3.12
container: python:3.12.6

services:
postgres:
Expand All @@ -34,11 +34,6 @@ jobs:
- name: Checkout repo code
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install GDAL
run: |
apt-get update
Expand Down
74 changes: 11 additions & 63 deletions app/src/app/components/ContextMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,73 +1,16 @@
import React from "react";
import { ContextMenu, Text } from "@radix-ui/themes";
import { useMapStore } from "@/app/store/mapStore";
import { useMutation } from "@tanstack/react-query";
import { patchShatterParents } from "@api/apiHandlers";
import {
BLOCK_SOURCE_ID,
BLOCK_LAYER_ID,
BLOCK_LAYER_ID_CHILD,
} from "@constants/layers";
import { ShatterResult } from "../api/apiHandlers";

export const MapContextMenu: React.FC = () => {
const mapRef = useMapStore(state => state.mapRef)
const mapDocument = useMapStore(state => state.mapDocument)
const contextMenu = useMapStore(state => state.contextMenu)
const shatterIds = useMapStore(state => state.shatterIds)
const setShatterIds = useMapStore(state => state.setShatterIds)
const setMapLock = useMapStore(state => state.setMapLock)

const patchShatter = useMutation<
ShatterResult,
void,
{ document_id: string; geoids: string[] }
>({
mutationFn: patchShatterParents,
onMutate: ({ document_id, geoids }) => {
setMapLock(true)
console.log(
`Shattering parents for ${geoids} in document ${document_id}...`,
`Locked at `, performance.now()
);
},
onError: (error) => {
console.log("Error updating assignments: ", error);
},
onSuccess: (data) => {
console.log(
`Successfully shattered parents into ${data.children.length} children`
);
// I'm not sure when this would be the case
// Perhaps, paint-shatter multiple?
// but to cover our bases
const multipleShattered = data.parents.geoids.length > 1;

setShatterIds(
shatterIds.parents,
shatterIds.children,
data.parents.geoids,
[new Set(data.children.map((child) => child.geo_id))],
multipleShattered
);
// mapRef?.current?.setFilter(BLOCK_LAYER_ID, [
// "match",
// ["get", "path"],
// data.parents.geoids, // will need to add existing filters
// false,
// true,
// ]);
},
});

const mapDocument = useMapStore((state) => state.mapDocument);
const contextMenu = useMapStore((state) => state.contextMenu);
const handleShatter = useMapStore((state) => state.handleShatter);
if (!contextMenu) return null;

const handleShatter = () => {
const handleSelect = () => {
if (!mapDocument || contextMenu?.data?.id === undefined) return;
patchShatter.mutate({
document_id: mapDocument.document_id,
geoids: [contextMenu.data.id.toString()],
});
handleShatter(mapDocument.document_id, [contextMenu.data.id.toString()]);
contextMenu.close();
};

Expand Down Expand Up @@ -96,7 +39,12 @@ export const MapContextMenu: React.FC = () => {
</Text>
</ContextMenu.Label>
)}
<ContextMenu.Item onClick={handleShatter}>Shatter</ContextMenu.Item>
<ContextMenu.Item
disabled={!mapDocument?.child_layer}
onSelect={handleSelect}
>
Shatter
</ContextMenu.Item>
</ContextMenu.Content>
</ContextMenu.Root>
);
Expand Down
89 changes: 2 additions & 87 deletions app/src/app/components/Map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,66 +6,20 @@ import maplibregl, {
import "maplibre-gl/dist/maplibre-gl.css";
import { Protocol } from "pmtiles";
import type { MutableRefObject } from "react";
import React, { useEffect, useRef, useState } from "react";
import React, { useEffect, useRef } from "react";
import { MAP_OPTIONS } from "../constants/configuration";
import {
mapEvents,
handleResetMapSelectState,
} from "../utils/events/mapEvents";
import {
BLOCK_HOVER_LAYER_ID,
BLOCK_SOURCE_ID,
INTERACTIVE_LAYERS,
} from "../constants/layers";
import { useSearchParams } from "next/navigation";
import { INTERACTIVE_LAYERS } from "../constants/layers";
import { useMapStore } from "../store/mapStore";
import {
FormatAssignments,
getDocument,
DocumentObject,
patchUpdateAssignments,
AssignmentsCreate,
getAssignments,
Assignment,
getZonePopulations,
} from "../api/apiHandlers";
import { useMutation, useQuery, skipToken } from "@tanstack/react-query";

export const MapComponent: React.FC = () => {
const searchParams = useSearchParams();
const map: MutableRefObject<Map | null> = useRef(null);
const mapContainer: MutableRefObject<HTMLDivElement | null> = useRef(null);
const [mapLoaded, setMapLoaded] = useState(false);
const mapLock = useMapStore((state) => state.mapLock);

const patchUpdates = useMutation({
mutationFn: patchUpdateAssignments,
onMutate: () => {
console.log("Updating assignments");
},
onError: (error) => {
console.log("Error updating assignments: ", error);
},
onSuccess: (data: AssignmentsCreate) => {
console.log(
`Successfully upserted ${data.assignments_upserted} assignments`
);
mapMetrics.refetch();
},
});
const freshMap = useMapStore((state) => state.freshMap);
const zoneAssignments = useMapStore((state) => state.zoneAssignments);
const loadZoneAssignments = useMapStore((state) => state.loadZoneAssignments);

const mapDocument = useMapStore((state) => state.mapDocument);
const setMapDocument = useMapStore((state) => state.setMapDocument);
const setMapRef = useMapStore((state) => state.setMapRef);
const setMapMetrics = useMapStore((state) => state.setMapMetrics);

const mapMetrics = useQuery({
queryKey: ["zonePopulations", mapDocument],
queryFn: mapDocument ? () => getZonePopulations(mapDocument) : skipToken,
});

useEffect(() => {
let protocol = new Protocol();
Expand All @@ -75,19 +29,6 @@ export const MapComponent: React.FC = () => {
};
}, []);

useEffect(() => {
const document_id = searchParams.get("document_id");
if (document_id && !useMapStore.getState().mapDocument) {
getDocument(document_id).then((res: DocumentObject) => {
setMapDocument(res);
});
}
}, [searchParams, setMapDocument]);

useEffect(() => {
setMapMetrics(mapMetrics);
}, [mapMetrics.data]);

useEffect(() => {
if (map.current || !mapContainer.current) return;

Expand All @@ -104,17 +45,7 @@ export const MapComponent: React.FC = () => {
map.current.addControl(new maplibregl.NavigationControl());

map.current.on("load", () => {
setMapLoaded(true);
setMapRef(map);
const mapDocument = useMapStore.getState().mapDocument;

if (mapDocument) {
console.log("fetching assignments");
getAssignments(mapDocument).then((res: Assignment[]) => {
console.log("got", res.length, "assignments");
loadZoneAssignments(res);
});
}
});
INTERACTIVE_LAYERS.forEach((layer) => {
mapEvents.forEach((action) => {
Expand All @@ -139,22 +70,6 @@ export const MapComponent: React.FC = () => {
};
});

/**
* send assignments to the server when zones change.
*/
useEffect(() => {
if (mapLoaded && map.current && zoneAssignments.size) {
const assignments = FormatAssignments();
patchUpdates.mutate(assignments);
}
}, [mapLoaded, zoneAssignments]);

useEffect(() => {
if (mapLoaded && map.current) {
handleResetMapSelectState(map);
}
}, [mapLoaded, freshMap]);

return (
<div
className={`h-full relative w-full flex-1 lg:h-screen
Expand Down
26 changes: 22 additions & 4 deletions app/src/app/components/sidebar/BrushSizeSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import { Slider, Flex } from "@radix-ui/themes";
import { Slider, Flex, Heading, Text } from "@radix-ui/themes";
import { useMapStore } from "../../store/mapStore";

/**
* BrushSizeSelector
* Note: right now the brush size is an arbitrary value between
* 1 and 100. This is slightly arbitrary. Should we communicate brush size
* differently or not display the brush size?
*
* @description A slider to select the brush size
* @returns {JSX.Element} The component
*/
export function BrushSizeSelector() {
const brushSize = useMapStore((state) => state.brushSize);
const setBrushSize = useMapStore((state) => state.setBrushSize);
Expand All @@ -11,16 +20,25 @@ export function BrushSizeSelector() {
};

return (
<Flex direction="row" gap="4" maxWidth="300px" style={{alignItems:'center'}}>
<h4>Brush Size</h4>
<Flex direction="row" gap="4" maxWidth="300px" mb="3" align="center">
<Heading
as="h4"
size="2"
weight="regular"
style={{ whiteSpace: "nowrap" }}
>
Brush Size
</Heading>
<Slider
defaultValue={[brushSize]}
size="2"
onValueChange={handleChangeEnd}
min={1}
max={100}
/>
{brushSize}
<Text size="2" as="span" color="gray">
{brushSize}
</Text>
</Flex>
);
}
6 changes: 2 additions & 4 deletions app/src/app/components/sidebar/ColorPicker.jsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
import React, { useState } from "react";
import { palette, color10 } from "../../constants/colors";
import { _colorScheme, colorScheme } from "../../constants/colors";
import { Button } from "@radix-ui/themes";
import { styled } from "@stitches/react";
import * as RadioGroup from "@radix-ui/react-radio-group";
import { blackA } from "@radix-ui/colors";
import { useMapStore } from "../../store/mapStore";

export function ColorPicker() {
const [color, setColor] = useState(null);
const [open, setOpen] = useState(false);
const selectedZone = useMapStore((state) => state.selectedZone);
const setSelectedZone = useMapStore((state) => state.setSelectedZone);
const setZoneAssignments = useMapStore((state) => state.setZoneAssignments);
const accumulatedGeoids = useMapStore((state) => state.accumulatedGeoids);
const resetAccumulatedBlockPopulations = useMapStore((state) => state.resetAccumulatedBlockPopulations);

const colorArray = color10;
const colorArray = colorScheme;
if (!colorArray) return null;
const handleRadioChange = (value) => {
console.log(
Expand Down
49 changes: 11 additions & 38 deletions app/src/app/components/sidebar/GerryDBViewSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,58 +1,31 @@
import { useEffect, useState } from "react";
import { useState } from "react";
import { Select } from "@radix-ui/themes";
import { getAvailableDistrictrMaps } from "../../api/apiHandlers";
import { getAvailableDistrictrMaps } from "../../utils/api/apiHandlers";
import { useMapStore } from "../../store/mapStore";
import { createMapDocument } from "../../api/apiHandlers";
import { useQuery, useMutation } from "@tanstack/react-query";
import { useRouter, usePathname, useSearchParams } from "next/navigation";
import { useQuery } from "@tanstack/react-query";
import { document } from "@/app/utils/api/mutations";

export function GerryDBViewSelector() {
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
const [limit, setLimit] = useState<number>(20);
const [limit, setLimit] = useState<number>(30);
const [offset, setOffset] = useState<number>(0);
const [selected, setSelected] = useState<string | undefined>(undefined);
const mapDocument = useMapStore((state) => state.mapDocument);
const setMapDocument = useMapStore((state) => state.setMapDocument);
const document = useMutation({
mutationFn: createMapDocument,
onMutate: () => {
console.log("Creating document");
},
onError: (error) => {
console.error("Error creating map document: ", error);
},
onSuccess: (data) => {
setMapDocument(data);
const urlParams = new URLSearchParams(searchParams.toString());
urlParams.set("document_id", data.document_id);
router.push(pathname + "?" + urlParams.toString());
},
});

const { isPending, isError, data, error } = useQuery({
queryKey: ["views", limit, offset],
queryFn: () => getAvailableDistrictrMaps(limit, offset),
});

useEffect(() => {
if (mapDocument && data) {
const selectedView = data.find(
(view) => view.gerrydb_table_name === mapDocument.gerrydb_table,
);
setSelected(selectedView?.name);
}
}, [data, mapDocument]);

const selectedView = data?.find(
(view) => view.gerrydb_table_name === mapDocument?.gerrydb_table,
);

const handleValueChange = (value: string) => {
console.log("Value changed: ", value);
const selectedDistrictrMap = data?.find((view) => view.name === value);
console.log("Selected view: ", selectedDistrictrMap);
setSelected(value);
if (
!selectedDistrictrMap ||
selectedDistrictrMap.gerrydb_table_name === document.data?.gerrydb_table
selectedDistrictrMap.gerrydb_table_name === mapDocument?.gerrydb_table
) {
console.log("No document or same document");
return;
Expand All @@ -66,7 +39,7 @@ export function GerryDBViewSelector() {
if (isError) return <div>Error loading geographies: {error.message}</div>;

return (
<Select.Root size="3" onValueChange={handleValueChange} value={selected}>
<Select.Root size="3" onValueChange={handleValueChange} value={selectedView?.name}>
<Select.Trigger placeholder="Select a geography" />
<Select.Content>
<Select.Group>
Expand Down
Loading

0 comments on commit 2a4cbe0

Please sign in to comment.