From 647c37cad32ad53008065b08b5e3ca1ad4ebdeac Mon Sep 17 00:00:00 2001 From: TurtIeSocks <58572875+TurtIeSocks@users.noreply.github.com> Date: Sun, 26 Jun 2022 17:05:47 -0400 Subject: [PATCH 1/4] Lots of Refinments - Add multiDomain and hidden support to manual areas - Maintain hidden areas for areaRestriction purposes but still hide them from the map and sidebar menu - Add a check in weather model to fix an edgecase - *Allow* manualAreas and an areas.json to be used together - Fix reset button - Fix sidebar menu grid items when the name is too long, assuming it's a multi-word place - Less hacky ScanArea tile stuff --- server/src/configs/local.example.json | 6 +- server/src/graphql/resolvers.js | 23 +++--- server/src/models/Weather.js | 13 ++-- server/src/services/config.js | 73 +++++++++++-------- server/src/services/functions/getAreaSql.js | 7 +- src/components/layout/drawer/AreaTile.jsx | 79 ++++++++++++--------- src/components/layout/drawer/Areas.jsx | 10 +-- src/components/tiles/ScanArea.jsx | 5 +- src/hooks/useStore.js | 4 +- 9 files changed, 123 insertions(+), 97 deletions(-) diff --git a/server/src/configs/local.example.json b/server/src/configs/local.example.json index 273bec007..7c73ab3e9 100644 --- a/server/src/configs/local.example.json +++ b/server/src/configs/local.example.json @@ -314,13 +314,15 @@ "name": "San Francisco", "lat": 37.79539194255634, "lon": -122.39333173075096, - "parent": "California" + "parent": "California", + "hidden": false }, { "name": "Houston", "lat": 33.6145517, "lon": -108.6038347, - "parent": "Texas" + "parent": "Texas", + "domain": "map_2.your_map.com" } ] } \ No newline at end of file diff --git a/server/src/graphql/resolvers.js b/server/src/graphql/resolvers.js index 6e10fe204..d0be2763f 100644 --- a/server/src/graphql/resolvers.js +++ b/server/src/graphql/resolvers.js @@ -168,17 +168,18 @@ module.exports = { if (clientV !== serverV) throw new UserInputError('old_client') if (!perms) throw new AuthenticationError('session_expired') - const scanAreas = config.scanAreas[req.headers.host] - ? config.scanAreas[req.headers.host] - : config.scanAreas.main - if (perms?.scanAreas && scanAreas.features.length) { + if (perms?.scanAreas) { + const scanAreas = config.scanAreas[req.headers.host] + ? config.scanAreas[req.headers.host] + : config.scanAreas.main return [ { ...scanAreas, features: scanAreas.features.filter( (feature) => - !perms.areaRestrictions.length || - perms.areaRestrictions.includes(feature.properties.name), + !feature.properties.hidden && + (!perms.areaRestrictions.length || + perms.areaRestrictions.includes(feature.properties.name)), ), }, ] @@ -189,10 +190,11 @@ module.exports = { if (clientV !== serverV) throw new UserInputError('old_client') if (!perms) throw new AuthenticationError('session_expired') - const scanAreas = config.scanAreasMenu[req.headers.host] - ? config.scanAreasMenu[req.headers.host] - : config.scanAreasMenu.main - if (perms?.scanAreas && scanAreas.length) { + if (perms?.scanAreas) { + const scanAreas = config.scanAreasMenu[req.headers.host] + ? config.scanAreasMenu[req.headers.host] + : config.scanAreasMenu.main + if (perms.areaRestrictions.length) { const filtered = scanAreas .map((parent) => ({ @@ -203,6 +205,7 @@ module.exports = { })) .filter((parent) => parent.children.length) + // Adds new blanks to account for area restrictions trimming some filtered.forEach(({ children }) => { if (children.length % 2 === 1) { children.push({ diff --git a/server/src/models/Weather.js b/server/src/models/Weather.js index fb7f53217..344b2faad 100644 --- a/server/src/models/Weather.js +++ b/server/src/models/Weather.js @@ -52,15 +52,16 @@ module.exports = class Weather extends Model { !merged.length || merged.some( (area) => - pointInPolygon( + config.scanAreasObj[area] && + (pointInPolygon( point(config.scanAreasObj[area].geometry.coordinates[0][0]), geojson, ) || - pointInPolygon( - point([cell.longitude, cell.latitude]), - config.scanAreasObj[area], - ) || - booleanOverlap(geojson, config.scanAreasObj[area]), + pointInPolygon( + point([cell.longitude, cell.latitude]), + config.scanAreasObj[area], + ) || + booleanOverlap(geojson, config.scanAreasObj[area])), ) return ( hasOverlap && { diff --git a/server/src/services/config.js b/server/src/services/config.js index 6e898d2e4..4de2fbb65 100644 --- a/server/src/services/config.js +++ b/server/src/services/config.js @@ -166,51 +166,52 @@ config.authMethods = [ } }) +const manualGeojson = { + type: 'FeatureCollection', + features: config.manualAreas + .filter((area) => ['lat', 'lon', 'name'].every((k) => k in area && !area.hidden)) + .map((area) => { + const { lat, lon, ...rest } = area + return { + type: 'Feature', + properties: { + center: [lat, lon], + manual: true, + ...rest, + }, + geometry: { + type: 'Polygon', + coordinates: [[[lon, lat]]], + }, + } + }), +} + // Load each areas.json -const loadScanPolygons = (fileName) => { +const loadScanPolygons = (fileName, domain) => { const geojson = fs.existsSync(resolve(`${__dirname}/../configs/${fileName}`)) ? JSON.parse(fs.readFileSync(resolve(__dirname, `../configs/${fileName}`))) : { features: [] } return { ...geojson, - features: geojson.features - .filter((f) => !f.properties.hidden) - .map((f) => ({ + features: [ + ...manualGeojson.features.filter( + (f) => !f.properties.domain || f.properties.domain === domain, + ), + ...geojson.features.map((f) => ({ ...f, properties: { ...f.properties, center: center(f).geometry.coordinates.reverse(), }, - })) - .sort((a, b) => a.properties.name.localeCompare(b.properties.name)), + })), + ].sort((a, b) => a.properties.name.localeCompare(b.properties.name)), } } // Check if an areas.json exists config.scanAreas = { - main: config.manualAreas.length - ? { - type: 'FeatureCollection', - features: config.manualAreas - .filter((area) => ['lat', 'lon', 'name'].every((k) => k in area)) - .map((area) => { - const { lat, lon, ...rest } = area - return { - type: 'Feature', - properties: { - center: [lat, lon], - manual: true, - ...rest, - }, - geometry: { - type: 'Polygon', - coordinates: [[[lon, lat]]], - }, - } - }) - .sort((a, b) => a.properties.name.localeCompare(b.properties.name)), - } - : loadScanPolygons(config.map.geoJsonFileName), + main: loadScanPolygons(config.map.geoJsonFileName), ...Object.fromEntries( config.multiDomains.map((d) => [ d.general?.geoJsonFileName ? d.domain : 'main', @@ -224,7 +225,13 @@ config.scanAreas = { config.scanAreasMenu = Object.fromEntries( Object.entries(config.scanAreas).map(([domain, areas]) => { const parents = { '': { children: [], name: '' } } - areas.features.forEach((feature) => { + + const noHidden = { + ...areas, + features: areas.features.filter((f) => !f.properties.hidden), + } + // Finds unique parents and determines if the parents have their own properties + noHidden.features.forEach((feature) => { if (feature.properties.parent) { parents[feature.properties.parent] = { name: feature.properties.parent, @@ -235,13 +242,17 @@ config.scanAreasMenu = Object.fromEntries( } } }) - areas.features.forEach((feature) => { + + // Finds the children of each parent + noHidden.features.forEach((feature) => { if (feature.properties.parent) { parents[feature.properties.parent].children.push(feature) } else if (!parents[feature.properties.name]) { parents[''].children.push(feature) } }) + + // Create blanks for better formatting when there's an odd number of children Object.values(parents).forEach(({ children }) => { if (children.length % 2 === 1) { children.push({ diff --git a/server/src/services/functions/getAreaSql.js b/server/src/services/functions/getAreaSql.js index df85675ba..8871a76db 100644 --- a/server/src/services/functions/getAreaSql.js +++ b/server/src/services/functions/getAreaSql.js @@ -1,4 +1,3 @@ -const config = require('../config') const areas = require('../areas') module.exports = function getAreaRestrictionSql( @@ -8,11 +7,7 @@ module.exports = function getAreaRestrictionSql( isMad, category, ) { - if ( - !areaRestrictions?.length && - (!onlyAreas?.length || config.manualAreas.length) - ) - return true + if (!areaRestrictions?.length && !onlyAreas?.length) return true const cleanUserAreas = onlyAreas.filter((area) => areas.names.includes(area)) const consolidatedAreas = areaRestrictions.length diff --git a/src/components/layout/drawer/AreaTile.jsx b/src/components/layout/drawer/AreaTile.jsx index ad8178ab2..168154b66 100644 --- a/src/components/layout/drawer/AreaTile.jsx +++ b/src/components/layout/drawer/AreaTile.jsx @@ -21,13 +21,16 @@ export default function AreaTile({ const hasAll = childAreas && - childAreas.every((c) => scanAreas.filter.areas.includes(c.properties.name)) + childAreas.every( + (c) => + c.properties.manual || + scanAreas.filter.areas.includes(c.properties.name), + ) const hasSome = childAreas && childAreas.some((c) => scanAreas.filter.areas.includes(c.properties.name)) - const hasManual = childAreas - ? childAreas.some((c) => c.properties.manual) - : feature.properties.manual + const hasManual = + feature?.properties?.manual || childAreas.every((c) => c.properties.manual) return ( { - if (feature?.properties) { + if (feature?.properties?.center) { map.flyTo( feature.properties.center, feature.properties.zoom || scanAreasZoom, @@ -52,48 +56,53 @@ export default function AreaTile({ } }} > - - + + {name || feature.properties.name ? ( Utility.getProperName(name || feature.properties.name) + .split(' ') + .join('\n') ) : ( <>  )} - {!hasManual && ( - - + + setAreas( name - ? hasAll - : scanAreas.filter.areas.includes(feature.properties.name) - } - onChange={() => - setAreas( - name - ? childAreas.map((c) => c.properties.name) - : feature.properties.name, - allAreas, - name ? hasSome : false, - ) - } - disabled={!childAreas.length} - /> - - )} + ? childAreas.map((c) => c.properties.name) + : feature.properties.name, + allAreas, + name ? hasSome : false, + ) + } + style={{ + color: + !childAreas.length || hasManual + ? feature?.properties?.fillColor || + feature?.properties?.fill || + '#212121' + : 'none', + }} + disabled={!childAreas.length || hasManual} + /> + diff --git a/src/components/layout/drawer/Areas.jsx b/src/components/layout/drawer/Areas.jsx index d9003acdf..f1607570c 100644 --- a/src/components/layout/drawer/Areas.jsx +++ b/src/components/layout/drawer/Areas.jsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useMemo } from 'react' import { useQuery } from '@apollo/client' import { Grid, Button, Paper } from '@material-ui/core' import { useTranslation } from 'react-i18next' @@ -12,10 +12,12 @@ export default function AreaDropDown({ scanAreaMenuHeight, scanAreasZoom }) { const { t } = useTranslation() const setAreas = useStore((s) => s.setAreas) - const allAreas = React.useMemo(() => { + const allAreas = useMemo(() => { if (data?.scanAreasMenu) { return data.scanAreasMenu.flatMap((parent) => - parent.children.map((child) => child.properties.name), + parent.children + .filter((child) => !child.properties.manual) + .map((child) => child.properties.name), ) } return [] @@ -52,7 +54,7 @@ export default function AreaDropDown({ scanAreaMenuHeight, scanAreasZoom }) { {name && ( diff --git a/src/components/tiles/ScanArea.jsx b/src/components/tiles/ScanArea.jsx index 6629a4305..05386c8dc 100644 --- a/src/components/tiles/ScanArea.jsx +++ b/src/components/tiles/ScanArea.jsx @@ -13,6 +13,9 @@ export function ScanAreaTile({ userSettings, }) { const setAreas = useStore((s) => s.setAreas) + const names = item.features + .filter((f) => !f.properties.manual) + .map((f) => f.properties.name) const handleClick = (name) => { if (selectedAreas.includes(name)) { @@ -53,7 +56,7 @@ export function ScanAreaTile({ if (webhookMode) { layer.on('click', () => handleClick(name.toLowerCase())) } else if (!feature.properties.manual) { - layer.on('click', () => setAreas(name)) + layer.on('click', () => setAreas(name, names)) } } } diff --git a/src/hooks/useStore.js b/src/hooks/useStore.js index b6da1c7e1..7e214920a 100644 --- a/src/hooks/useStore.js +++ b/src/hooks/useStore.js @@ -31,8 +31,8 @@ export const useStore = create( ...filters.scanAreas, filter: { ...filters.scanAreas.filter, - areas: [...existing].filter( - (area) => !validAreas.length || validAreas.includes(area), + areas: [...existing].filter((area) => + validAreas.includes(area), ), }, }, From 88229c630291a26d148c06067fb67cbf9603cd24 Mon Sep 17 00:00:00 2001 From: TurtIeSocks <58572875+TurtIeSocks@users.noreply.github.com> Date: Sun, 26 Jun 2022 17:30:38 -0400 Subject: [PATCH 2/4] linting --- server/src/services/config.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/src/services/config.js b/server/src/services/config.js index 4de2fbb65..077f51351 100644 --- a/server/src/services/config.js +++ b/server/src/services/config.js @@ -169,7 +169,9 @@ config.authMethods = [ const manualGeojson = { type: 'FeatureCollection', features: config.manualAreas - .filter((area) => ['lat', 'lon', 'name'].every((k) => k in area && !area.hidden)) + .filter((area) => + ['lat', 'lon', 'name'].every((k) => k in area && !area.hidden), + ) .map((area) => { const { lat, lon, ...rest } = area return { From 40d3dd6ad4ac2ba6768400846d4bd6ffaed19599 Mon Sep 17 00:00:00 2001 From: TurtIeSocks <58572875+TurtIeSocks@users.noreply.github.com> Date: Sun, 26 Jun 2022 17:35:27 -0400 Subject: [PATCH 3/4] Update areas.js --- server/src/services/areas.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/services/areas.js b/server/src/services/areas.js index 525280a6c..b9d28da56 100644 --- a/server/src/services/areas.js +++ b/server/src/services/areas.js @@ -6,7 +6,7 @@ const loadAreas = () => { const normalized = { type: 'FeatureCollection', features: [] } Object.values(config.scanAreas).forEach((area) => { if (area?.features.length) { - normalized.features.push(...area.features) + normalized.features.push(...area.features.filter((f) => !f.manual)) } }) return normalized From 6a0b9711afd9c16d9866ec19ad050cb1661daec6 Mon Sep 17 00:00:00 2001 From: TurtIeSocks <58572875+TurtIeSocks@users.noreply.github.com> Date: Sun, 26 Jun 2022 17:36:59 -0400 Subject: [PATCH 4/4] Update default.json --- server/src/configs/default.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/configs/default.json b/server/src/configs/default.json index 236ed63e4..718171d47 100644 --- a/server/src/configs/default.json +++ b/server/src/configs/default.json @@ -94,7 +94,7 @@ "minZoom": 10, "maxZoom": 18, "interactionRangeZoom": 15, - "scanAreasZoom": 12, + "scanAreasZoom": 15, "scanCellsZoom": 13, "submissionZoom": 15, "activeWeatherZoom": 13,