From 757451bd6b88508c917e79cdb5639f31abd6a0ce Mon Sep 17 00:00:00 2001 From: Oliver Roick Date: Thu, 19 Sep 2024 10:57:31 +1000 Subject: [PATCH 01/13] feat: Zoom to select area --- package.json | 1 + pnpm-lock.yaml | 31 +++- src/app/(home)/globe/index.tsx | 27 ++- src/app/(home)/globe/state/machine.ts | 158 ++++------------- src/app/(home)/globe/state/selectors.ts | 39 +---- src/app/(home)/globe/state/types.ts | 45 +++++ src/types/countries.d.ts | 220 ------------------------ 7 files changed, 132 insertions(+), 389 deletions(-) create mode 100644 src/app/(home)/globe/state/types.ts delete mode 100644 src/types/countries.d.ts diff --git a/package.json b/package.json index 08f5730..0804e06 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "@phosphor-icons/react": "^2.1.7", "@prisma/client": "5.15.0", "@statelyai/inspect": "^0.3.1", + "@turf/bbox": "^7.1.0", "@xstate/react": "^4.1.1", "execa": "^9.3.0", "framer-motion": "^11.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b48185a..14083c7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -38,6 +38,9 @@ importers: '@statelyai/inspect': specifier: ^0.3.1 version: 0.3.1(ws@8.17.0)(xstate@5.13.1) + '@turf/bbox': + specifier: ^7.1.0 + version: 7.1.0 '@xstate/react': specifier: ^4.1.1 version: 4.1.1(@types/react@18.3.2)(react@18.3.1)(xstate@5.13.1) @@ -1525,6 +1528,9 @@ packages: '@tsconfig/node16@1.0.4': resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + '@turf/bbox@7.1.0': + resolution: {integrity: sha512-PdWPz9tW86PD78vSZj2fiRaB8JhUHy6piSa/QXb83lucxPK+HTAdzlDQMTKj5okRCU8Ox/25IR2ep9T8NdopRA==, tarball: https://registry.npmjs.org/@turf/bbox/-/bbox-7.1.0.tgz} + '@turf/boolean-clockwise@5.1.5': resolution: {integrity: sha512-FqbmEEOJ4rU4/2t7FKx0HUWmjFEVqR+NJrFP7ymGSjja2SQ7Q91nnBihGuT+yuHHl6ElMjQ3ttsB/eTmyCycxA==} @@ -1534,12 +1540,18 @@ packages: '@turf/helpers@5.1.5': resolution: {integrity: sha512-/lF+JR+qNDHZ8bF9d+Cp58nxtZWJ3sqFe6n3u3Vpj+/0cqkjk4nXKYBSY0azm+GIYB5mWKxUXvuP/m0ZnKj1bw==} + '@turf/helpers@7.1.0': + resolution: {integrity: sha512-dTeILEUVeNbaEeoZUOhxH5auv7WWlOShbx7QSd4s0T4Z0/iz90z9yaVCtZOLbU89umKotwKaJQltBNO9CzVgaQ==, tarball: https://registry.npmjs.org/@turf/helpers/-/helpers-7.1.0.tgz} + '@turf/invariant@5.2.0': resolution: {integrity: sha512-28RCBGvCYsajVkw2EydpzLdcYyhSA77LovuOvgCJplJWaNVyJYH6BOR3HR9w50MEkPqb/Vc/jdo6I6ermlRtQA==} '@turf/meta@5.2.0': resolution: {integrity: sha512-ZjQ3Ii62X9FjnK4hhdsbT+64AYRpaI8XMBMcyftEOGSmPMUVnkbvuv3C9geuElAXfQU7Zk1oWGOcrGOD9zr78Q==} + '@turf/meta@7.1.0': + resolution: {integrity: sha512-ZgGpWWiKz797Fe8lfRj7HKCkGR+nSJ/5aKXMyofCvLSc2PuYJs/qyyifDPWjASQQCzseJ7AlF2Pc/XQ/3XkkuA==, tarball: https://registry.npmjs.org/@turf/meta/-/meta-7.1.0.tgz} + '@turf/rewind@5.1.5': resolution: {integrity: sha512-Gdem7JXNu+G4hMllQHXRFRihJl3+pNl7qY+l4qhQFxq+hiU1cQoVFnyoleIqWKIrdK/i2YubaSwc3SCM7N5mMw==} @@ -3496,7 +3508,7 @@ packages: resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} tslib@2.6.2: - resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==, tarball: https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz} tweakpane@4.0.4: resolution: {integrity: sha512-RkWD54zDlEbnN01wQPk0ANHGbdCvlJx/E8A1QxhTfCbX+ROWos1Ws2MnhOm39aUGMOh+36TjUwpDmLfmwTr1Fg==, tarball: https://registry.npmjs.org/tweakpane/-/tweakpane-4.0.4.tgz} @@ -6048,6 +6060,13 @@ snapshots: '@tsconfig/node16@1.0.4': {} + '@turf/bbox@7.1.0': + dependencies: + '@turf/helpers': 7.1.0 + '@turf/meta': 7.1.0 + '@types/geojson': 7946.0.14 + tslib: 2.6.2 + '@turf/boolean-clockwise@5.1.5': dependencies: '@turf/helpers': 5.1.5 @@ -6059,6 +6078,11 @@ snapshots: '@turf/helpers@5.1.5': {} + '@turf/helpers@7.1.0': + dependencies: + '@types/geojson': 7946.0.14 + tslib: 2.6.2 + '@turf/invariant@5.2.0': dependencies: '@turf/helpers': 5.1.5 @@ -6067,6 +6091,11 @@ snapshots: dependencies: '@turf/helpers': 5.1.5 + '@turf/meta@7.1.0': + dependencies: + '@turf/helpers': 7.1.0 + '@types/geojson': 7946.0.14 + '@turf/rewind@5.1.5': dependencies: '@turf/boolean-clockwise': 5.1.5 diff --git a/src/app/(home)/globe/index.tsx b/src/app/(home)/globe/index.tsx index 2a443f7..1350abf 100644 --- a/src/app/(home)/globe/index.tsx +++ b/src/app/(home)/globe/index.tsx @@ -38,11 +38,12 @@ function GlobeInner() { state.matches("Page is mounting") ); const pageUrl = MachineContext.useSelector(selectors.pageUrl); + const mapBounds = MachineContext.useSelector(selectors.mapBounds); // On mount, pass route parameters to the machine useEffect(() => { actorRef.send({ - type: "Page has mounted", + type: "event:page:mounted", context: { areaId: params.areaId || null }, }); }, []); @@ -54,6 +55,16 @@ function GlobeInner() { } }, [router, pageUrl, pageIsMounting]); + useEffect(() => { + if (mapRef.current && mapBounds) { + const [x1, y1, x2, z2] = mapBounds; + mapRef.current.fitBounds([x1, y1, x2, z2], { + padding: 200, + duration: 500, + }); + } + }, [mapBounds]); + const onClick = useCallback((event: MapMouseEvent) => { if (mapRef.current) { const features = mapRef.current.queryRenderedFeatures(event.point, { @@ -62,8 +73,18 @@ function GlobeInner() { if (features.length > 0) { const feature = features[0]; - if (feature?.properties?.id) { - router.push(`/area/${feature.properties.id}`); + if (feature) { + actorRef.send({ + type: "event:area:select", + area: { + type: "Feature", + geometry: feature.geometry, + properties: { + id: feature.properties.id, + name: feature.properties.name, + }, + }, + }); } } } diff --git a/src/app/(home)/globe/state/machine.ts b/src/app/(home)/globe/state/machine.ts index 243ea87..3559805 100644 --- a/src/app/(home)/globe/state/machine.ts +++ b/src/app/(home)/globe/state/machine.ts @@ -1,8 +1,6 @@ -import { - CountryCapitalsGeoJSON, - CountryLimitsGeoJSON, -} from "@/types/countries"; -import { assign, createMachine, assertEvent, fromPromise } from "xstate"; +import { bbox } from "@turf/bbox"; +import { assign, createMachine, assertEvent } from "xstate"; +import { StateContext, StateActions, StateEvents } from "./types"; export const globeViewMachine = createMachine( { @@ -10,169 +8,73 @@ export const globeViewMachine = createMachine( id: "globeView", types: { - context: {} as { - areaId: string | null; - countryLimitsGeoJSON: CountryLimitsGeoJSON | null; - countryCapitalsGeoJSON: CountryCapitalsGeoJSON | null; - }, - events: {} as - | { - type: "Page has mounted"; - context: { - areaId: string | null; - }; - } - | { - type: "Select area"; - areaId: string; - } - | { - type: "Clear area selection"; - }, - actions: {} as - | { - type: "initContext"; - context: { - areaId: string | null; - }; - } - | { - type: "setAreaId"; - areaId: string; - } - | { - type: "clearAreaId"; - } - | { - type: "assignMapData"; - countryLimitsGeojson: CountryLimitsGeoJSON; - countryCapitalsGeojson: CountryCapitalsGeoJSON; - }, + context: {} as StateContext, + events: {} as StateEvents, + actions: {} as StateActions, }, context: { areaId: null, - countryLimitsGeoJSON: null, - countryCapitalsGeoJSON: null, + mapBounds: null, }, states: { - "Map is loading": { - invoke: { - id: "fetchMapData", - src: "fetchMapData", - onError: { - target: "Unexpected error", - }, - onDone: { - target: "Initial globe view", - actions: assign({ - countryLimitsGeoJSON: ({ event }) => - event.output.countryLimitsGeojson, - countryCapitalsGeoJSON: ({ event }) => - event.output.countryCapitalsGeojson, - }), - }, - }, - }, - - "Initial globe view": { + initial: { on: { - "Select area": { - target: "Country is selected", - actions: "setAreaId", + "event:area:select": { + target: "area:selected", + actions: "action:area:select", }, }, }, - "Country is selected": { + "area:selected": { on: { - "Clear area selection": { - target: "Initial globe view", - actions: "clearAreaId", + "event:area:clear": { + target: "initial", + actions: "action:area:clear", }, - "Select area": { - target: "Country is selected", - actions: "setAreaId", + "event:area:select": { + target: "area:selected", + actions: "action:area:select", }, }, }, "Unexpected error": {}, - "Page is mounting": { + "page:mounting": { on: { - "Page has mounted": { - target: "Map is loading", + "event:page:mounted": { + target: "initial", reenter: true, - actions: "initContext", + actions: "action:initializeContext", }, }, }, }, - initial: "Page is mounting", + initial: "page:mounting", }, { actions: { - initContext: assign(({ event }) => { - assertEvent(event, "Page has mounted"); + "action:initializeContext": assign(({ event }) => { + assertEvent(event, "event:page:mounted"); return { ...event.context, }; }), - setAreaId: assign(({ event }) => { - assertEvent(event, "Select area"); + "action:area:select": assign(({ event }) => { + assertEvent(event, "event:area:select"); return { - areaId: event.areaId, + areaId: event.area.properties.id, + mapBounds: bbox(event.area), }; }), - clearAreaId: assign({ + "action:area:clear": assign({ areaId: undefined, }), }, - actors: { - fetchMapData: fromPromise(async () => { - const countryLimitsGeojson = await fetch( - "/naturalearth-3.3.0/ne_50m_admin_0_countries.geojson" - ) - .then((response) => response.json() as Promise) - .then((data) => ({ - ...data, - features: data.features - .map((feature) => ({ - ...feature, - properties: { - ...feature.properties, - id: feature.properties.iso_a2, - }, - })) - .filter((feature) => feature.properties.id !== "-99"), - })); - - const countryCapitalsGeojson = await fetch( - "/naturalearth-3.3.0/ne_50m_populated_places_adm0cap.geojson" - ) - .then( - (response) => response.json() as Promise - ) - .then((data) => ({ - ...data, - features: data.features.map((feature) => ({ - ...feature, - properties: { - ...feature.properties, - id: feature.properties.ISO_A2, - }, - })), - })); - - return { - countryLimitsGeojson, - countryCapitalsGeojson, - }; - }), - }, } ); diff --git a/src/app/(home)/globe/state/selectors.ts b/src/app/(home)/globe/state/selectors.ts index 3637d93..2d8660d 100644 --- a/src/app/(home)/globe/state/selectors.ts +++ b/src/app/(home)/globe/state/selectors.ts @@ -6,45 +6,10 @@ type Snapshot = SnapshotFrom; export const selectors = { pageUrl: (state: Snapshot) => { const { areaId } = state.context; - return areaId ? `/area/${areaId}` : `/`; }, - currentCountryLimit: (state: Snapshot) => { - const { areaId, countryLimitsGeoJSON } = state.context; - - if (!areaId || !countryLimitsGeoJSON) return null; - - return countryLimitsGeoJSON.features.find( - (f) => f.properties.id === areaId - ); - }, - currentCountryArcs: (state: Snapshot) => { - const { areaId, countryCapitalsGeoJSON } = state.context; - - if (!areaId || !countryCapitalsGeoJSON) return null; - - const originCountry = countryCapitalsGeoJSON.features.find( - (f) => f.properties.id === areaId - ); - - if (!originCountry) { - return null; - } - - const targetCountries = countryCapitalsGeoJSON.features - .filter((feature) => feature.properties.id !== areaId) - .sort(() => Math.random() - 0.5) - .slice(0, 10); - - if (targetCountries.length === 0) { - return null; - } - - const arcs = targetCountries.map((feature) => ({ - sourcePosition: originCountry?.geometry.coordinates, - targetPosition: feature.geometry.coordinates, - })); - return arcs; + mapBounds: (state: Snapshot) => { + return state.context.mapBounds; }, }; diff --git a/src/app/(home)/globe/state/types.ts b/src/app/(home)/globe/state/types.ts new file mode 100644 index 0000000..2ab5d97 --- /dev/null +++ b/src/app/(home)/globe/state/types.ts @@ -0,0 +1,45 @@ +import { ProductionArea } from "@/types/data"; +import { BBox, Feature, Geometry } from "geojson"; + +interface EventPageMount { + type: "event:page:mounted"; + context: { + areaId: string | null; + }; +} + +interface EventAreaSelect { + type: "event:area:select"; + area: Feature; +} + +interface EventAreaClear { + type: "event:area:clear"; +} + +export type StateEvents = EventPageMount | EventAreaSelect | EventAreaClear; + +interface ActionInitContext { + type: "action:initializeContext"; + context: { + areaId: string | null; + }; +} +interface ActionAreaSelect { + type: "action:area:select"; + areaId: string; + mapBounds: BBox | null; +} +interface ActionAreaClear { + type: "action:area:clear"; +} + +export type StateActions = + | ActionInitContext + | ActionAreaSelect + | ActionAreaClear; + +export interface StateContext { + areaId: string | null; + mapBounds: BBox | null; +} diff --git a/src/types/countries.d.ts b/src/types/countries.d.ts deleted file mode 100644 index fa24efc..0000000 --- a/src/types/countries.d.ts +++ /dev/null @@ -1,220 +0,0 @@ -import type { Feature, FeatureCollection, Point, Polygon } from "geojson"; - -type CountryLimitFeature = Feature; - -export interface CountryLimitsGeoJSON extends FeatureCollection { - features: CountryLimitFeature[]; -} -interface CountryLimitProperties { - id: string; - scalerank: number; - labelrank: number; - sovereignt: string; - sov_a3: string; - adm0_dif: number; - level: number; - type: string; - admin: string; - adm0_a3: string; - geou_dif: number; - geounit: string; - gu_a3: string; - su_dif: number; - subunit: string; - su_a3: string; - brk_diff: number; - name: string; - name_long: string; - brk_a3: string; - brk_name: string; - brk_group: null | string; - abbrev: string; - postal: string; - formal_en: string | null; - formal_fr: string | null; - note_adm0: string | null; - note_brk: string | null; - name_sort: string; - name_alt: string | null; - mapcolor7: number; - mapcolor8: number; - mapcolor9: number; - mapcolor13: number; - pop_est: number; - gdp_md_est: number; - pop_year: number; - lastcensus: number; - gdp_year: number; - economy: string; - income_grp: string; - wikipedia: number; - fips_10: string | null; - iso_a2: string; - iso_a3: string; - iso_n3: string; - un_a3: string; - wb_a2: string; - wb_a3: string; - woe_id: number; - adm0_a3_is: string; - adm0_a3_us: string; - adm0_a3_un: number; - adm0_a3_wb: number; - continent: string; - region_un: string; - subregion: string; - region_wb: string; - name_len: number; - long_len: number; - abbrev_len: number; - tiny: number; - homepart: number; - featureclass: string; -} - -export type CountryCapitalFeature = Feature; - -export interface CountryCapitalsGeoJSON extends FeatureCollection { - features: CountryCapitalFeature[]; -} - -interface CountryCapitalProperties { - id: string; - SCALERANK: number; - NATSCALE: number; - LABELRANK: number; - FEATURECLA: string; - NAME: string; - NAMEPAR: string | null; - NAMEALT: string | null; - NAMEASCII: string; - ADM0CAP: number; - CAPIN: string | null; - WORLDCITY: number; - MEGACITY: number; - SOV0NAME: string; - SOV_A3: string; - ADM0NAME: string; - ADM0_A3: string; - ADM1NAME: string; - ISO_A2: string; - NOTE: string | null; - LATITUDE: number; - LONGITUDE: number; - POP_MAX: number; - POP_MIN: number; - POP_OTHER: number; - RANK_MAX: number; - RANK_MIN: number; - MEGANAME: string | null; - LS_NAME: string; - MAX_POP10: number; - MAX_POP20: number; - MAX_POP50: number; - MAX_POP300: number; - MAX_POP310: number; - MAX_NATSCA: number; - MIN_AREAKM: number; - MAX_AREAKM: number; - MIN_AREAMI: number; - MAX_AREAMI: number; - MIN_PERKM: number; - MAX_PERKM: number; - MIN_PERMI: number; - MAX_PERMI: number; - MIN_BBXMIN: number; - MAX_BBXMIN: number; - MIN_BBXMAX: number; - MAX_BBXMAX: number; - MIN_BBYMIN: number; - MAX_BBYMIN: number; - MIN_BBYMAX: number; - MAX_BBYMAX: number; - MEAN_BBXC: number; - MEAN_BBYC: number; - TIMEZONE: string; - UN_FID: number; - POP1950: number; - POP1955: number; - POP1960: number; - POP1965: number; - POP1970: number; - POP1975: number; - POP1980: number; - POP1985: number; - POP1990: number; - POP1995: number; - POP2000: number; - POP2005: number; - POP2010: number; - POP2015: number; - POP2020: number; - POP2025: number; - POP2050: number; - MIN_ZOOM: number; - WIKIDATAID: string; - WOF_ID: number; - CAPALT: number; - NAME_EN: string; - NAME_DE: string; - NAME_ES: string; - NAME_FR: string; - NAME_PT: string; - NAME_RU: string; - NAME_ZH: string; - LABEL: string | null; - NAME_AR: string; - NAME_BN: string; - NAME_EL: string; - NAME_HI: string; - NAME_HU: string; - NAME_ID: string; - NAME_IT: string; - NAME_JA: string; - NAME_KO: string; - NAME_NL: string; - NAME_PL: string; - NAME_SV: string; - NAME_TR: string; - NAME_VI: string; - NE_ID: number; - NAME_FA: string; - NAME_HE: string; - NAME_UK: string; - NAME_UR: string; - NAME_ZHT: string; - GEONAMESID: number; - FCLASS_ISO: string | null; - FCLASS_US: string | null; - FCLASS_FR: string | null; - FCLASS_RU: string | null; - FCLASS_ES: string | null; - FCLASS_CN: string | null; - FCLASS_TW: string | null; - FCLASS_IN: string | null; - FCLASS_NP: string | null; - FCLASS_PK: string | null; - FCLASS_DE: string | null; - FCLASS_GB: string | null; - FCLASS_BR: string | null; - FCLASS_IL: string | null; - FCLASS_PS: string | null; - FCLASS_SA: string | null; - FCLASS_EG: string | null; - FCLASS_MA: string | null; - FCLASS_PT: string | null; - FCLASS_AR: string | null; - FCLASS_JP: string | null; - FCLASS_KO: string | null; - FCLASS_VN: string | null; - FCLASS_TR: string | null; - FCLASS_ID: string | null; - FCLASS_PL: string | null; - FCLASS_GR: string | null; - FCLASS_IT: string | null; - FCLASS_NL: string | null; - FCLASS_SE: string | null; - FCLASS_BD: string | null; - FCLASS_UA: string | null; - FCLASS_TLC: string | null; -} From 416ccef564e07c80d6288bda22175c040d17dbde Mon Sep 17 00:00:00 2001 From: Vitor George Date: Thu, 19 Sep 2024 13:03:58 +0100 Subject: [PATCH 02/13] Use filter by id approach, fix spelling --- src/app/(home)/globe/index.tsx | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/app/(home)/globe/index.tsx b/src/app/(home)/globe/index.tsx index 1350abf..14b85ba 100644 --- a/src/app/(home)/globe/index.tsx +++ b/src/app/(home)/globe/index.tsx @@ -22,7 +22,7 @@ import EdgeLayer from "./layers/edges"; const appUrl = process.env.NEXT_PUBLIC_APP_URL; -interface IHighligthArea extends Feature { +interface IHighlightArea extends Feature { popupLocation: LngLat; } @@ -31,7 +31,7 @@ function GlobeInner() { const params = useParams<{ areaId: string }>(); const actorRef = MachineContext.useActorRef(); const mapRef = useRef(null); - const [highlightArea, setHighlightArea] = useState(); + const [highlightArea, setHighlightArea] = useState(); // Selectors const pageIsMounting = MachineContext.useSelector((state) => @@ -102,7 +102,7 @@ function GlobeInner() { setHighlightArea({ ...interactiveItem.toJSON(), popupLocation: event.lngLat, - } as IHighligthArea); + } as IHighlightArea); } else { setHighlightArea(undefined); } @@ -173,19 +173,19 @@ function GlobeInner() { "fill-color": "rgba(250, 250, 249, 0.3)", // Transparent blue }} /> - - - {highlightArea && ( - + {highlightArea?.properties?.id && ( - - )} + )} + Date: Mon, 23 Sep 2024 11:51:48 +0100 Subject: [PATCH 03/13] Use feature state to set hover, remove nodes layer --- src/app/(home)/globe/index.tsx | 71 ++++++++++---------- src/app/api/tiles/areas/[z]/[x]/[y]/route.ts | 3 +- 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/src/app/(home)/globe/index.tsx b/src/app/(home)/globe/index.tsx index 14b85ba..924f748 100644 --- a/src/app/(home)/globe/index.tsx +++ b/src/app/(home)/globe/index.tsx @@ -7,22 +7,29 @@ import Map, { MapRef, MapMouseEvent, MapLayerMouseEvent, + MapGeoJSONFeature, } from "react-map-gl/maplibre"; import "maplibre-gl/dist/maplibre-gl.css"; import { LngLat } from "react-map-gl"; import { EItemType } from "@/types/components"; import MapPopup from "@/app/components/map-popup"; -import { ProductionArea } from "@/types/data"; import { MachineContext, MachineProvider } from "./state"; import { selectors } from "./state/selectors"; -import { Feature, Polygon } from "geojson"; import EdgeLayer from "./layers/edges"; const appUrl = process.env.NEXT_PUBLIC_APP_URL; -interface IHighlightArea extends Feature { +// Colors +const SEA_BLUE = "rgb(173, 216, 230)"; +const AREA_HIGHLIGHT_COLOR = "rgba(250, 250, 249, 0.7)"; +const AREA_DEFAULT_COLOR = "rgba(250, 250, 249, 0.3)"; +const AREA_HIGHLIGHT_OUTLINE_COLOR = "rgba(0, 0, 0, 1)"; +const AREA_DEFAULT_OUTLINE_COLOR = "rgba(0, 0, 0, 0.3)"; + +interface IHighlightArea { + feature: MapGeoJSONFeature; popupLocation: LngLat; } @@ -98,16 +105,24 @@ function GlobeInner() { }); const interactiveItem = features && features[0]; + + // Clear current highlighted area, if any + if (highlightArea && highlightArea.feature !== interactiveItem) { + mapRef.current.setFeatureState(highlightArea.feature, { hover: false }); + } + + // Handle the current item under the mouse if (interactiveItem) { + mapRef.current.setFeatureState(interactiveItem, { hover: true }); setHighlightArea({ - ...interactiveItem.toJSON(), + feature: interactiveItem, popupLocation: event.lngLat, } as IHighlightArea); } else { setHighlightArea(undefined); } }, - [setHighlightArea] + [highlightArea, setHighlightArea] ); return ( @@ -149,7 +164,7 @@ function GlobeInner() { id="background-layer" type="fill" paint={{ - "fill-color": "rgb(173, 216, 230)", // Sea blue color + "fill-color": SEA_BLUE, }} /> @@ -170,34 +185,18 @@ function GlobeInner() { type="fill" source-layer="default" paint={{ - "fill-color": "rgba(250, 250, 249, 0.3)", // Transparent blue - }} - /> - {highlightArea?.properties?.id && ( - - )} - - - - @@ -206,9 +205,9 @@ function GlobeInner() { {highlightArea && ( diff --git a/src/app/api/tiles/areas/[z]/[x]/[y]/route.ts b/src/app/api/tiles/areas/[z]/[x]/[y]/route.ts index c6756e8..c25e021 100644 --- a/src/app/api/tiles/areas/[z]/[x]/[y]/route.ts +++ b/src/app/api/tiles/areas/[z]/[x]/[y]/route.ts @@ -30,9 +30,10 @@ export async function GET( const yNum = Number(y); const query = ` - SELECT ST_AsMVT(tile) FROM ( + SELECT ST_AsMVT(tile, 'default', 4096, 'geom', 'id_int') FROM ( SELECT id, + ((ctid::text::point)[0]::bigint << 32) | (ctid::text::point)[1]::bigint AS id_int, name, ST_AsMVTGeom( limits, From 71fdeddff0a60c16226607b6fbee1e09be974bdc Mon Sep 17 00:00:00 2001 From: Vitor George Date: Mon, 23 Sep 2024 12:20:28 +0100 Subject: [PATCH 04/13] Move map to components folder --- src/app/(home)/layout.tsx | 4 ++-- src/app/{(home)/globe => components/map}/index.tsx | 0 src/app/{(home)/globe => components/map}/layers/edges.tsx | 0 src/app/{(home)/globe => components/map}/state/index.tsx | 0 src/app/{(home)/globe => components/map}/state/machine.ts | 0 src/app/{(home)/globe => components/map}/state/selectors.ts | 0 src/app/{(home)/globe => components/map}/state/types.ts | 0 7 files changed, 2 insertions(+), 2 deletions(-) rename src/app/{(home)/globe => components/map}/index.tsx (100%) rename src/app/{(home)/globe => components/map}/layers/edges.tsx (100%) rename src/app/{(home)/globe => components/map}/state/index.tsx (100%) rename src/app/{(home)/globe => components/map}/state/machine.ts (100%) rename src/app/{(home)/globe => components/map}/state/selectors.ts (100%) rename src/app/{(home)/globe => components/map}/state/types.ts (100%) diff --git a/src/app/(home)/layout.tsx b/src/app/(home)/layout.tsx index ec73cca..44dcaac 100644 --- a/src/app/(home)/layout.tsx +++ b/src/app/(home)/layout.tsx @@ -1,5 +1,5 @@ import Menu from "../components/menu"; -import Globe from "./globe"; +import Map from "../components/map"; import WelcomeModal from "./welcome-modal"; export default function Layout({ children }: { children: React.ReactNode }) { @@ -7,7 +7,7 @@ export default function Layout({ children }: { children: React.ReactNode }) { <>
- + {children}
diff --git a/src/app/(home)/globe/index.tsx b/src/app/components/map/index.tsx similarity index 100% rename from src/app/(home)/globe/index.tsx rename to src/app/components/map/index.tsx diff --git a/src/app/(home)/globe/layers/edges.tsx b/src/app/components/map/layers/edges.tsx similarity index 100% rename from src/app/(home)/globe/layers/edges.tsx rename to src/app/components/map/layers/edges.tsx diff --git a/src/app/(home)/globe/state/index.tsx b/src/app/components/map/state/index.tsx similarity index 100% rename from src/app/(home)/globe/state/index.tsx rename to src/app/components/map/state/index.tsx diff --git a/src/app/(home)/globe/state/machine.ts b/src/app/components/map/state/machine.ts similarity index 100% rename from src/app/(home)/globe/state/machine.ts rename to src/app/components/map/state/machine.ts diff --git a/src/app/(home)/globe/state/selectors.ts b/src/app/components/map/state/selectors.ts similarity index 100% rename from src/app/(home)/globe/state/selectors.ts rename to src/app/components/map/state/selectors.ts diff --git a/src/app/(home)/globe/state/types.ts b/src/app/components/map/state/types.ts similarity index 100% rename from src/app/(home)/globe/state/types.ts rename to src/app/components/map/state/types.ts From 503beaa7a79d074b2d924aa18013f0ca3c0784e4 Mon Sep 17 00:00:00 2001 From: Vitor George Date: Mon, 23 Sep 2024 12:22:46 +0100 Subject: [PATCH 05/13] Fix diagram layout, clear unused state --- src/app/components/map/state/machine.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/app/components/map/state/machine.ts b/src/app/components/map/state/machine.ts index 3559805..e784c26 100644 --- a/src/app/components/map/state/machine.ts +++ b/src/app/components/map/state/machine.ts @@ -4,7 +4,7 @@ import { StateContext, StateActions, StateEvents } from "./types"; export const globeViewMachine = createMachine( { - /** @xstate-layout N4IgpgJg5mDOIC5RQDYHsBGYBqBLMA7gHQCyAhgA4AEusV6ZEuAdlAMQRrNhEsBuaANY8AZmAAuAYwAW5CgBEy4sgG0ADAF1EoCmli5xuLtpAAPRAEYALAFYiAThsB2J-YAc9gGxqATF4DMTgA0IACeiAC0bhZE-jZuTv7+9u5uVm7+VgC+WSGomDj4xHI0dAxMrGxgAE7VaNVEFChKIvUAtkRiUrKUisrqWkgguvqGxkPmCMluRBY2VmrWFvZWPpkWIeEIERZunkRONn7RLnE+Pp6eOXnoWHiERACSzAa4ZChU+VhUfEVsAMpgFBgSTiKhkapgVSaEwjV7jUCTNSbRBqa4gL6FB4AYTQAFdmOJqqFSlRYECQeJIGxscCIeDIWQyRTQUZmANYXp4cwTEiUQg0bkMbcscRcQSiSTaMzgaDqYDZWCIVCOUM4WMeRNIv41ERPPZrGorPYfK5PLZTfyrBYnEQrFYktavJ4-D5skLMfdiAAFMgwUltfGEljsX3+6RkOiBiWQVU6Lka3mWWx2zwWQILNxpE2+fkRHUzfwZOIm2zufwWHJC5hoCBwEyeoqc0ZspPbJwzfWG42mry2fx59IzNbWLNqOL2xLoxsPErS8oh5vcttOKx6qwu+xnCxqRIrPM+CwxNzxFw2LwWQ+Vj0ir1PF6Gd6fEU-JtqhOtrUIOb+IjRfz6moQEdm6-KOH+q6eB2HhFrs0TTreRREOKhLEqS5KKpAS6Jl+FjeAc45xJcZ5dvySQxFu5xOD4ajeOOThXDeBR3gAqtwpgUJSkBUDUdTVNhn6IogdGzCsiw2EciwrAOYSRHBRA2LslHxMaUE2IxNzMUhYZgAGQaGKwAkImYlhHEQtELDqW7uE44mDos5n2jRajOPMyzujkQA */ + /** @xstate-layout N4IgpgJg5mDOIC5RQDYHsBGYBqBLMA7gHS4B2uALrgIYoDEYAbmKRQtQE5jUKxgpgAxhQDaABgC6iUAAc0sSrjSlpIAB6IAjAE4AbETFiALACYjADnOmA7GO2aAzABoQAT0QBaTZv1jzusU0AVmCHTXMg62sAX2iXVEwcfGJObl5+IQpIBmZWdi4eQQFOcSkkEDkFKmVVDQQPByJrcOswyN0TB21m7Rd3eu9ff0CQoLCI611Y+PQsPEIiVJ4+AWFsphY2JfTV0UlVSsUa8rrNE2siHyNNMSCg3V0HMWbrPq0HIKJza3NHbzHzH5NNMQAk5skiDJqDAEABbNAAV1YZCgOU2CChMPhSKyEFKB3kRxUJ08JjERAczXMlPuZiCt10QTe9QcHQMH10Okeula1jMsTiIFIaAgcFUYKShAJVSUxNAdQ8unMFKpNI6RnpkWZHjGRiIRl02j813M2iCgIcIIl82IZEUtGlRNqiEpBnMJiC3Q9ZgclO1Jk0ep+Su8H2e1w+VtmkpSBR2mUgjuqcvUiAuYUpmkmRlMfjJrzcWnJrW09kCWZzjMcUcSNsh0LAcMRyNIUCTsudCE0XzC10DhuMXSMem1OYuJl+gPp4TD5wF0SAA */ id: "globeView", types: { @@ -42,8 +42,6 @@ export const globeViewMachine = createMachine( }, }, - "Unexpected error": {}, - "page:mounting": { on: { "event:page:mounted": { From aacb20814146fd0b5b8649e8497601156d8a123c Mon Sep 17 00:00:00 2001 From: Vitor George Date: Mon, 23 Sep 2024 15:02:53 +0100 Subject: [PATCH 06/13] Move events and actions types to separated files for maintainability --- src/app/components/map/state/machine.ts | 9 +++- src/app/components/map/state/types.ts | 45 ------------------- src/app/components/map/state/types/actions.ts | 21 +++++++++ src/app/components/map/state/types/events.ts | 20 +++++++++ 4 files changed, 49 insertions(+), 46 deletions(-) delete mode 100644 src/app/components/map/state/types.ts create mode 100644 src/app/components/map/state/types/actions.ts create mode 100644 src/app/components/map/state/types/events.ts diff --git a/src/app/components/map/state/machine.ts b/src/app/components/map/state/machine.ts index e784c26..afe1e1c 100644 --- a/src/app/components/map/state/machine.ts +++ b/src/app/components/map/state/machine.ts @@ -1,6 +1,13 @@ import { bbox } from "@turf/bbox"; import { assign, createMachine, assertEvent } from "xstate"; -import { StateContext, StateActions, StateEvents } from "./types"; +import { StateEvents } from "./types/events"; +import { StateActions } from "./types/actions"; +import { BBox } from "geojson"; + +interface StateContext { + areaId: string | null; + mapBounds: BBox | null; +} export const globeViewMachine = createMachine( { diff --git a/src/app/components/map/state/types.ts b/src/app/components/map/state/types.ts deleted file mode 100644 index 2ab5d97..0000000 --- a/src/app/components/map/state/types.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { ProductionArea } from "@/types/data"; -import { BBox, Feature, Geometry } from "geojson"; - -interface EventPageMount { - type: "event:page:mounted"; - context: { - areaId: string | null; - }; -} - -interface EventAreaSelect { - type: "event:area:select"; - area: Feature; -} - -interface EventAreaClear { - type: "event:area:clear"; -} - -export type StateEvents = EventPageMount | EventAreaSelect | EventAreaClear; - -interface ActionInitContext { - type: "action:initializeContext"; - context: { - areaId: string | null; - }; -} -interface ActionAreaSelect { - type: "action:area:select"; - areaId: string; - mapBounds: BBox | null; -} -interface ActionAreaClear { - type: "action:area:clear"; -} - -export type StateActions = - | ActionInitContext - | ActionAreaSelect - | ActionAreaClear; - -export interface StateContext { - areaId: string | null; - mapBounds: BBox | null; -} diff --git a/src/app/components/map/state/types/actions.ts b/src/app/components/map/state/types/actions.ts new file mode 100644 index 0000000..bce15c3 --- /dev/null +++ b/src/app/components/map/state/types/actions.ts @@ -0,0 +1,21 @@ +import { BBox } from "geojson"; + +interface ActionInitContext { + type: "action:initializeContext"; + context: { + areaId: string | null; + }; +} +interface ActionAreaSelect { + type: "action:area:select"; + areaId: string; + mapBounds: BBox | null; +} +interface ActionAreaClear { + type: "action:area:clear"; +} + +export type StateActions = + | ActionInitContext + | ActionAreaSelect + | ActionAreaClear; diff --git a/src/app/components/map/state/types/events.ts b/src/app/components/map/state/types/events.ts new file mode 100644 index 0000000..cfb8952 --- /dev/null +++ b/src/app/components/map/state/types/events.ts @@ -0,0 +1,20 @@ +import { ProductionArea } from "@/types/data"; +import { Feature, Geometry } from "geojson"; + +interface EventPageMount { + type: "event:page:mounted"; + context: { + areaId: string | null; + }; +} + +interface EventAreaSelect { + type: "event:area:select"; + area: Feature; +} + +interface EventAreaClear { + type: "event:area:clear"; +} + +export type StateEvents = EventPageMount | EventAreaSelect | EventAreaClear; From ba2af0a5bf9651d4377c337291954640d327cd14 Mon Sep 17 00:00:00 2001 From: Vitor George Date: Mon, 23 Sep 2024 15:17:58 +0100 Subject: [PATCH 07/13] Add map ref to machine context --- src/app/components/map/index.tsx | 18 +++++++++++++++--- src/app/components/map/state/machine.ts | 18 ++++++++++++++---- src/app/components/map/state/types/actions.ts | 8 ++++++++ src/app/components/map/state/types/events.ts | 14 ++++++++++++-- 4 files changed, 49 insertions(+), 9 deletions(-) diff --git a/src/app/components/map/index.tsx b/src/app/components/map/index.tsx index 924f748..71c4f2f 100644 --- a/src/app/components/map/index.tsx +++ b/src/app/components/map/index.tsx @@ -41,8 +41,11 @@ function GlobeInner() { const [highlightArea, setHighlightArea] = useState(); // Selectors - const pageIsMounting = MachineContext.useSelector((state) => - state.matches("Page is mounting") + const pageIsMounting = MachineContext.useSelector((s) => + s.matches("page:mounting") + ); + const mapIsMounting = MachineContext.useSelector((s) => + s.matches("map:mounting") ); const pageUrl = MachineContext.useSelector(selectors.pageUrl); const mapBounds = MachineContext.useSelector(selectors.mapBounds); @@ -50,11 +53,20 @@ function GlobeInner() { // On mount, pass route parameters to the machine useEffect(() => { actorRef.send({ - type: "event:page:mounted", + type: "event:page:mount", context: { areaId: params.areaId || null }, }); }, []); + useEffect(() => { + if (mapIsMounting && mapRef.current) { + actorRef.send({ + type: "event:map:mount", + mapRef: mapRef.current, + }); + } + }, [mapIsMounting, mapRef.current]); + // Update the URL when necessary useEffect(() => { if (!pageIsMounting) { diff --git a/src/app/components/map/state/machine.ts b/src/app/components/map/state/machine.ts index afe1e1c..806730d 100644 --- a/src/app/components/map/state/machine.ts +++ b/src/app/components/map/state/machine.ts @@ -11,7 +11,7 @@ interface StateContext { export const globeViewMachine = createMachine( { - /** @xstate-layout N4IgpgJg5mDOIC5RQDYHsBGYBqBLMA7gHS4B2uALrgIYoDEYAbmKRQtQE5jUKxgpgAxhQDaABgC6iUAAc0sSrjSlpIAB6IAjAE4AbETFiALACYjADnOmA7GO2aAzABoQAT0QBaTZv1jzusU0AVmCHTXMg62sAX2iXVEwcfGJObl5+IQpIBmZWdi4eQQFOcSkkEDkFKmVVDQQPByJrcOswyN0TB21m7Rd3eu9ff0CQoLCI611Y+PQsPEIiVJ4+AWFsphY2JfTV0UlVSsUa8rrNE2siHyNNMSCg3V0HMWbrPq0HIKJza3NHbzHzH5NNMQAk5skiDJqDAEABbNAAV1YZCgOU2CChMPhSKyEFKB3kRxUJ08JjERAczXMlPuZiCt10QTe9QcHQMH10Okeula1jMsTiIFIaAgcFUYKShAJVSUxNAdQ8unMFKpNI6RnpkWZHjGRiIRl02j813M2iCgIcIIl82IZEUtGlRNqiEpBnMJiC3Q9ZgclO1Jk0ep+Su8H2e1w+VtmkpSBR2mUgjuqcvUiAuYUpmkmRlMfjJrzcWnJrW09kCWZzjMcUcSNsh0LAcMRyNIUCTsudCE0XzC10DhuMXSMem1OYuJl+gPp4TD5wF0SAA */ + /** @xstate-layout N4IgpgJg5mDOIC5RQDYHsBGYBqBLMA7gHS4B2uALrgIYoDEYAbmKRQtQE5jUKxgpgAxhQDaABgC6iUAAc0sSrjSlpIAB6IArAE4A7EQDMBgCwAmYwYAclg7u0BGYwBoQAT0SX7Re5d3ndYtqaAGyWYpoAvhEuqJg4+MSc3Lz8QhSQDMys7Fw8ggKc4lJIIHIKVMqqGgg6moYW9poBwYE+li7uCPa6wUSapi1GA76aBqaWUTHoWHiEREk8fALCGUwsbAspy6KSqmWKlSXVxvamRMZiBsHGOmPBpprGuh2IDkRi45f23WH2HxPRECxGYJIgyagwBAAWzQAFdWGQoJl1ghwZCYfCdsVZPIDiojohTOE+r5TOYxI0xnoXghTLovJZrp5tMYTpYBpMgdN4nModQZNC4QjSEi1tk+QKMawintcRV8aBqrV6gZGs1WtYaXSGUz7Cy2RzOaQ0BA4KpgTyCLLykoFepEABaYI0p2ci2zYhkRS0a14qqIOx1OlXSyaD7aMTBXQGLWh7zXTT9HxmTw2N3cj3zXJbNKQX3y-0IMz6EJEoI9MxR51uRB6rxXUL9EwPBzBdNxTNosCCzGI-O2wtmLUsoi6Xxjkw9VPGdsg3n8nvCqD9w6KxBmYznMKaP6mAws0zabTDzdj3Q9XQWCMWNtRCJAA */ id: "globeView", types: { @@ -51,13 +51,23 @@ export const globeViewMachine = createMachine( "page:mounting": { on: { - "event:page:mounted": { - target: "initial", + "event:page:mount": { + target: "map:mounting", reenter: true, actions: "action:initializeContext", }, }, }, + + "map:mounting": { + on: { + "event:map:mount": { + target: "initial", + reenter: true, + actions: "action:setMapRef", + }, + }, + }, }, initial: "page:mounting", @@ -65,7 +75,7 @@ export const globeViewMachine = createMachine( { actions: { "action:initializeContext": assign(({ event }) => { - assertEvent(event, "event:page:mounted"); + assertEvent(event, "event:page:mount"); return { ...event.context, }; diff --git a/src/app/components/map/state/types/actions.ts b/src/app/components/map/state/types/actions.ts index bce15c3..612019c 100644 --- a/src/app/components/map/state/types/actions.ts +++ b/src/app/components/map/state/types/actions.ts @@ -1,4 +1,5 @@ import { BBox } from "geojson"; +import { MapRef } from "react-map-gl"; interface ActionInitContext { type: "action:initializeContext"; @@ -6,6 +7,12 @@ interface ActionInitContext { areaId: string | null; }; } + +interface ActionSetMapRef { + type: "action:setMapRef"; + mapRef: MapRef; +} + interface ActionAreaSelect { type: "action:area:select"; areaId: string; @@ -17,5 +24,6 @@ interface ActionAreaClear { export type StateActions = | ActionInitContext + | ActionSetMapRef | ActionAreaSelect | ActionAreaClear; diff --git a/src/app/components/map/state/types/events.ts b/src/app/components/map/state/types/events.ts index cfb8952..80715e2 100644 --- a/src/app/components/map/state/types/events.ts +++ b/src/app/components/map/state/types/events.ts @@ -1,13 +1,19 @@ import { ProductionArea } from "@/types/data"; import { Feature, Geometry } from "geojson"; +import { MapRef } from "react-map-gl/maplibre"; interface EventPageMount { - type: "event:page:mounted"; + type: "event:page:mount"; context: { areaId: string | null; }; } +interface EventMapMount { + type: "event:map:mount"; + mapRef: MapRef; +} + interface EventAreaSelect { type: "event:area:select"; area: Feature; @@ -17,4 +23,8 @@ interface EventAreaClear { type: "event:area:clear"; } -export type StateEvents = EventPageMount | EventAreaSelect | EventAreaClear; +export type StateEvents = + | EventPageMount + | EventMapMount + | EventAreaSelect + | EventAreaClear; From 398bba70d5b88187e348412f99504cc1b0171481 Mon Sep 17 00:00:00 2001 From: Vitor George Date: Mon, 23 Sep 2024 18:13:00 +0100 Subject: [PATCH 08/13] Migrate mousemove events to state --- src/app/components/map-popup.tsx | 2 +- src/app/components/map/index.tsx | 73 +++++++------------ src/app/components/map/state/machine.ts | 67 ++++++++++++++++- src/app/components/map/state/types/actions.ts | 11 ++- src/app/components/map/state/types/events.ts | 7 ++ 5 files changed, 109 insertions(+), 51 deletions(-) diff --git a/src/app/components/map-popup.tsx b/src/app/components/map-popup.tsx index 27d4661..277610a 100644 --- a/src/app/components/map-popup.tsx +++ b/src/app/components/map-popup.tsx @@ -4,7 +4,7 @@ import { EItemType } from "@/types/components"; import TypeIcon from "./icons/type-icon"; import "./css/popup.css"; -interface IMapPopup { +export interface IMapPopup { id: string; label: string; itemType: EItemType; diff --git a/src/app/components/map/index.tsx b/src/app/components/map/index.tsx index 71c4f2f..d007cf4 100644 --- a/src/app/components/map/index.tsx +++ b/src/app/components/map/index.tsx @@ -1,5 +1,5 @@ "use client"; -import React, { useEffect, useCallback, useRef, useState } from "react"; +import React, { useEffect, useCallback, useRef } from "react"; import { useParams, useRouter } from "next/navigation"; import Map, { Layer, @@ -7,12 +7,9 @@ import Map, { MapRef, MapMouseEvent, MapLayerMouseEvent, - MapGeoJSONFeature, } from "react-map-gl/maplibre"; import "maplibre-gl/dist/maplibre-gl.css"; -import { LngLat } from "react-map-gl"; -import { EItemType } from "@/types/components"; import MapPopup from "@/app/components/map-popup"; import { MachineContext, MachineProvider } from "./state"; @@ -28,17 +25,11 @@ const AREA_DEFAULT_COLOR = "rgba(250, 250, 249, 0.3)"; const AREA_HIGHLIGHT_OUTLINE_COLOR = "rgba(0, 0, 0, 1)"; const AREA_DEFAULT_OUTLINE_COLOR = "rgba(0, 0, 0, 0.3)"; -interface IHighlightArea { - feature: MapGeoJSONFeature; - popupLocation: LngLat; -} - function GlobeInner() { const router = useRouter(); const params = useParams<{ areaId: string }>(); const actorRef = MachineContext.useActorRef(); const mapRef = useRef(null); - const [highlightArea, setHighlightArea] = useState(); // Selectors const pageIsMounting = MachineContext.useSelector((s) => @@ -49,8 +40,21 @@ function GlobeInner() { ); const pageUrl = MachineContext.useSelector(selectors.pageUrl); const mapBounds = MachineContext.useSelector(selectors.mapBounds); + const eventHandlers = MachineContext.useSelector( + (state) => state.context.eventHandlers + ); + const mapPopup = MachineContext.useSelector( + (state) => state.context.mapPopup + ); + + const handleMouseMove = useCallback((event: MapLayerMouseEvent) => { + actorRef.send({ + type: "event:map:mousemove", + mapEvent: event, + }); + }, []); - // On mount, pass route parameters to the machine + // On mount, pass route parameters to the machineaad useEffect(() => { actorRef.send({ type: "event:page:mount", @@ -109,33 +113,16 @@ function GlobeInner() { } }, []); - const onMouseMove = useCallback( - (event: MapLayerMouseEvent) => { - if (!mapRef.current) return; - const features = mapRef.current.queryRenderedFeatures(event.point, { - layers: ["area-clickable-polygon"], - }); - - const interactiveItem = features && features[0]; - - // Clear current highlighted area, if any - if (highlightArea && highlightArea.feature !== interactiveItem) { - mapRef.current.setFeatureState(highlightArea.feature, { hover: false }); - } + // Enable/disable map mousemove + useEffect(() => { + if (!mapRef.current) return; - // Handle the current item under the mouse - if (interactiveItem) { - mapRef.current.setFeatureState(interactiveItem, { hover: true }); - setHighlightArea({ - feature: interactiveItem, - popupLocation: event.lngLat, - } as IHighlightArea); - } else { - setHighlightArea(undefined); - } - }, - [highlightArea, setHighlightArea] - ); + if (eventHandlers.mousemove) { + mapRef.current.on("mousemove", handleMouseMove); + } else { + mapRef.current.off("mousemove", handleMouseMove); + } + }, [handleMouseMove, eventHandlers.mousemove, mapRef.current]); return (
@@ -148,8 +135,6 @@ function GlobeInner() { zoom: 2, }} onClick={onClick} - onMouseMove={onMouseMove} - onMouseLeave={() => setHighlightArea(undefined)} style={{ width: "100%", height: "100%" }} > - {highlightArea && ( - - )} + {mapPopup && }
diff --git a/src/app/components/map/state/machine.ts b/src/app/components/map/state/machine.ts index 806730d..5cd2f42 100644 --- a/src/app/components/map/state/machine.ts +++ b/src/app/components/map/state/machine.ts @@ -3,15 +3,24 @@ import { assign, createMachine, assertEvent } from "xstate"; import { StateEvents } from "./types/events"; import { StateActions } from "./types/actions"; import { BBox } from "geojson"; +import { MapGeoJSONFeature, MapRef } from "react-map-gl/maplibre"; +import { IMapPopup } from "../../map-popup"; +import { EItemType } from "@/types/components"; interface StateContext { + mapRef: MapRef | null; areaId: string | null; + highlightedArea: MapGeoJSONFeature | null; + mapPopup: IMapPopup | null; mapBounds: BBox | null; + eventHandlers: { + mousemove: boolean; + }; } export const globeViewMachine = createMachine( { - /** @xstate-layout N4IgpgJg5mDOIC5RQDYHsBGYBqBLMA7gHS4B2uALrgIYoDEYAbmKRQtQE5jUKxgpgAxhQDaABgC6iUAAc0sSrjSlpIAB6IArAE4A7EQDMBgCwAmYwYAclg7u0BGYwBoQAT0SX7Re5d3ndYtqaAGyWYpoAvhEuqJg4+MSc3Lz8QhSQDMys7Fw8ggKc4lJIIHIKVMqqGgg6moYW9poBwYE+li7uCPa6wUSapi1GA76aBqaWUTHoWHiEREk8fALCGUwsbAspy6KSqmWKlSXVxvamRMZiBsHGOmPBpprGuh2IDkRi45f23WH2HxPRECxGYJIgyagwBAAWzQAFdWGQoJl1ghwZCYfCdsVZPIDiojohTOE+r5TOYxI0xnoXghTLovJZrp5tMYTpYBpMgdN4nModQZNC4QjSEi1tk+QKMawintcRV8aBqrV6gZGs1WtYaXSGUz7Cy2RzOaQ0BA4KpgTyCLLykoFepEABaYI0p2ci2zYhkRS0a14qqIOx1OlXSyaD7aMTBXQGLWh7zXTT9HxmTw2N3cj3zXJbNKQX3y-0IMz6EJEoI9MxR51uRB6rxXUL9EwPBzBdNxTNosCCzGI-O2wtmLUsoi6Xxjkw9VPGdsg3n8nvCqD9w6KxBmYznMKaP6mAws0zabTDzdj3Q9XQWCMWNtRCJAA */ + /** @xstate-layout N4IgpgJg5mDOIC5RQDYHsBGYBqBLMA7gHS4B2uALrgIYoDEYAbmKRQtQE5jUKxgpgAxhQDaABgC6iUAAc0sSrjSlpIAB6IArAE4A7EQDMBgCwAmYwYAclg7u0BGYwBoQAT0SX7Re5d3ndYtqaAGyWYpoAvhEuqJg4+MSc3Lz8QhSQDMys7Fw8ggKc4lJIIHIKVMqqGgg6moYW9poBwYE+li7uCPa6wUSapi1GA76aBqaWUTHoWHiEREk8fALCGUwsbAspy6KSqmWKlSXVxvamRMZiBsHGOmPBpprGuh2IDkRi45f23WH2HxPRECxGYJIgyagwBAAWzQAFdWGQoJl1ghwZCYfCdsVZPIDiojohTOE+r5TOYxI0xnoXghTLovJZrp5tMYTpYBpMgdN4nModQZNC4QjSEi1tk+QKMawintcRV8aBqrV6gZGs1WtYaXSGUz7Cy2RzAcCecQyIpaMjxfzBbC+DDmDKSvt5VUPNciIzgg57uztH7TDSDNpeuFWZpLH7gj1RlFAaQ0BA4KpjbMCLLykoFepEABaYI0vPvMTFgxiUJR0v-Tkp0Fmqi0dN410IOx1OlXSyaD7aMu6Axazvea6afo+MyeGzV7mp+a5LZpSCNl0EhBmfQhIlBHpmKP5tyIPVeK6hfomB7eqdxGdosA24VQJeZ5tmLUsoi6Xwfkw9CfGS8g3lrSlKgRUfQ5FUQMxjHOMJND+UwgzMP1X2gj9dB6XQLB7CxgljCIgA */ id: "globeView", types: { @@ -21,8 +30,14 @@ export const globeViewMachine = createMachine( }, context: { + mapRef: null, areaId: null, + highlightedArea: null, + mapPopup: null, mapBounds: null, + eventHandlers: { + mousemove: true, + }, }, states: { @@ -32,6 +47,11 @@ export const globeViewMachine = createMachine( target: "area:selected", actions: "action:area:select", }, + + "event:map:mousemove": { + target: "initial", + actions: "action:setHighlightedArea", + }, }, }, @@ -80,6 +100,51 @@ export const globeViewMachine = createMachine( ...event.context, }; }), + "action:setMapRef": assign(({ event }) => { + assertEvent(event, "event:map:mount"); + + return { + mapRef: event.mapRef, + }; + }), + "action:setHighlightedArea": assign(({ event, context }) => { + assertEvent(event, "event:map:mousemove"); + + const { highlightedArea, mapRef } = context; + + if (!mapRef) { + return {}; + } + + if (highlightedArea) { + mapRef.setFeatureState(highlightedArea, { + hover: false, + }); + } + + const features = mapRef.queryRenderedFeatures(event.mapEvent.point, { + layers: ["area-clickable-polygon"], + }); + + const feature = features && features[0]; + + if (feature) { + mapRef.setFeatureState(feature, { hover: true }); + } + + return { + highlightedArea: feature || null, + mapPopup: feature + ? { + id: feature.properties.id, + label: feature.properties.name, + itemType: EItemType.area, + longitude: event.mapEvent.lngLat.lng, + latitude: event.mapEvent.lngLat.lat, + } + : null, + }; + }), "action:area:select": assign(({ event }) => { assertEvent(event, "event:area:select"); return { diff --git a/src/app/components/map/state/types/actions.ts b/src/app/components/map/state/types/actions.ts index 612019c..2685411 100644 --- a/src/app/components/map/state/types/actions.ts +++ b/src/app/components/map/state/types/actions.ts @@ -1,5 +1,6 @@ import { BBox } from "geojson"; -import { MapRef } from "react-map-gl"; +import { MapGeoJSONFeature, MapRef } from "react-map-gl/maplibre"; +import { IMapPopup } from "@/app/components/map-popup"; interface ActionInitContext { type: "action:initializeContext"; @@ -13,6 +14,12 @@ interface ActionSetMapRef { mapRef: MapRef; } +interface ActionSetHighlightedArea { + type: "action:setHighlightedArea"; + highlightedArea: MapGeoJSONFeature | null; + MapPopup: IMapPopup | null; +} + interface ActionAreaSelect { type: "action:area:select"; areaId: string; @@ -25,5 +32,7 @@ interface ActionAreaClear { export type StateActions = | ActionInitContext | ActionSetMapRef + | ActionSetHighlightedArea + | ActionAreaSelect | ActionAreaSelect | ActionAreaClear; diff --git a/src/app/components/map/state/types/events.ts b/src/app/components/map/state/types/events.ts index 80715e2..d3798f6 100644 --- a/src/app/components/map/state/types/events.ts +++ b/src/app/components/map/state/types/events.ts @@ -1,6 +1,7 @@ import { ProductionArea } from "@/types/data"; import { Feature, Geometry } from "geojson"; import { MapRef } from "react-map-gl/maplibre"; +import { MapLayerMouseEvent } from "react-map-gl/dist/esm/exports-maplibre"; interface EventPageMount { type: "event:page:mount"; @@ -14,6 +15,11 @@ interface EventMapMount { mapRef: MapRef; } +interface EventMapMouseMove { + type: "event:map:mousemove"; + mapEvent: MapLayerMouseEvent; +} + interface EventAreaSelect { type: "event:area:select"; area: Feature; @@ -26,5 +32,6 @@ interface EventAreaClear { export type StateEvents = | EventPageMount | EventMapMount + | EventMapMouseMove | EventAreaSelect | EventAreaClear; From 51ef516d013e2e9c693a283c823e09233f7c2271 Mon Sep 17 00:00:00 2001 From: Vitor George Date: Tue, 24 Sep 2024 10:56:15 +0100 Subject: [PATCH 09/13] Remove stately inspector in favor of a custom logger --- .env | 2 +- package.json | 1 - pnpm-lock.yaml | 103 +------------------------ src/app/components/map/state/index.tsx | 42 +++++++--- 4 files changed, 38 insertions(+), 110 deletions(-) diff --git a/.env b/.env index 88606f2..06565df 100644 --- a/.env +++ b/.env @@ -11,4 +11,4 @@ SEED_DATA_PATH="./seed-data/v3" FLOW_FILE_SIZE_LIMIT_MB=1 # Environment variables for client usage -NEXT_PUBLIC_USE_STATELY_INSPECTOR=false +NEXT_PUBLIC_XSTATE_INSPECT=false diff --git a/package.json b/package.json index 8335d10..c8f184e 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,6 @@ "@nextui-org/react": "^2.3.6", "@phosphor-icons/react": "^2.1.7", "@prisma/client": "5.15.0", - "@statelyai/inspect": "^0.3.1", "@turf/bbox": "^7.1.0", "@xstate/react": "^4.1.1", "d3": "^7.9.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6f3c645..ed74851 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,9 +35,6 @@ importers: '@prisma/client': specifier: 5.15.0 version: 5.15.0(prisma@5.15.0) - '@statelyai/inspect': - specifier: ^0.3.1 - version: 0.3.1(ws@8.17.0)(xstate@5.13.1) '@turf/bbox': specifier: ^7.1.0 version: 7.1.0 @@ -1681,11 +1678,6 @@ packages: resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} engines: {node: '>=18'} - '@statelyai/inspect@0.3.1': - resolution: {integrity: sha512-KW3owf5UbPs1+/xOGJoSV4D69hP5xTX7PCzARr2R1senwfUIwyGP8yEsB8dvkMvekYvgFS0qa6lmg1eszYr2tw==} - peerDependencies: - xstate: ^5.5.1 - '@swc/helpers@0.4.14': resolution: {integrity: sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==} @@ -1712,7 +1704,7 @@ packages: resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} '@turf/bbox@7.1.0': - resolution: {integrity: sha512-PdWPz9tW86PD78vSZj2fiRaB8JhUHy6piSa/QXb83lucxPK+HTAdzlDQMTKj5okRCU8Ox/25IR2ep9T8NdopRA==, tarball: https://registry.npmjs.org/@turf/bbox/-/bbox-7.1.0.tgz} + resolution: {integrity: sha512-PdWPz9tW86PD78vSZj2fiRaB8JhUHy6piSa/QXb83lucxPK+HTAdzlDQMTKj5okRCU8Ox/25IR2ep9T8NdopRA==} '@turf/boolean-clockwise@5.1.5': resolution: {integrity: sha512-FqbmEEOJ4rU4/2t7FKx0HUWmjFEVqR+NJrFP7ymGSjja2SQ7Q91nnBihGuT+yuHHl6ElMjQ3ttsB/eTmyCycxA==} @@ -1724,7 +1716,7 @@ packages: resolution: {integrity: sha512-/lF+JR+qNDHZ8bF9d+Cp58nxtZWJ3sqFe6n3u3Vpj+/0cqkjk4nXKYBSY0azm+GIYB5mWKxUXvuP/m0ZnKj1bw==} '@turf/helpers@7.1.0': - resolution: {integrity: sha512-dTeILEUVeNbaEeoZUOhxH5auv7WWlOShbx7QSd4s0T4Z0/iz90z9yaVCtZOLbU89umKotwKaJQltBNO9CzVgaQ==, tarball: https://registry.npmjs.org/@turf/helpers/-/helpers-7.1.0.tgz} + resolution: {integrity: sha512-dTeILEUVeNbaEeoZUOhxH5auv7WWlOShbx7QSd4s0T4Z0/iz90z9yaVCtZOLbU89umKotwKaJQltBNO9CzVgaQ==} '@turf/invariant@5.2.0': resolution: {integrity: sha512-28RCBGvCYsajVkw2EydpzLdcYyhSA77LovuOvgCJplJWaNVyJYH6BOR3HR9w50MEkPqb/Vc/jdo6I6ermlRtQA==} @@ -1733,7 +1725,7 @@ packages: resolution: {integrity: sha512-ZjQ3Ii62X9FjnK4hhdsbT+64AYRpaI8XMBMcyftEOGSmPMUVnkbvuv3C9geuElAXfQU7Zk1oWGOcrGOD9zr78Q==} '@turf/meta@7.1.0': - resolution: {integrity: sha512-ZgGpWWiKz797Fe8lfRj7HKCkGR+nSJ/5aKXMyofCvLSc2PuYJs/qyyifDPWjASQQCzseJ7AlF2Pc/XQ/3XkkuA==, tarball: https://registry.npmjs.org/@turf/meta/-/meta-7.1.0.tgz} + resolution: {integrity: sha512-ZgGpWWiKz797Fe8lfRj7HKCkGR+nSJ/5aKXMyofCvLSc2PuYJs/qyyifDPWjASQQCzseJ7AlF2Pc/XQ/3XkkuA==} '@turf/rewind@5.1.5': resolution: {integrity: sha512-Gdem7JXNu+G4hMllQHXRFRihJl3+pNl7qY+l4qhQFxq+hiU1cQoVFnyoleIqWKIrdK/i2YubaSwc3SCM7N5mMw==} @@ -2287,10 +2279,6 @@ packages: console-control-strings@1.1.0: resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} - copy-anything@3.0.5: - resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} - engines: {node: '>=12.13'} - core-assert@0.2.1: resolution: {integrity: sha512-IG97qShIP+nrJCXMCgkNZgH7jZQ4n8RpPyPeXX++T6avR/KhLhgLiHKoEn5Rc1KjfycSfA9DMa6m+4C4eguHhw==} engines: {node: '>=0.10.0'} @@ -2770,10 +2758,6 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} - event-target-shim@6.0.2: - resolution: {integrity: sha512-8q3LsZjRezbFZ2PN+uP+Q7pnHUMmAOziU2vA2OwoFaKIXxlxl38IylhSSgUorWu/rf4er67w0ikBqjBFk/pomA==} - engines: {node: '>=10.13.0'} - execa@9.3.0: resolution: {integrity: sha512-l6JFbqnHEadBoVAVpN5dl2yCyfX28WoBAGaoQcNmLLSedOxTxcn2Qa83s8I/PA5i56vWru2OHOtrwF7Om2vqlg==} engines: {node: ^18.19.0 || >=20.5.0} @@ -2806,9 +2790,6 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - fast-safe-stringify@2.1.1: - resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} - fast-xml-parser@4.3.6: resolution: {integrity: sha512-M2SovcRxD4+vC493Uc2GZVcZaj66CCJhWurC4viynVSTvrpErCShNcDz1lAho6n9REQKvL/ll4A4/fw6Y9z8nw==} hasBin: true @@ -3261,10 +3242,6 @@ packages: resolution: {integrity: sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==} engines: {node: '>= 0.4'} - is-what@4.1.16: - resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} - engines: {node: '>=12.13'} - isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} @@ -3278,11 +3255,6 @@ packages: resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} engines: {node: '>=0.10.0'} - isomorphic-ws@5.0.0: - resolution: {integrity: sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==} - peerDependencies: - ws: '*' - iterator.prototype@1.1.2: resolution: {integrity: sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==} @@ -3663,9 +3635,6 @@ packages: resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} engines: {node: '>=18'} - partysocket@0.0.25: - resolution: {integrity: sha512-1oCGA65fydX/FgdnsiBh68buOvfxuteoZVSb3Paci2kRp/7lhF0HyA8EDb5X/O6FxId1e+usPTQNRuzFEvkJbQ==} - path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -3972,10 +3941,6 @@ packages: resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} engines: {node: '>= 0.4'} - safe-stable-stringify@2.4.3: - resolution: {integrity: sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==} - engines: {node: '>=10'} - safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -4177,10 +4142,6 @@ packages: supercluster@8.0.1: resolution: {integrity: sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==} - superjson@1.13.3: - resolution: {integrity: sha512-mJiVjfd2vokfDxsQPOwJ/PtanO87LhpYY88ubI5dUB1Ab58Txbyje3+jpm+/83R/fevaq/107NNhtYBLuoTrFg==} - engines: {node: '>=10'} - supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -4277,7 +4238,7 @@ packages: resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} tslib@2.6.2: - resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==, tarball: https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz} + resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} tsx@4.19.0: resolution: {integrity: sha512-bV30kM7bsLZKZIOCHeMNVMJ32/LuJzLVajkQI/qf92J2Qr08ueLQvW00PUZGiuLPP760UINwupgUj8qrSCPUKg==} @@ -4405,10 +4366,6 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - uuid@9.0.1: - resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} - hasBin: true - v8-compile-cache-lib@3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} @@ -4453,18 +4410,6 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - ws@8.17.0: - resolution: {integrity: sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - xstate@5.13.1: resolution: {integrity: sha512-saBUxsTb29Vq8bjq1TjLdGCYs2pneGMzQ7pqQyXh1nqZaSnKHkCkxf3EV+EDYbLnQioxK9HNMYPQrz4whj3RJQ==} @@ -6906,18 +6851,6 @@ snapshots: '@sindresorhus/merge-streams@4.0.0': {} - '@statelyai/inspect@0.3.1(ws@8.17.0)(xstate@5.13.1)': - dependencies: - fast-safe-stringify: 2.1.1 - isomorphic-ws: 5.0.0(ws@8.17.0) - partysocket: 0.0.25 - safe-stable-stringify: 2.4.3 - superjson: 1.13.3 - uuid: 9.0.1 - xstate: 5.13.1 - transitivePeerDependencies: - - ws - '@swc/helpers@0.4.14': dependencies: tslib: 2.6.2 @@ -7653,10 +7586,6 @@ snapshots: console-control-strings@1.1.0: optional: true - copy-anything@3.0.5: - dependencies: - is-what: 4.1.16 - core-assert@0.2.1: dependencies: buf-compare: 1.0.1 @@ -8308,8 +8237,6 @@ snapshots: esutils@2.0.3: {} - event-target-shim@6.0.2: {} - execa@9.3.0: dependencies: '@sindresorhus/merge-streams': 4.0.0 @@ -8352,8 +8279,6 @@ snapshots: fast-levenshtein@2.0.6: {} - fast-safe-stringify@2.1.1: {} - fast-xml-parser@4.3.6: dependencies: strnum: 1.0.5 @@ -8798,8 +8723,6 @@ snapshots: call-bind: 1.0.7 get-intrinsic: 1.2.4 - is-what@4.1.16: {} - isarray@1.0.0: {} isarray@2.0.5: {} @@ -8808,10 +8731,6 @@ snapshots: isobject@3.0.1: {} - isomorphic-ws@5.0.0(ws@8.17.0): - dependencies: - ws: 8.17.0 - iterator.prototype@1.1.2: dependencies: define-properties: 1.2.1 @@ -9282,10 +9201,6 @@ snapshots: parse-ms@4.0.0: {} - partysocket@0.0.25: - dependencies: - event-target-shim: 6.0.2 - path-exists@4.0.0: {} path-is-absolute@1.0.1: {} @@ -9593,8 +9508,6 @@ snapshots: es-errors: 1.3.0 is-regex: 1.1.4 - safe-stable-stringify@2.4.3: {} - safer-buffer@2.1.2: {} scheduler@0.23.2: @@ -9826,10 +9739,6 @@ snapshots: dependencies: kdbush: 4.0.2 - superjson@1.13.3: - dependencies: - copy-anything: 3.0.5 - supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -10097,8 +10006,6 @@ snapshots: util-deprecate@1.0.2: {} - uuid@9.0.1: {} - v8-compile-cache-lib@3.0.1: optional: true @@ -10173,8 +10080,6 @@ snapshots: wrappy@1.0.2: {} - ws@8.17.0: {} - xstate@5.13.1: {} yallist@4.0.0: {} diff --git a/src/app/components/map/state/index.tsx b/src/app/components/map/state/index.tsx index 3f6e615..ac67076 100644 --- a/src/app/components/map/state/index.tsx +++ b/src/app/components/map/state/index.tsx @@ -1,15 +1,39 @@ import { createActorContext } from "@xstate/react"; import { globeViewMachine } from "./machine"; -import { createBrowserInspector } from "@statelyai/inspect"; -export const MachineContext = createActorContext( - globeViewMachine, - process.env.NEXT_PUBLIC_USE_STATELY_INSPECTOR === "true" - ? { - inspect: createBrowserInspector().inspect, - } - : undefined -); +export const MachineContext = createActorContext(globeViewMachine, { + inspect: + process.env.NEXT_PUBLIC_XSTATE_INSPECT === "true" + ? (inspectEvent) => { + if ( + inspectEvent.type !== "@xstate.event" && + inspectEvent.type !== "@xstate.snapshot" + ) + return; + + console.groupCollapsed( + "%c event", + "color: gray; font-weight: lighter;", + inspectEvent.type + ); + + if (inspectEvent.type === "@xstate.event") { + console.log( + "%c event", + "color: #03A9F4; font-weight: bold;", + inspectEvent.event + ); + } else if (inspectEvent.type === "@xstate.snapshot") { + console.log( + "%c state", + "color: #4CAF50; font-weight: bold;", + inspectEvent.snapshot + ); + } + console.groupEnd(); + } + : undefined, +}); export function MachineProvider(props: { children: React.ReactNode }) { return {props.children}; From 733d47071d686507bce8925448dd3ee6e5929c69 Mon Sep 17 00:00:00 2001 From: Vitor George Date: Tue, 24 Sep 2024 11:36:59 +0100 Subject: [PATCH 10/13] Zoom to area bounds on click --- src/app/api/areas/[id]/route.ts | 47 ++++++++ src/app/components/map/index.tsx | 21 +--- src/app/components/map/state/machine.ts | 108 ++++++++++++++---- src/app/components/map/state/types/actions.ts | 21 ++-- src/app/components/map/state/types/events.ts | 12 +- 5 files changed, 158 insertions(+), 51 deletions(-) create mode 100644 src/app/api/areas/[id]/route.ts diff --git a/src/app/api/areas/[id]/route.ts b/src/app/api/areas/[id]/route.ts new file mode 100644 index 0000000..3af1a97 --- /dev/null +++ b/src/app/api/areas/[id]/route.ts @@ -0,0 +1,47 @@ +import { NextRequest, NextResponse } from "next/server"; +import prisma from "@/lib/prisma"; + +export interface FetchAreaResponse { + id: string; + name: string; + boundingBox: GeoJSON.Feature | null; +} + +export async function GET( + req: NextRequest, + { params }: { params: { id: string } } +) { + const { id } = params; + + try { + const area = await prisma.area.findUnique({ + where: { id }, + select: { + id: true, + name: true, + }, + }); + + if (!area) { + return NextResponse.json({ error: "Area not found" }, { status: 404 }); + } + + const boundingBox = (await prisma.$queryRaw` + SELECT ST_AsGeoJSON(ST_Extent(ST_Transform(limits, 4326))) as geojson FROM "Area" WHERE id = ${id} + `) as { geojson: string }[]; + + const result = { + ...area, + boundingBox: JSON.parse(boundingBox[0]?.geojson) || null, // Extract the bounding box as GeoJSON + }; + + return NextResponse.json(result); + } catch (error) { + // eslint-disable-next-line no-console + console.error("Error fetching area:", error); + return NextResponse.json( + { error: "Internal server error" }, + { status: 500 } + ); + } +} diff --git a/src/app/components/map/index.tsx b/src/app/components/map/index.tsx index d007cf4..6a3be6d 100644 --- a/src/app/components/map/index.tsx +++ b/src/app/components/map/index.tsx @@ -54,7 +54,7 @@ function GlobeInner() { }); }, []); - // On mount, pass route parameters to the machineaad + // On mount, pass route parameters to the machine useEffect(() => { actorRef.send({ type: "event:page:mount", @@ -78,16 +78,6 @@ function GlobeInner() { } }, [router, pageUrl, pageIsMounting]); - useEffect(() => { - if (mapRef.current && mapBounds) { - const [x1, y1, x2, z2] = mapBounds; - mapRef.current.fitBounds([x1, y1, x2, z2], { - padding: 200, - duration: 500, - }); - } - }, [mapBounds]); - const onClick = useCallback((event: MapMouseEvent) => { if (mapRef.current) { const features = mapRef.current.queryRenderedFeatures(event.point, { @@ -99,14 +89,7 @@ function GlobeInner() { if (feature) { actorRef.send({ type: "event:area:select", - area: { - type: "Feature", - geometry: feature.geometry, - properties: { - id: feature.properties.id, - name: feature.properties.name, - }, - }, + areaId: feature.properties.id, }); } } diff --git a/src/app/components/map/state/machine.ts b/src/app/components/map/state/machine.ts index 5cd2f42..6dc98d7 100644 --- a/src/app/components/map/state/machine.ts +++ b/src/app/components/map/state/machine.ts @@ -1,16 +1,19 @@ import { bbox } from "@turf/bbox"; -import { assign, createMachine, assertEvent } from "xstate"; +import { assign, createMachine, assertEvent, fromPromise, setup } from "xstate"; import { StateEvents } from "./types/events"; import { StateActions } from "./types/actions"; import { BBox } from "geojson"; import { MapGeoJSONFeature, MapRef } from "react-map-gl/maplibre"; import { IMapPopup } from "../../map-popup"; import { EItemType } from "@/types/components"; +import { FetchAreaResponse } from "@/app/api/areas/[id]/route"; interface StateContext { mapRef: MapRef | null; areaId: string | null; highlightedArea: MapGeoJSONFeature | null; + currentAreaId: string | null; + currentArea: FetchAreaResponse | null; mapPopup: IMapPopup | null; mapBounds: BBox | null; eventHandlers: { @@ -20,7 +23,7 @@ interface StateContext { export const globeViewMachine = createMachine( { - /** @xstate-layout N4IgpgJg5mDOIC5RQDYHsBGYBqBLMA7gHS4B2uALrgIYoDEYAbmKRQtQE5jUKxgpgAxhQDaABgC6iUAAc0sSrjSlpIAB6IArAE4A7EQDMBgCwAmYwYAclg7u0BGYwBoQAT0SX7Re5d3ndYtqaAGyWYpoAvhEuqJg4+MSc3Lz8QhSQDMys7Fw8ggKc4lJIIHIKVMqqGgg6moYW9poBwYE+li7uCPa6wUSapi1GA76aBqaWUTHoWHiEREk8fALCGUwsbAspy6KSqmWKlSXVxvamRMZiBsHGOmPBpprGuh2IDkRi45f23WH2HxPRECxGYJIgyagwBAAWzQAFdWGQoJl1ghwZCYfCdsVZPIDiojohTOE+r5TOYxI0xnoXghTLovJZrp5tMYTpYBpMgdN4nModQZNC4QjSEi1tk+QKMawintcRV8aBqrV6gZGs1WtYaXSGUz7Cy2RzAcCecQyIpaMjxfzBbC+DDmDKSvt5VUPNciIzgg57uztH7TDSDNpeuFWZpLH7gj1RlFAaQ0BA4KpjbMCLLykoFepEABaYI0vPvMTFgxiUJR0v-Tkp0Fmqi0dN410IOx1OlXSyaD7aMu6Axazvea6afo+MyeGzV7mp+a5LZpSCNl0EhBmfQhIlBHpmKP5tyIPVeK6hfomB7eqdxGdosA24VQJeZ5tmLUsoi6Xwfkw9CfGS8g3lrSlKgRUfQ5FUQMxjHOMJND+UwgzMP1X2gj9dB6XQLB7CxgljCIgA */ + /** @xstate-layout N4IgpgJg5mDOIC5RQDYHsBGYBqBLMA7gHQFoBOKECAbvgQMRjVgB2ALggIZlicKxgUYAMZsA2gAYAuolAAHNLFxtcaFrJAAPRAHYdEogGZDADh0mAbBYCcAJluGArIYA0IAJ6IAjBYNeJJhIALCbWOo62JoYWjgC+sW6omDh0JOSUNHSMzOwIALaccvloAK4CeWjMkjJIIApKKmoa2giWQUSWNj6RttZ9tm6eCIbWFkQSjkFBjqGjOjGG8YnoWHiERHKcMMUl7LgsUNmsHJvbFbvi0hr1yqrqtS22E0QzOvZBEl7OvTqDiLY6LwdCwhLzWKZeEy2CxLEBJVapApFc57A5HXJInbsarXRS3JoPRCOayOIxBQxffS+ayQkx-BAAoFtExgiFQmEJOErFLrbi8BAAMzAbGEAAt9ocIGowER9tQ0ABrGXwnnEPl8IUi8UHBBytDCTiNFjVHG1G5G5q6Cy2IheQwSBxvCZ6AL0gC0bxeEjsEzskR0H0WnJVazVPD4tEI6I46oQwiE3FN8jxFsJCC8XgDRGsnwpZhGEQD9J8fgCwVC4Ui0Tiwe5oaIscjDCYxy44f4ghElxqyYad0tCD6BkcEhs1mMkX84XpESIejeTwk5kM0yCMXinJYaAgcA0IbouL7BNALTdFndPiBa9sU3s87CXlssP361IFCoTcP+PuJ90JKIALRCYI69KOOiuB4-zAbaIKOBEkJBJOUTPnWqSnGAWIqAcX6pr+CCIfSvTtOYehREE8wsiYQQock9aYiiWFQDh-Zpoh7QhBM-gOOCvTWIR4JzmY8wBiMwTRDRCK8u2mpihKzHHloughGS1jATY5gxEu-G2oCkzBNSQTgmYEmqg27afmaKYsXh-iOEC8yGL0mYOhCQTulmAJ2XBPjjiugIbrEQA */ id: "globeView", types: { @@ -33,6 +36,8 @@ export const globeViewMachine = createMachine( mapRef: null, areaId: null, highlightedArea: null, + currentAreaId: null, + currentArea: null, mapPopup: null, mapBounds: null, eventHandlers: { @@ -41,34 +46,21 @@ export const globeViewMachine = createMachine( }, states: { - initial: { + "world:view": { on: { "event:area:select": { - target: "area:selected", - actions: "action:area:select", + target: "area:fetching", + actions: "action:setCurrentAreaId", + reenter: true, }, "event:map:mousemove": { - target: "initial", + target: "world:view", actions: "action:setHighlightedArea", }, }, }, - "area:selected": { - on: { - "event:area:clear": { - target: "initial", - actions: "action:area:clear", - }, - - "event:area:select": { - target: "area:selected", - actions: "action:area:select", - }, - }, - }, - "page:mounting": { on: { "event:page:mount": { @@ -82,12 +74,43 @@ export const globeViewMachine = createMachine( "map:mounting": { on: { "event:map:mount": { - target: "initial", + target: "world:view", reenter: true, actions: "action:setMapRef", }, }, }, + + "area:fetching": { + invoke: { + src: "actor:fetchArea", + input: ({ context: { currentAreaId } }) => ({ + areaId: currentAreaId, + }), + onDone: { + target: "area:view", + actions: "action:setCurrentArea", + }, + }, + }, + + "area:view": { + on: { + "event:area:clear": { + target: "world:view", + actions: "action:area:clear", + reenter: true, + }, + + "event:area:select": { + target: "area:fetching", + reenter: true, + actions: "action:setCurrentAreaId", + }, + }, + + entry: "action:setAreaMapView", + }, }, initial: "page:mounting", @@ -145,16 +168,53 @@ export const globeViewMachine = createMachine( : null, }; }), - "action:area:select": assign(({ event }) => { + "action:setCurrentArea": assign(({ event }) => { + assertEvent(event, "xstate.done.actor.0.globeView.area:fetching"); + + return { + currentArea: event.output, + }; + }), + "action:setAreaMapView": assign(({ context }) => { + const { mapRef, currentArea } = context; + + if (!mapRef || !currentArea || !currentArea.boundingBox) { + return {}; + } + + const bounds = bbox(currentArea.boundingBox); + + mapRef.fitBounds( + [ + [bounds[0], bounds[1]], + [bounds[2], bounds[3]], + ], + { + padding: 100, + } + ); + + return { + mapBounds: bounds, + }; + }), + "action:setCurrentAreaId": assign(({ event }) => { assertEvent(event, "event:area:select"); return { - areaId: event.area.properties.id, - mapBounds: bbox(event.area), + currentAreaId: event.areaId, }; }), "action:area:clear": assign({ areaId: undefined, }), }, + actors: { + "actor:fetchArea": fromPromise( + async ({ input }) => { + const response = await fetch(`/api/areas/${input.areaId}`); + return await response.json(); + } + ), + }, } ); diff --git a/src/app/components/map/state/types/actions.ts b/src/app/components/map/state/types/actions.ts index 2685411..60382db 100644 --- a/src/app/components/map/state/types/actions.ts +++ b/src/app/components/map/state/types/actions.ts @@ -1,4 +1,3 @@ -import { BBox } from "geojson"; import { MapGeoJSONFeature, MapRef } from "react-map-gl/maplibre"; import { IMapPopup } from "@/app/components/map-popup"; @@ -20,10 +19,17 @@ interface ActionSetHighlightedArea { MapPopup: IMapPopup | null; } -interface ActionAreaSelect { - type: "action:area:select"; - areaId: string; - mapBounds: BBox | null; +interface ActionSetCurrentAreaId { + type: "action:setCurrentAreaId"; + currentAreaId: string; +} +interface ActionSetCurrentArea { + type: "action:setCurrentArea"; + area: GeoJSON.Feature; +} + +interface ActionSetAreaMapView { + type: "action:setAreaMapView"; } interface ActionAreaClear { type: "action:area:clear"; @@ -33,6 +39,7 @@ export type StateActions = | ActionInitContext | ActionSetMapRef | ActionSetHighlightedArea - | ActionAreaSelect - | ActionAreaSelect + | ActionSetCurrentAreaId + | ActionSetCurrentArea + | ActionSetAreaMapView | ActionAreaClear; diff --git a/src/app/components/map/state/types/events.ts b/src/app/components/map/state/types/events.ts index d3798f6..c20dcd9 100644 --- a/src/app/components/map/state/types/events.ts +++ b/src/app/components/map/state/types/events.ts @@ -2,6 +2,7 @@ import { ProductionArea } from "@/types/data"; import { Feature, Geometry } from "geojson"; import { MapRef } from "react-map-gl/maplibre"; import { MapLayerMouseEvent } from "react-map-gl/dist/esm/exports-maplibre"; +import { FetchAreaResponse } from "@/app/api/areas/[id]/route"; interface EventPageMount { type: "event:page:mount"; @@ -22,7 +23,15 @@ interface EventMapMouseMove { interface EventAreaSelect { type: "event:area:select"; - area: Feature; + areaId: string; +} + +interface EventFetchAreaDone { + type: "xstate.done.actor.0.globeView.area:fetching"; + input: { + areaId: string; + }; + output: FetchAreaResponse; } interface EventAreaClear { @@ -34,4 +43,5 @@ export type StateEvents = | EventMapMount | EventMapMouseMove | EventAreaSelect + | EventFetchAreaDone | EventAreaClear; From 6e01a6b428fbca65914dadd53c93d31d03615e1c Mon Sep 17 00:00:00 2001 From: Vitor George Date: Tue, 24 Sep 2024 11:57:58 +0100 Subject: [PATCH 11/13] Keep area highlight on area map view --- src/app/components/map/state/machine.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/app/components/map/state/machine.ts b/src/app/components/map/state/machine.ts index 6dc98d7..ae022d4 100644 --- a/src/app/components/map/state/machine.ts +++ b/src/app/components/map/state/machine.ts @@ -23,7 +23,7 @@ interface StateContext { export const globeViewMachine = createMachine( { - /** @xstate-layout N4IgpgJg5mDOIC5RQDYHsBGYBqBLMA7gHQFoBOKECAbvgQMRjVgB2ALggIZlicKxgUYAMZsA2gAYAuolAAHNLFxtcaFrJAAPRAHYdEogGZDADh0mAbBYCcAJluGArIYA0IAJ6IAjBYNeJJhIALCbWOo62JoYWjgC+sW6omDh0JOSUNHSMzOwIALaccvloAK4CeWjMkjJIIApKKmoa2giWQUSWNj6RttZ9tm6eCIbWFkQSjkFBjqGjOjGG8YnoWHiERHKcMMUl7LgsUNmsHJvbFbvi0hr1yqrqtS22E0QzOvZBEl7OvTqDiLY6LwdCwhLzWKZeEy2CxLEBJVapApFc57A5HXJInbsarXRS3JoPRCOayOIxBQxffS+ayQkx-BAAoFtExgiFQmEJOErFLrbi8BAAMzAbGEAAt9ocIGowER9tQ0ABrGXwnnEPl8IUi8UHBBytDCTiNFjVHG1G5G5q6Cy2IheQwSBxvCZ6AL0gC0bxeEjsEzskR0H0WnJVazVPD4tEI6I46oQwiE3FN8jxFsJCC8XgDRGsnwpZhGEQD9J8fgCwVC4Ui0Tiwe5oaIscjDCYxy44f4ghElxqyYad0tCD6BkcEhs1mMkX84XpESIejeTwk5kM0yCMXinJYaAgcA0IbouL7BNALTdFndPiBa9sU3s87CXlssP361IFCoTcP+PuJ90JKIALRCYI69KOOiuB4-zAbaIKOBEkJBJOUTPnWqSnGAWIqAcX6pr+CCIfSvTtOYehREE8wsiYQQock9aYiiWFQDh-Zpoh7QhBM-gOOCvTWIR4JzmY8wBiMwTRDRCK8u2mpihKzHHloughGS1jATY5gxEu-G2oCkzBNSQTgmYEmqg27afmaKYsXh-iOEC8yGL0mYOhCQTulmAJ2XBPjjiugIbrEQA */ + /** @xstate-layout N4IgpgJg5mDOIC5RQDYHsBGYBqBLMA7gHQFoBOKECAbvgQMRjVgB2ALggIZlicKxgUYAMZsA2gAYAuolAAHNLFxtcaFrJAAPRAHYdEogGZDADh0mAbBYCcAJluGArIYA0IAJ6IAjBYNeJJhIALCbWOo62JoYWjgC+sW6omDh0JOSUNHSMzOwIALaccvloAK4CeWjMkjJIIApKKmoa2giWQUSWNj6RttZ9tm6eCIbWFkQSjkFBjqGjOjGG8YnoWHiERHKcMMUl7LgsUNmsHJvbFbvi0hr1yqrqtS22E0QzOvZBEl7OvTqDiLY6LwdCwhLzWKZeEy2CxLEBJVapApFc57A5HXJInbsarXRS3JoPRCOayOIxBQxffS+ayQkx-BAAoFtExgiFQmEJOErFLrbi8BAAMzAbGEAAt9ocIGowER9tQ0ABrGXwnnEPl8IUi8UHBBytDCTiNFjVHG1G5G5q6Cy2IheQwSBxvCZ6AL0gC0bxeEjsEzskR0H0WnJVazVPD4tEI6I46oQwiE3FN8jxFsJCC8XgDRGsnwpZhGEQD9J8fgCwVC4Ui0Tiwe5oaIscjDCYxy44f4ghElxqyYad0tCD6BkcEhs1mMkX84XpESIejeTwk5kM0yCMVhIdSjayLYxhR25UqYCTdRT-bTGdMc8zIWi82soTpHkQbqe1jnjinlkCYI5nJYaAQHAGiboQuJ9gSoAtG6Fjur0YzjlEMTmJYMzRBudapKQFBUE24H4vcUG6CSRAAtEJgjr0o46K4z4MhRtogo4ESQkEk5RBhyT1qcYBYioBz4amREIGx9K9O05h6FEQTzCyJhBJxCLrJiKL8VAgnnsJbHtCEEz+A44K9NYYngnOZjzAGIzBOhtZcVu7aamKEoaZBWi6CEZIPo4NjmDES4mbagKTME1JBOCZiKaqDbtnhZpnq5LT+J+c4WIYvSZg6EJBO6WYAp+zE+OOK6AvE8RAA */ id: "globeView", types: { @@ -107,6 +107,11 @@ export const globeViewMachine = createMachine( reenter: true, actions: "action:setCurrentAreaId", }, + + "event:map:mousemove": { + target: "area:view", + actions: "action:setHighlightedArea", + }, }, entry: "action:setAreaMapView", From 886005b12720fae456bbdf1e4112f98404f26139 Mon Sep 17 00:00:00 2001 From: Vitor George Date: Tue, 24 Sep 2024 13:10:45 +0100 Subject: [PATCH 12/13] Fix lint issues --- package.json | 1 + pnpm-lock.yaml | 18 ++++++++++++++++++ src/app/components/map/index.tsx | 1 - src/app/components/map/state/index.tsx | 2 ++ src/app/components/map/state/machine.ts | 2 +- src/app/components/map/state/types/events.ts | 2 -- 6 files changed, 22 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index c8f184e..eccdc62 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "@eslint/js": "^9.3.0", "@types/d3": "^7.4.3", "@types/d3-sankey": "^0.12.4", + "@types/fs-extra": "^11.0.4", "@types/geojson": "^7946.0.14", "@types/node": "^20.12.12", "@types/react": "^18.3.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ed74851..4293f3e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -84,6 +84,9 @@ importers: '@types/d3-sankey': specifier: ^0.12.4 version: 0.12.4 + '@types/fs-extra': + specifier: ^11.0.4 + version: 11.0.4 '@types/geojson': specifier: ^7946.0.14 version: 7946.0.14 @@ -1838,6 +1841,9 @@ packages: '@types/d3@7.4.3': resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==} + '@types/fs-extra@11.0.4': + resolution: {integrity: sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==} + '@types/geojson-vt@3.2.5': resolution: {integrity: sha512-qDO7wqtprzlpe8FfQ//ClPV9xiuoh2nkIgiouIptON9w5jvD/fA4szvP9GBlDVdJ5dldAl0kX/sy3URbWwLx0g==} @@ -1850,6 +1856,9 @@ packages: '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + '@types/jsonfile@6.1.4': + resolution: {integrity: sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==} + '@types/lodash.debounce@4.0.9': resolution: {integrity: sha512-Ma5JcgTREwpLRwMM+XwBR7DaWe96nC38uCBDFKZWbNKD+osjVzdpnUSwBcqCptrp16sSOLBAUb50Car5I0TCsQ==} @@ -7056,6 +7065,11 @@ snapshots: '@types/d3-transition': 3.0.8 '@types/d3-zoom': 3.0.8 + '@types/fs-extra@11.0.4': + dependencies: + '@types/jsonfile': 6.1.4 + '@types/node': 20.12.12 + '@types/geojson-vt@3.2.5': dependencies: '@types/geojson': 7946.0.14 @@ -7066,6 +7080,10 @@ snapshots: '@types/json5@0.0.29': {} + '@types/jsonfile@6.1.4': + dependencies: + '@types/node': 20.12.12 + '@types/lodash.debounce@4.0.9': dependencies: '@types/lodash': 4.17.1 diff --git a/src/app/components/map/index.tsx b/src/app/components/map/index.tsx index 6a3be6d..002c518 100644 --- a/src/app/components/map/index.tsx +++ b/src/app/components/map/index.tsx @@ -39,7 +39,6 @@ function GlobeInner() { s.matches("map:mounting") ); const pageUrl = MachineContext.useSelector(selectors.pageUrl); - const mapBounds = MachineContext.useSelector(selectors.mapBounds); const eventHandlers = MachineContext.useSelector( (state) => state.context.eventHandlers ); diff --git a/src/app/components/map/state/index.tsx b/src/app/components/map/state/index.tsx index ac67076..c616cdd 100644 --- a/src/app/components/map/state/index.tsx +++ b/src/app/components/map/state/index.tsx @@ -1,6 +1,8 @@ import { createActorContext } from "@xstate/react"; import { globeViewMachine } from "./machine"; +/* eslint-disable no-console */ + export const MachineContext = createActorContext(globeViewMachine, { inspect: process.env.NEXT_PUBLIC_XSTATE_INSPECT === "true" diff --git a/src/app/components/map/state/machine.ts b/src/app/components/map/state/machine.ts index ae022d4..ca5715d 100644 --- a/src/app/components/map/state/machine.ts +++ b/src/app/components/map/state/machine.ts @@ -1,5 +1,5 @@ import { bbox } from "@turf/bbox"; -import { assign, createMachine, assertEvent, fromPromise, setup } from "xstate"; +import { assign, createMachine, assertEvent, fromPromise } from "xstate"; import { StateEvents } from "./types/events"; import { StateActions } from "./types/actions"; import { BBox } from "geojson"; diff --git a/src/app/components/map/state/types/events.ts b/src/app/components/map/state/types/events.ts index c20dcd9..53bd510 100644 --- a/src/app/components/map/state/types/events.ts +++ b/src/app/components/map/state/types/events.ts @@ -1,5 +1,3 @@ -import { ProductionArea } from "@/types/data"; -import { Feature, Geometry } from "geojson"; import { MapRef } from "react-map-gl/maplibre"; import { MapLayerMouseEvent } from "react-map-gl/dist/esm/exports-maplibre"; import { FetchAreaResponse } from "@/app/api/areas/[id]/route"; From cefd8731e3e0990f8582f8a54292d0ae07666f32 Mon Sep 17 00:00:00 2001 From: Vitor George Date: Tue, 24 Sep 2024 15:19:32 +0100 Subject: [PATCH 13/13] Add more padding to area --- src/app/components/map/state/machine.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/components/map/state/machine.ts b/src/app/components/map/state/machine.ts index ca5715d..a856dae 100644 --- a/src/app/components/map/state/machine.ts +++ b/src/app/components/map/state/machine.ts @@ -195,7 +195,7 @@ export const globeViewMachine = createMachine( [bounds[2], bounds[3]], ], { - padding: 100, + padding: 400, } );