From c5f7ecf389b036038bccaf97a362780abb62909b Mon Sep 17 00:00:00 2001 From: Chad Burt Date: Mon, 14 Aug 2023 12:06:52 -0700 Subject: [PATCH] Sketch class cartography improvements (#632) * Better support GLStyleEditor for sketch classes Includes a preview map, sketch-class-specific insert-new-layer actions, and adds a constraint and migration to for sketch_classes to have a populated mapbox_gl_style value. --- packages/api/generated-schema-clean.gql | 12 + packages/api/generated-schema.gql | 12 + packages/api/migrations/committed/000271.sql | 48 ++ packages/api/schema.sql | 22 +- .../api/src/plugins/sketchClassStylePlugin.ts | 1 - packages/api/tests/forms.test.ts | 38 +- packages/api/tests/sketchClasses.test.ts | 42 +- packages/api/tests/sketches.test.ts | 48 +- packages/client/craco.config.js | 1 + .../data/GLStyleEditor/GLStyleEditor.tsx | 60 +- .../data/GLStyleEditor/GeostatsModal.tsx | 16 +- .../extensions/glStyleAutocomplete.ts | 72 +- .../admin/sketchClasses/SketchClassForm.tsx | 2 +- .../sketchClasses/SketchClassStyleAdmin.tsx | 469 ++++++++++- .../client/src/admin/sketchClasses/line.json | 14 + .../client/src/admin/sketchClasses/point.json | 8 + .../src/admin/sketchClasses/scorpion.json | 761 ++++++++++++++++++ .../src/dataLayers/MapContextManager.ts | 1 + packages/client/src/generated/graphql.ts | 19 +- packages/client/src/generated/queries.ts | 19 +- .../projects/Sketches/SketchEditorModal.tsx | 29 + .../src/queries/SketchClassAdmin.graphql | 3 + 22 files changed, 1594 insertions(+), 103 deletions(-) create mode 100644 packages/api/migrations/committed/000271.sql create mode 100644 packages/client/src/admin/sketchClasses/line.json create mode 100644 packages/client/src/admin/sketchClasses/point.json create mode 100644 packages/client/src/admin/sketchClasses/scorpion.json diff --git a/packages/api/generated-schema-clean.gql b/packages/api/generated-schema-clean.gql index 3304d897b..87884841b 100644 --- a/packages/api/generated-schema-clean.gql +++ b/packages/api/generated-schema-clean.gql @@ -4768,6 +4768,16 @@ type EnableOfflineSupportPayload { query: Query } +enum ExtendedGeostatsType { + ARRAY + BOOLEAN + MIXED + NULL + NUMBER + OBJECT + STRING +} + """All input for the `failDataUpload` mutation.""" input FailDataUploadInput { """ @@ -5449,6 +5459,8 @@ type FormElementType implements Node { allowAdminUpdates: Boolean! allowedLayouts: [FormElementLayout] componentName: String! + geostatsArrayOf: ExtendedGeostatsType + geostatsType: ExtendedGeostatsType isHidden: Boolean! """ diff --git a/packages/api/generated-schema.gql b/packages/api/generated-schema.gql index 3304d897b..87884841b 100644 --- a/packages/api/generated-schema.gql +++ b/packages/api/generated-schema.gql @@ -4768,6 +4768,16 @@ type EnableOfflineSupportPayload { query: Query } +enum ExtendedGeostatsType { + ARRAY + BOOLEAN + MIXED + NULL + NUMBER + OBJECT + STRING +} + """All input for the `failDataUpload` mutation.""" input FailDataUploadInput { """ @@ -5449,6 +5459,8 @@ type FormElementType implements Node { allowAdminUpdates: Boolean! allowedLayouts: [FormElementLayout] componentName: String! + geostatsArrayOf: ExtendedGeostatsType + geostatsType: ExtendedGeostatsType isHidden: Boolean! """ diff --git a/packages/api/migrations/committed/000271.sql b/packages/api/migrations/committed/000271.sql new file mode 100644 index 000000000..ec4443f0e --- /dev/null +++ b/packages/api/migrations/committed/000271.sql @@ -0,0 +1,48 @@ +--! Previous: sha1:69255ad304c205eab43cd0db6d0193bc403b8f8a +--! Hash: sha1:6d0e0ecb4eb8cbce2e864dce288208def90f03d1 + +-- Enter migration here +drop type if exists extended_geostats_type cascade; +create type extended_geostats_type as enum ( + 'string', + 'number', + 'boolean', + 'null', + 'mixed', + 'array', + 'object' +); + +alter table form_element_types add column if not exists geostats_type extended_geostats_type default null; +alter table form_element_types add column if not exists geostats_array_of extended_geostats_type default null; + +update form_element_types set geostats_type = 'string' where component_name = 'ShortText'; +update form_element_types set geostats_type = 'string' where component_name = 'TextArea'; +update form_element_types set geostats_type = 'string' where component_name = 'Name'; +update form_element_types set geostats_type = 'string' where component_name = 'Email'; +update form_element_types set geostats_type = 'number' where component_name = 'Rating'; +update form_element_types set geostats_type = 'number' where component_name = 'Number'; +update form_element_types set geostats_type = 'boolean' where component_name = 'YesNo'; +update form_element_types set geostats_type = 'string' where component_name = 'ComboBox'; +update form_element_types set geostats_type = 'array' where component_name = 'MultipleChoice'; +update form_element_types set geostats_array_of = 'string' where component_name = 'MultipleChoice'; +update form_element_types set geostats_type = 'string' where component_name = 'FeatureName'; +update form_element_types set geostats_type = 'number' where component_name = 'SAPRange'; +update form_element_types set geostats_type = 'number' where component_name = 'ParticipantCount'; + + +-- Then all polygons missing a style +update sketch_classes set mapbox_gl_style = '[{"type":"fill","paint":{"fill-color":"#f28e2c","fill-opacity":0.6},"layout":{}},{"type":"line","paint":{"line-color":"#f28e2c","line-width":2},"layout":{}}]' where geometry_type = 'POLYGON' and mapbox_gl_style is null; +update sketch_classes set mapbox_gl_style = '[{"type":"fill","paint":{"fill-color":"#f28e2c","fill-opacity":0.6},"layout":{}},{"type":"line","paint":{"line-color":"#f28e2c","line-width":2},"layout":{}}]' where geometry_type = 'POLYGON' and mapbox_gl_style = '{}'::jsonb; + +-- Then all points missing a style +update sketch_classes set mapbox_gl_style = '[{"type":"circle","paint":{"circle-color":"#fb9b2d","circle-radius":5,"circle-opacity":0.5,"circle-stroke-color":"#ff822e","circle-stroke-width":1},"layout":{}}]' where geometry_type = 'POINT' and mapbox_gl_style is null; +update sketch_classes set mapbox_gl_style = '[{"type":"circle","paint":{"circle-color":"#fb9b2d","circle-radius":5,"circle-opacity":0.5,"circle-stroke-color":"#ff822e","circle-stroke-width":1},"layout":{}}]' where geometry_type = 'POINT' and mapbox_gl_style = '{}'::jsonb; + +-- Then all lines missing a style +update sketch_classes set mapbox_gl_style = '[{"type":"line","paint":{"line-color":"#ffffff","line-width":4},"layout":{"line-cap":"round"}},{"type":"line","paint":{"line-color":"#ff9d14","line-width":2},"layout":{}}]' where geometry_type = 'LINESTRING' and mapbox_gl_style is null; +update sketch_classes set mapbox_gl_style = '[{"type":"line","paint":{"line-color":"#ffffff","line-width":4},"layout":{"line-cap":"round"}},{"type":"line","paint":{"line-color":"#ff9d14","line-width":2},"layout":{}}]' where geometry_type = 'LINESTRING' and mapbox_gl_style = '{}'::jsonb; + +-- Then add a constraint to make sure gl style is set for all polygon, point, and line sketches +alter table sketch_classes drop constraint if exists sketch_classes_mapbox_gl_style_not_null; +alter table sketch_classes add constraint sketch_classes_mapbox_gl_style_not_null check (mapbox_gl_style is not null or geometry_type not in ('POLYGON', 'POINT', 'LINESTRING')); diff --git a/packages/api/schema.sql b/packages/api/schema.sql index 3486156ab..4079dd48d 100644 --- a/packages/api/schema.sql +++ b/packages/api/schema.sql @@ -256,6 +256,21 @@ CREATE TYPE public.email_summary_frequency AS ENUM ( ); +-- +-- Name: extended_geostats_type; Type: TYPE; Schema: public; Owner: - +-- + +CREATE TYPE public.extended_geostats_type AS ENUM ( + 'string', + 'number', + 'boolean', + 'null', + 'mixed', + 'array', + 'object' +); + + -- -- Name: field_rule_operator; Type: TYPE; Schema: public; Owner: - -- @@ -1274,7 +1289,8 @@ CREATE TABLE public.sketch_classes ( preprocessing_project_url text, translated_props jsonb DEFAULT '{}'::jsonb NOT NULL, CONSTRAINT sketch_classes_geoprocessing_client_url_check CHECK ((geoprocessing_client_url ~* 'https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,255}\.[a-z]{2,9}\y([-a-zA-Z0-9@:%_\+.~#?&//=]*)$'::text)), - CONSTRAINT sketch_classes_geoprocessing_project_url_check CHECK ((geoprocessing_project_url ~* 'https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,255}\.[a-z]{2,9}\y([-a-zA-Z0-9@:%_\+.~#?&//=]*)$'::text)) + CONSTRAINT sketch_classes_geoprocessing_project_url_check CHECK ((geoprocessing_project_url ~* 'https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,255}\.[a-z]{2,9}\y([-a-zA-Z0-9@:%_\+.~#?&//=]*)$'::text)), + CONSTRAINT sketch_classes_mapbox_gl_style_not_null CHECK (((mapbox_gl_style IS NOT NULL) OR (geometry_type <> ALL (ARRAY['POLYGON'::public.sketch_geometry_type, 'POINT'::public.sketch_geometry_type, 'LINESTRING'::public.sketch_geometry_type])))) ); @@ -6986,7 +7002,9 @@ CREATE TABLE public.form_element_types ( is_spatial boolean DEFAULT false NOT NULL, sketch_class_template_id integer, is_required_for_sketch_classes boolean DEFAULT false NOT NULL, - allow_admin_updates boolean DEFAULT true NOT NULL + allow_admin_updates boolean DEFAULT true NOT NULL, + geostats_type public.extended_geostats_type, + geostats_array_of public.extended_geostats_type ); diff --git a/packages/api/src/plugins/sketchClassStylePlugin.ts b/packages/api/src/plugins/sketchClassStylePlugin.ts index 93cb3f89b..7a2f5d6a0 100644 --- a/packages/api/src/plugins/sketchClassStylePlugin.ts +++ b/packages/api/src/plugins/sketchClassStylePlugin.ts @@ -32,7 +32,6 @@ const SketchClassStylePlugin = makeExtendSchemaPlugin((build) => { ) => { const { pgClient, adminPool } = context; const { sketchClassId, style } = args; - console.log({ style }); if (!sketchClassId) { throw new Error("sketchClassId is required"); } diff --git a/packages/api/tests/forms.test.ts b/packages/api/tests/forms.test.ts index b7cdc896f..cae556e6f 100644 --- a/packages/api/tests/forms.test.ts +++ b/packages/api/tests/forms.test.ts @@ -60,7 +60,7 @@ describe("Forms", () => { async (conn, projectId, adminId, userIds) => { await createSession(conn, adminId, true, false, projectId); const sketchClassId = await conn.oneFirst( - sql`insert into sketch_classes (name, project_id) values ('Sketch Class A', ${projectId}) returning id` + sql`insert into sketch_classes (mapbox_gl_style, name, project_id) values ('[]'::jsonb, 'Sketch Class A', ${projectId}) returning id` ); expect( conn.oneFirst( @@ -77,7 +77,7 @@ describe("Forms", () => { async (conn, projectId, adminId, userIds) => { await createSession(conn, adminId, true, false, projectId); const sketchClassId = await conn.oneFirst( - sql`insert into sketch_classes (name, project_id) values ('Sketch Class A', ${projectId}) returning id` + sql`insert into sketch_classes (mapbox_gl_style, name, project_id) values ('[]'::jsonb, 'Sketch Class A', ${projectId}) returning id` ); const form = await conn.one( sql`select * from initialize_blank_sketch_class_form(${sketchClassId})` @@ -95,7 +95,7 @@ describe("Forms", () => { async (conn, projectId, adminId, userIds) => { await createSession(conn, adminId, true, false, projectId); const sketchClassId = await conn.oneFirst( - sql`insert into sketch_classes (name, project_id) values ('Sketch Class A', ${projectId}) returning id` + sql`insert into sketch_classes (mapbox_gl_style, name, project_id) values ('[]'::jsonb, 'Sketch Class A', ${projectId}) returning id` ); await createSession(conn, userIds[0], true, false, projectId); expect( @@ -113,7 +113,7 @@ describe("Forms", () => { async (conn, projectId, adminId, userIds) => { await createSession(conn, adminId, true, false, projectId); const sketchClassId = await conn.oneFirst( - sql`insert into sketch_classes (name, project_id) values ('Sketch Class A', ${projectId}) returning id` + sql`insert into sketch_classes (mapbox_gl_style, name, project_id) values ('[]'::jsonb, 'Sketch Class A', ${projectId}) returning id` ); const form = await conn.one( sql`select * from initialize_blank_sketch_class_form(${sketchClassId})` @@ -141,7 +141,7 @@ describe("Forms", () => { async (conn, projectId, adminId, userIds) => { await createSession(conn, adminId, true, false, projectId); const sketchClassId = await conn.oneFirst( - sql`insert into sketch_classes (name, project_id) values ('Sketch Class A', ${projectId}) returning id` + sql`insert into sketch_classes (mapbox_gl_style, name, project_id) values ('[]'::jsonb, 'Sketch Class A', ${projectId}) returning id` ); let form = await conn.one( sql`select * from initialize_blank_sketch_class_form(${sketchClassId})` @@ -168,7 +168,7 @@ describe("Forms", () => { async (conn, projectId, groupId, adminId) => { await createSession(conn, adminId, true, false, projectId); const sketchClassId = await conn.oneFirst( - sql`insert into sketch_classes (name, project_id) values ('Sketch Class A', ${projectId}) returning id` + sql`insert into sketch_classes (mapbox_gl_style, name, project_id) values ('[]'::jsonb, 'Sketch Class A', ${projectId}) returning id` ); await limitToGroup(conn, "sketch_class_id", sketchClassId, groupId); let form = await conn.one( @@ -261,7 +261,7 @@ describe("Forms", () => { async (conn, projectId, adminId, userIds) => { await createSession(conn, adminId, true, true, projectId); const sketchClassId = await conn.oneFirst( - sql`insert into sketch_classes (name, project_id) values ('Sketch Class A', ${projectId}) returning id` + sql`insert into sketch_classes (mapbox_gl_style, name, project_id) values ('[]'::jsonb, 'Sketch Class A', ${projectId}) returning id` ); let form = await conn.one( sql`select * from initialize_blank_sketch_class_form(${sketchClassId})` @@ -289,7 +289,7 @@ describe("Forms", () => { async (conn, projectId, adminId, userIds) => { await createSession(conn, adminId, true, false, projectId); const sketchClassId = await conn.oneFirst( - sql`insert into sketch_classes (name, project_id) values ('Sketch Class A', ${projectId}) returning id` + sql`insert into sketch_classes (mapbox_gl_style, name, project_id) values ('[]'::jsonb, 'Sketch Class A', ${projectId}) returning id` ); const form = await conn.one( sql`select * from initialize_blank_sketch_class_form(${sketchClassId})` @@ -305,7 +305,7 @@ describe("Forms", () => { async (conn, projectId, adminId, userIds) => { await createSession(conn, adminId, true, true, projectId); const sketchClassId = await conn.oneFirst( - sql`insert into sketch_classes (name, project_id) values ('Sketch Class A', ${projectId}) returning id` + sql`insert into sketch_classes (mapbox_gl_style, name, project_id) values ('[]'::jsonb, 'Sketch Class A', ${projectId}) returning id` ); const source = await conn.one( sql`select * from initialize_blank_sketch_class_form(${sketchClassId})` @@ -316,7 +316,7 @@ describe("Forms", () => { expect(template.is_template).toBe(true); await createSession(conn, adminId, true, false, projectId); const sketchClassBId = await conn.oneFirst( - sql`insert into sketch_classes (name, project_id) values ('Sketch Class B', ${projectId}) returning id` + sql`insert into sketch_classes (mapbox_gl_style, name, project_id) values ('[]'::jsonb, 'Sketch Class B', ${projectId}) returning id` ); const form = await conn.one( sql`select * from initialize_sketch_class_form_from_template(${sketchClassBId}, ${template.id})` @@ -332,7 +332,7 @@ describe("Forms", () => { async (conn, projectId, adminId, userIds) => { await createSession(conn, adminId, true, true, projectId); const sketchClassId = await conn.oneFirst( - sql`insert into sketch_classes (name, project_id) values ('Sketch Class A', ${projectId}) returning id` + sql`insert into sketch_classes (mapbox_gl_style, name, project_id) values ('[]'::jsonb, 'Sketch Class A', ${projectId}) returning id` ); const source = await conn.one( sql`select * from initialize_blank_sketch_class_form(${sketchClassId})` @@ -353,7 +353,7 @@ describe("Forms", () => { ).toBe(1); await createSession(conn, adminId, true, false, projectId); const sketchClassBId = await conn.oneFirst( - sql`insert into sketch_classes (name, project_id) values ('Sketch Class B', ${projectId}) returning id` + sql`insert into sketch_classes (mapbox_gl_style, name, project_id) values ('[]'::jsonb, 'Sketch Class B', ${projectId}) returning id` ); const form = await conn.one( sql`select * from initialize_sketch_class_form_from_template(${sketchClassBId}, ${template.id})` @@ -383,7 +383,7 @@ describe("Form Fields", () => { async (conn, projectId, adminId, userIds) => { await createSession(conn, adminId, true, false, projectId); const sketchClassId = await conn.oneFirst( - sql`insert into sketch_classes (name, project_id) values ('Sketch Class A', ${projectId}) returning id` + sql`insert into sketch_classes (mapbox_gl_style, name, project_id) values ('[]'::jsonb, 'Sketch Class A', ${projectId}) returning id` ); const form = await conn.one( sql`select * from initialize_blank_sketch_class_form(${sketchClassId})` @@ -412,7 +412,7 @@ describe("Form Fields", () => { async (conn, projectId, adminId, userIds) => { await createSession(conn, adminId, true, false, projectId); const sketchClassId = await conn.oneFirst( - sql`insert into sketch_classes (name, project_id) values ('Sketch Class A', ${projectId}) returning id` + sql`insert into sketch_classes (mapbox_gl_style, name, project_id) values ('[]'::jsonb, 'Sketch Class A', ${projectId}) returning id` ); const form = await conn.one( sql`select * from initialize_blank_sketch_class_form(${sketchClassId})` @@ -491,7 +491,7 @@ describe("Form Fields", () => { async (conn, projectId, groupId, adminId) => { await createSession(conn, adminId, true, false, projectId); const sketchClassId = await conn.oneFirst( - sql`insert into sketch_classes (name, project_id) values ('Sketch Class A', ${projectId}) returning id` + sql`insert into sketch_classes (mapbox_gl_style, name, project_id) values ('[]'::jsonb, 'Sketch Class A', ${projectId}) returning id` ); await limitToGroup(conn, "sketch_class_id", sketchClassId, groupId); let form = await conn.one( @@ -514,7 +514,7 @@ describe("Form Fields", () => { async (conn, projectId, adminId, userIds) => { await createSession(conn, adminId, true, false, projectId); const sketchClassId = await conn.oneFirst( - sql`insert into sketch_classes (name, project_id) values ('Sketch Class A', ${projectId}) returning id` + sql`insert into sketch_classes (mapbox_gl_style, name, project_id) values ('[]'::jsonb, 'Sketch Class A', ${projectId}) returning id` ); const form = await conn.one( sql`select * from initialize_blank_sketch_class_form(${sketchClassId})` @@ -537,7 +537,7 @@ describe("Form Fields", () => { async (conn, projectId, adminId, userIds) => { await createSession(conn, adminId, true, false, projectId); const sketchClassId = await conn.oneFirst( - sql`insert into sketch_classes (name, project_id) values ('Sketch Class A', ${projectId}) returning id` + sql`insert into sketch_classes (mapbox_gl_style, name, project_id) values ('[]'::jsonb, 'Sketch Class A', ${projectId}) returning id` ); const form = await conn.one( sql`select * from initialize_blank_sketch_class_form(${sketchClassId})` @@ -577,7 +577,7 @@ describe("Form Fields", () => { async (conn, projectId, adminId, userIds) => { await createSession(conn, adminId, true, false, projectId); const sketchClassId = await conn.oneFirst( - sql`insert into sketch_classes (name, project_id) values ('Sketch Class A', ${projectId}) returning id` + sql`insert into sketch_classes (mapbox_gl_style, name, project_id) values ('[]'::jsonb, 'Sketch Class A', ${projectId}) returning id` ); const form = await conn.one( sql`select * from initialize_blank_sketch_class_form(${sketchClassId})` @@ -670,7 +670,7 @@ describe("Form Fields", () => { async (conn, projectId, adminId, userIds) => { await createSession(conn, adminId, true, false, projectId); const sketchClassId = await conn.oneFirst( - sql`insert into sketch_classes (name, project_id) values ('Sketch Class A', ${projectId}) returning id` + sql`insert into sketch_classes (mapbox_gl_style, name, project_id) values ('[]'::jsonb, 'Sketch Class A', ${projectId}) returning id` ); const form = await conn.one( sql`select * from initialize_blank_sketch_class_form(${sketchClassId})` diff --git a/packages/api/tests/sketchClasses.test.ts b/packages/api/tests/sketchClasses.test.ts index 8f29a0cf4..e77236fae 100644 --- a/packages/api/tests/sketchClasses.test.ts +++ b/packages/api/tests/sketchClasses.test.ts @@ -21,13 +21,13 @@ describe("Access Control", () => { const projectId = await createProject(conn, adminId); await createSession(conn, adminId, true, false, projectId); const sketchClass = await conn.one( - sql`insert into sketch_classes (project_id, name) values (${projectId}, 'MPA') returning *` + sql`insert into sketch_classes (mapbox_gl_style, project_id, name) values ('[]'::jsonb, ${projectId}, 'MPA') returning *` ); expect(sketchClass.name).toBe("MPA"); await createSession(conn, regularGuy, true, false, projectId); expect( conn.any( - sql`insert into sketch_classes (project_id, name) values (${projectId}, 'MPA-eyy!') returning *` + sql`insert into sketch_classes (mapbox_gl_style, project_id, name) values ('[]'::jsonb, ${projectId}, 'MPA-eyy!') returning *` ) ).rejects.toThrow(); await conn.any(sql`ROLLBACK`); @@ -50,7 +50,7 @@ describe("Access Control", () => { ); await createSession(conn, adminId, true, false, projectId); const sketchClass = await conn.one( - sql`insert into sketch_classes (project_id, name) values (${projectId}, 'MPA') returning *` + sql`insert into sketch_classes (mapbox_gl_style, project_id, name) values ('[]'::jsonb, ${projectId}, 'MPA') returning *` ); expect(sketchClass.name).toBe("MPA"); await createSession(conn, approvedParticipant, true, false, projectId); @@ -89,7 +89,7 @@ describe("Access Control", () => { approvedParticipant, ]); const sketchClass = await conn.one( - sql`insert into sketch_classes (project_id, name) values (${projectId}, 'MPA') returning *` + sql`insert into sketch_classes (mapbox_gl_style, project_id, name) values ('[]'::jsonb, ${projectId}, 'MPA') returning *` ); await clearSession(conn); const aclId = await conn.oneFirst( @@ -124,7 +124,7 @@ test("Cannot change project_id", async () => { const projectBId = await createProject(conn, adminId, "public"); await createSession(conn, adminId, true, false, projectId); const sketchClass = await conn.one( - sql`insert into sketch_classes (project_id, name) values (${projectId}, 'MPA') returning *` + sql`insert into sketch_classes (mapbox_gl_style, project_id, name) values ('[]'::jsonb, ${projectId}, 'MPA') returning *` ); expect(sketchClass.name).toBe("MPA"); expect( @@ -144,10 +144,10 @@ describe("Collections - valid children", () => { const projectId = await createProject(conn, adminId, "invite_only"); await createSession(conn, adminId, true, false, projectId); const collectionId = await conn.oneFirst( - sql`insert into sketch_classes (project_id, name, geometry_type) values (${projectId}, 'Collection', 'COLLECTION') returning id` + sql`insert into sketch_classes (mapbox_gl_style, project_id, name, geometry_type) values ('[]'::jsonb, ${projectId}, 'Collection', 'COLLECTION') returning id` ); const mpaId = await conn.oneFirst( - sql`insert into sketch_classes (project_id, name, geometry_type) values (${projectId}, 'MPA', 'POLYGON') returning id` + sql`insert into sketch_classes (mapbox_gl_style, project_id, name, geometry_type) values ('[]'::jsonb, ${projectId}, 'MPA', 'POLYGON') returning id` ); await conn.any( sql`select add_valid_child_sketch_class(${collectionId}, ${mpaId})` @@ -168,10 +168,10 @@ describe("Collections - valid children", () => { const projectId = await createProject(conn, adminId, "invite_only"); await createSession(conn, adminId, true, false, projectId); const collectionId = await conn.oneFirst( - sql`insert into sketch_classes (project_id, name, geometry_type) values (${projectId}, 'Collection', 'COLLECTION') returning id` + sql`insert into sketch_classes (mapbox_gl_style, project_id, name, geometry_type) values ('[]'::jsonb, ${projectId}, 'Collection', 'COLLECTION') returning id` ); const mpaId = await conn.oneFirst( - sql`insert into sketch_classes (project_id, name, geometry_type) values (${projectId}, 'MPA', 'POLYGON') returning id` + sql`insert into sketch_classes (mapbox_gl_style, project_id, name, geometry_type) values ('[]'::jsonb, ${projectId}, 'MPA', 'POLYGON') returning id` ); await conn.any( sql`select add_valid_child_sketch_class(${collectionId}, ${mpaId})` @@ -191,10 +191,10 @@ describe("Collections - valid children", () => { const projectId = await createProject(conn, adminId, "invite_only"); await createSession(conn, adminId, true, false, projectId); const collectionId = await conn.oneFirst( - sql`insert into sketch_classes (project_id, name, geometry_type) values (${projectId}, 'Collection', 'COLLECTION') returning id` + sql`insert into sketch_classes (mapbox_gl_style, project_id, name, geometry_type) values ('[]'::jsonb, ${projectId}, 'Collection', 'COLLECTION') returning id` ); const mpaId = await conn.oneFirst( - sql`insert into sketch_classes (project_id, name, geometry_type) values (${projectId}, 'MPA', 'POLYGON') returning id` + sql`insert into sketch_classes (mapbox_gl_style, project_id, name, geometry_type) values ('[]'::jsonb, ${projectId}, 'MPA', 'POLYGON') returning id` ); await conn.any( sql`select add_valid_child_sketch_class(${collectionId}, ${mpaId})` @@ -218,13 +218,13 @@ describe("Collections - valid children", () => { const projectId = await createProject(conn, adminId, "invite_only"); await createSession(conn, adminId, true, false, projectId); const collectionId = await conn.oneFirst( - sql`insert into sketch_classes (project_id, name, geometry_type) values (${projectId}, 'Collection', 'COLLECTION') returning id` + sql`insert into sketch_classes (mapbox_gl_style, project_id, name, geometry_type) values ('[]'::jsonb, ${projectId}, 'Collection', 'COLLECTION') returning id` ); const mpaId = await conn.oneFirst( - sql`insert into sketch_classes (project_id, name, geometry_type) values (${projectId}, 'MPA', 'POLYGON') returning id` + sql`insert into sketch_classes (mapbox_gl_style, project_id, name, geometry_type) values ('[]'::jsonb, ${projectId}, 'MPA', 'POLYGON') returning id` ); const notValidChildId = await conn.oneFirst( - sql`insert into sketch_classes (project_id, name, geometry_type) values (${projectId}, 'Not Valid Child', 'POLYGON') returning id` + sql`insert into sketch_classes (mapbox_gl_style, project_id, name, geometry_type) values ('[]'::jsonb, ${projectId}, 'Not Valid Child', 'POLYGON') returning id` ); await conn.any( sql`select add_valid_child_sketch_class(${collectionId}, ${mpaId})` @@ -243,10 +243,10 @@ describe("Collections - valid children", () => { const projectId = await createProject(conn, adminId, "invite_only"); await createSession(conn, adminId, true, false, projectId); const collectionId = await conn.oneFirst( - sql`insert into sketch_classes (project_id, name, geometry_type) values (${projectId}, 'Collection', 'COLLECTION') returning id` + sql`insert into sketch_classes (mapbox_gl_style, project_id, name, geometry_type) values ('[]'::jsonb, ${projectId}, 'Collection', 'COLLECTION') returning id` ); const mpaId = await conn.oneFirst( - sql`insert into sketch_classes (project_id, name, geometry_type) values (${projectId}, 'MPA', 'POLYGON') returning id` + sql`insert into sketch_classes (mapbox_gl_style, project_id, name, geometry_type) values ('[]'::jsonb, ${projectId}, 'MPA', 'POLYGON') returning id` ); expect( conn.any( @@ -264,10 +264,10 @@ describe("Collections - valid children", () => { const projectBId = await createProject(conn, adminId, "invite_only"); await createSession(conn, adminId, true, false, projectId); const collectionId = await conn.oneFirst( - sql`insert into sketch_classes (project_id, name, geometry_type) values (${projectBId}, 'Collection', 'COLLECTION') returning id` + sql`insert into sketch_classes (mapbox_gl_style, project_id, name, geometry_type) values ('[]'::jsonb, ${projectBId}, 'Collection', 'COLLECTION') returning id` ); const mpaId = await conn.oneFirst( - sql`insert into sketch_classes (project_id, name, geometry_type) values (${projectId}, 'MPA', 'POLYGON') returning id` + sql`insert into sketch_classes (mapbox_gl_style, project_id, name, geometry_type) values ('[]'::jsonb, ${projectId}, 'MPA', 'POLYGON') returning id` ); expect( conn.any( @@ -285,7 +285,7 @@ test("sketchCount indicates how many sketches have been created", async function const projectId = await createProject(conn, adminId); await createSession(conn, adminId, true, false, projectId); const sketchClassId = await conn.oneFirst( - sql`insert into sketch_classes (project_id, name, geometry_type) values (${projectId}, 'Collection', 'COLLECTION') returning id` + sql`insert into sketch_classes (mapbox_gl_style, project_id, name, geometry_type) values ('[]'::jsonb, ${projectId}, 'Collection', 'COLLECTION') returning id` ); let count = 11; while (count--) { @@ -308,7 +308,7 @@ test("Cannot delete SketchClasses with more that 10 sketches", async () => { const projectId = await createProject(conn, adminId); await createSession(conn, adminId, true, false, projectId); const sketchClassId = await conn.oneFirst( - sql`insert into sketch_classes (project_id, name, geometry_type) values (${projectId}, 'Collection', 'COLLECTION') returning id` + sql`insert into sketch_classes (mapbox_gl_style, project_id, name, geometry_type) values ('[]'::jsonb, ${projectId}, 'Collection', 'COLLECTION') returning id` ); let count = 11; while (count--) { @@ -446,7 +446,7 @@ test("Don't allow geometry type to be changed for sketch classes that aren't a p const projectBId = await createProject(conn, adminId, "public"); await createSession(conn, adminId, true, false, projectId); const sketchClass = await conn.one( - sql`insert into sketch_classes (project_id, name, geometry_type) values (${projectId}, 'MPA', 'POLYGON') returning *` + sql`insert into sketch_classes (mapbox_gl_style, project_id, name, geometry_type) values ('[]'::jsonb, ${projectId}, 'MPA', 'POLYGON') returning *` ); expect(sketchClass.name).toBe("MPA"); expect( diff --git a/packages/api/tests/sketches.test.ts b/packages/api/tests/sketches.test.ts index dabb1b3ac..1f2f5083d 100644 --- a/packages/api/tests/sketches.test.ts +++ b/packages/api/tests/sketches.test.ts @@ -24,7 +24,7 @@ describe("Access Control", () => { const projectId = await createProject(conn, adminId, "public"); await createSession(conn, adminId, true, false, projectId); const sketchClassId = await conn.oneFirst( - sql`insert into sketch_classes (project_id, name, geometry_type) values (${projectId}, 'Collection', 'COLLECTION') returning id` + sql`insert into sketch_classes (mapbox_gl_style, project_id, name, geometry_type) values ('[]'::jsonb, ${projectId}, 'Collection', 'COLLECTION') returning id` ); await createSession(conn, regularJoe, true, false, projectId); const sketchId = await conn.oneFirst( @@ -41,7 +41,7 @@ describe("Access Control", () => { const projectId = await createProject(conn, adminId, "public"); await createSession(conn, adminId, true, false, projectId); const sketchClassId = await conn.oneFirst( - sql`insert into sketch_classes (project_id, name, geometry_type) values (${projectId}, 'Collection', 'COLLECTION') returning id` + sql`insert into sketch_classes (mapbox_gl_style, project_id, name, geometry_type) values ('[]'::jsonb, ${projectId}, 'Collection', 'COLLECTION') returning id` ); await clearSession(conn); const aclId = await conn.oneFirst( @@ -73,7 +73,7 @@ describe("Access Control", () => { const projectId = await createProject(conn, adminId, "public"); await createSession(conn, adminId, true, false, projectId); const sketchClassId = await conn.oneFirst( - sql`insert into sketch_classes (project_id, name, geometry_type) values (${projectId}, 'Collection', 'COLLECTION') returning id` + sql`insert into sketch_classes (mapbox_gl_style, project_id, name, geometry_type) values ('[]'::jsonb, ${projectId}, 'Collection', 'COLLECTION') returning id` ); const groupId = await createGroup(conn, projectId, "Group A", [ groupMember, @@ -111,7 +111,7 @@ describe("Access Control", () => { const projectId = await createProject(conn, adminId, "public"); await createSession(conn, adminId, true, false, projectId); const sketchClassId = await conn.oneFirst( - sql`insert into sketch_classes (project_id, name, geometry_type, is_archived) values (${projectId}, 'Collection', 'COLLECTION', true) returning id` + sql`insert into sketch_classes (mapbox_gl_style, project_id, name, geometry_type, is_archived) values ('[]'::jsonb, ${projectId}, 'Collection', 'COLLECTION', true) returning id` ); const sketchId = await conn.oneFirst( sql`insert into sketches (user_id, sketch_class_id, name) values (${adminId}, ${sketchClassId}, 'my sketch') returning id` @@ -134,7 +134,7 @@ describe("Access Control", () => { const projectId = await createProject(conn, adminId, "public"); await createSession(conn, adminId, true, false, projectId); const sketchClassId = await conn.oneFirst( - sql`insert into sketch_classes (project_id, name, geometry_type) values (${projectId}, 'Collection', 'COLLECTION') returning id` + sql`insert into sketch_classes (mapbox_gl_style, project_id, name, geometry_type) values ('[]'::jsonb, ${projectId}, 'Collection', 'COLLECTION') returning id` ); await createSession(conn, regularJoe, true, false, projectId); expect( @@ -153,7 +153,7 @@ describe("Access Control", () => { const projectId = await createProject(conn, adminId, "public"); await createSession(conn, adminId, true, false, projectId); const sketchClassId = await conn.oneFirst( - sql`insert into sketch_classes (project_id, name, geometry_type) values (${projectId}, 'Collection', 'COLLECTION') returning id` + sql`insert into sketch_classes (mapbox_gl_style, project_id, name, geometry_type) values ('[]'::jsonb, ${projectId}, 'Collection', 'COLLECTION') returning id` ); await createSession(conn, userA, true, false, projectId); expect( @@ -172,7 +172,7 @@ describe("Access Control", () => { const projectId = await createProject(conn, adminId, "public"); await createSession(conn, adminId, true, false, projectId); const sketchClassId = await conn.oneFirst( - sql`insert into sketch_classes (project_id, name, geometry_type) values (${projectId}, 'Collection', 'COLLECTION') returning id` + sql`insert into sketch_classes (mapbox_gl_style, project_id, name, geometry_type) values ('[]'::jsonb, ${projectId}, 'Collection', 'COLLECTION') returning id` ); await createSession(conn, userA, true, false, projectId); const sketchId = await conn.oneFirst( @@ -195,7 +195,7 @@ describe("Access Control", () => { const projectId = await createProject(conn, adminId, "public"); await createSession(conn, adminId, true, false, projectId); const sketchClassId = await conn.oneFirst( - sql`insert into sketch_classes (project_id, name, geometry_type) values (${projectId}, 'Collection', 'COLLECTION') returning id` + sql`insert into sketch_classes (mapbox_gl_style, project_id, name, geometry_type) values ('[]'::jsonb, ${projectId}, 'Collection', 'COLLECTION') returning id` ); await createSession(conn, userA, true, false, projectId); const sketchAId = await conn.oneFirst( @@ -223,7 +223,7 @@ describe("Access Control", () => { const projectId = await createProject(conn, adminId, "public"); await createSession(conn, adminId, true, false, projectId); const sketchClassId = await conn.oneFirst( - sql`insert into sketch_classes (project_id, name, geometry_type) values (${projectId}, 'Collection', 'COLLECTION') returning id` + sql`insert into sketch_classes (mapbox_gl_style, project_id, name, geometry_type) values ('[]'::jsonb, ${projectId}, 'Collection', 'COLLECTION') returning id` ); await createSession(conn, userA, true, false, projectId); const sketchAId = await conn.oneFirst( @@ -362,7 +362,7 @@ const validateGeometryTypeCheck = async ( async (conn, projectId, adminId, userIds) => { await createSession(conn, adminId, true, false, projectId); const sketchClassId = await conn.oneFirst( - sql`insert into sketch_classes (project_id, name, geometry_type, allow_multi) values (${projectId}, ${ + sql`insert into sketch_classes (mapbox_gl_style, project_id, name, geometry_type, allow_multi) values ('[]'::jsonb, ${projectId}, ${ geometryType + " sketch class" }, ${geometryType}, ${allowMulti}) returning id` ); @@ -415,10 +415,10 @@ describe("Collections", () => { async (conn, projectId, adminId, userIds) => { await createSession(conn, adminId, true, false, projectId); const polygonClassId = await conn.oneFirst( - sql`insert into sketch_classes (project_id, name, geometry_type, allow_multi) values (${projectId}, 'Poly', 'POLYGON', false) returning id` + sql`insert into sketch_classes (mapbox_gl_style, project_id, name, geometry_type, allow_multi) values ('[]'::jsonb, ${projectId}, 'Poly', 'POLYGON', false) returning id` ); const collectionClassId = await conn.oneFirst( - sql`insert into sketch_classes (project_id, name, geometry_type) values (${projectId}, 'Collection', 'COLLECTION') returning id` + sql`insert into sketch_classes (mapbox_gl_style, project_id, name, geometry_type) values ('[]'::jsonb, ${projectId}, 'Collection', 'COLLECTION') returning id` ); await createSession(conn, userIds[0], true, false, projectId); const collectionId = await conn.oneFirst( @@ -439,7 +439,7 @@ describe("Collections", () => { async (conn, projectId, adminId, userIds) => { await createSession(conn, adminId, true, false, projectId); const collectionClassId = await conn.oneFirst( - sql`insert into sketch_classes (project_id, name, geometry_type) values (${projectId}, 'Collection', 'COLLECTION') returning id` + sql`insert into sketch_classes (mapbox_gl_style, project_id, name, geometry_type) values ('[]'::jsonb, ${projectId}, 'Collection', 'COLLECTION') returning id` ); await createSession(conn, userIds[0], true, false, projectId); const collectionId = await conn.oneFirst( @@ -460,13 +460,13 @@ describe("Collections", () => { async (conn, projectId, adminId, userIds) => { await createSession(conn, adminId, true, false, projectId); const polygonClassId = await conn.oneFirst( - sql`insert into sketch_classes (project_id, name, geometry_type, allow_multi) values (${projectId}, 'Poly', 'POLYGON', false) returning id` + sql`insert into sketch_classes (mapbox_gl_style, project_id, name, geometry_type, allow_multi) values ('[]'::jsonb, ${projectId}, 'Poly', 'POLYGON', false) returning id` ); const invalidChildId = await conn.oneFirst( - sql`insert into sketch_classes (project_id, name, geometry_type, allow_multi) values (${projectId}, 'Invalid child', 'POLYGON', false) returning id` + sql`insert into sketch_classes (mapbox_gl_style, project_id, name, geometry_type, allow_multi) values ('[]'::jsonb, ${projectId}, 'Invalid child', 'POLYGON', false) returning id` ); const collectionClassId = await conn.oneFirst( - sql`insert into sketch_classes (project_id, name, geometry_type) values (${projectId}, 'Collection', 'COLLECTION') returning id` + sql`insert into sketch_classes (mapbox_gl_style, project_id, name, geometry_type) values ('[]'::jsonb, ${projectId}, 'Collection', 'COLLECTION') returning id` ); await conn.any( sql`select add_valid_child_sketch_class(${collectionClassId}, ${polygonClassId})` @@ -497,7 +497,7 @@ describe("Generated columns", () => { async (conn, projectId, adminId, userIds) => { await createSession(conn, adminId, true, false, projectId); const polygonClassId = await conn.oneFirst( - sql`insert into sketch_classes (project_id, name, geometry_type, allow_multi) values (${projectId}, 'Poly', 'POLYGON', false) returning id` + sql`insert into sketch_classes (mapbox_gl_style, project_id, name, geometry_type, allow_multi) values ('[]'::jsonb, ${projectId}, 'Poly', 'POLYGON', false) returning id` ); await createSession(conn, userIds[0], true, false, projectId); const bbox = await conn.oneFirst( @@ -515,7 +515,7 @@ describe("Generated columns", () => { async (conn, projectId, adminId, userIds) => { await createSession(conn, adminId, true, false, projectId); const polygonClassId = await conn.oneFirst( - sql`insert into sketch_classes (project_id, name, geometry_type, allow_multi) values (${projectId}, 'Poly', 'POLYGON', false) returning id` + sql`insert into sketch_classes (mapbox_gl_style, project_id, name, geometry_type, allow_multi) values ('[]'::jsonb, ${projectId}, 'Poly', 'POLYGON', false) returning id` ); await createSession(conn, userIds[0], true, false, projectId); const numVertices = await conn.oneFirst( @@ -541,7 +541,7 @@ describe("Folders", () => { async (conn, projectId, adminId, userIds) => { await createSession(conn, adminId, true, false, projectId); const polygonClassId = await conn.oneFirst( - sql`insert into sketch_classes (project_id, name, geometry_type, allow_multi) values (${projectId}, 'Poly', 'POLYGON', false) returning id` + sql`insert into sketch_classes (mapbox_gl_style, project_id, name, geometry_type, allow_multi) values ('[]'::jsonb, ${projectId}, 'Poly', 'POLYGON', false) returning id` ); await createSession(conn, userIds[0], true, false, projectId); const folderId = await conn.oneFirst( @@ -578,10 +578,10 @@ describe("Folders", () => { async (conn, projectId, adminId, userIds) => { await createSession(conn, adminId, true, false, projectId); const polygonClassId = await conn.oneFirst( - sql`insert into sketch_classes (project_id, name, geometry_type, allow_multi) values (${projectId}, 'Poly', 'POLYGON', false) returning id` + sql`insert into sketch_classes (mapbox_gl_style, project_id, name, geometry_type, allow_multi) values ('[]'::jsonb, ${projectId}, 'Poly', 'POLYGON', false) returning id` ); const collectionClassId = await conn.oneFirst( - sql`insert into sketch_classes (project_id, name, geometry_type) values (${projectId}, 'Collection', 'COLLECTION') returning id` + sql`insert into sketch_classes (mapbox_gl_style, project_id, name, geometry_type) values ('[]'::jsonb, ${projectId}, 'Collection', 'COLLECTION') returning id` ); await createSession(conn, userIds[0], true, false, projectId); const folderId = await conn.oneFirst( @@ -685,7 +685,7 @@ describe("Copy operations", () => { async (conn, projectId, adminId, userIds) => { await createSession(conn, adminId, true, false, projectId); const polygonClassId = await conn.oneFirst( - sql`insert into sketch_classes (project_id, name, geometry_type, allow_multi) values (${projectId}, 'Poly', 'POLYGON', false) returning id` + sql`insert into sketch_classes (mapbox_gl_style, project_id, name, geometry_type, allow_multi) values ('[]'::jsonb, ${projectId}, 'Poly', 'POLYGON', false) returning id` ); await createSession(conn, userIds[0], true, false, projectId); const polyId = await conn.oneFirst( @@ -720,10 +720,10 @@ describe("Copy operations", () => { await createSession(conn, adminId, true, false, projectId); const polygonClassId = await conn.oneFirst( - sql`insert into sketch_classes (project_id, name, geometry_type, allow_multi) values (${projectId}, 'Poly', 'POLYGON', false) returning id` + sql`insert into sketch_classes (mapbox_gl_style, project_id, name, geometry_type, allow_multi) values ('[]'::jsonb, ${projectId}, 'Poly', 'POLYGON', false) returning id` ); const collectionClassId = await conn.oneFirst( - sql`insert into sketch_classes (project_id, name, geometry_type) values (${projectId}, 'Collection', 'COLLECTION') returning id` + sql`insert into sketch_classes (mapbox_gl_style, project_id, name, geometry_type) values ('[]'::jsonb, ${projectId}, 'Collection', 'COLLECTION') returning id` ); await createSession(conn, userIds[0], true, false, projectId); const collectionId = await conn.oneFirst( diff --git a/packages/client/craco.config.js b/packages/client/craco.config.js index be94b156f..1ce3256a4 100644 --- a/packages/client/craco.config.js +++ b/packages/client/craco.config.js @@ -90,6 +90,7 @@ module.exports = { }; babelLoaderOptions.ignore = [ "./node_modules/mapbox-gl/dist/mapbox-gl.js", + "./node_modules/d3-scale-chromatic", ]; return babelLoaderOptions; }, diff --git a/packages/client/src/admin/data/GLStyleEditor/GLStyleEditor.tsx b/packages/client/src/admin/data/GLStyleEditor/GLStyleEditor.tsx index f686b664d..f147063e6 100644 --- a/packages/client/src/admin/data/GLStyleEditor/GLStyleEditor.tsx +++ b/packages/client/src/admin/data/GLStyleEditor/GLStyleEditor.tsx @@ -7,6 +7,8 @@ import { linter, lintGutter } from "@codemirror/lint"; import { color } from "./extensions/glStyleColor"; import { glStyleLinter } from "./extensions/glStyleValidator"; import { + GeostatsAttribute, + GeostatsAttributeType, GeostatsLayer, getInsertLayerOptions, glStyleAutocomplete, @@ -33,6 +35,7 @@ import { useSpritesQuery, GetSpriteDocument, GetSpriteQuery, + AdminSketchingDetailsFragment, } from "../../../generated/graphql"; import getSlug from "../../../getSlug"; import SpritePopover from "./SpritePopover"; @@ -46,14 +49,37 @@ import { glStyleHoverTooltips } from "./extensions/glStyleHoverTooltips"; require("./RadixDropdown.css"); +/** + * Strict mapbox/geostats stringifies objects and arrays, which isn't helpful + * when dealing with sketch classes. GeoJSON can contain arrays and objects in + * properties, and so can MVT (it's not strictly specified in the spec). + * https://docs.mapbox.com/data/tilesets/guides/vector-tiles-standards/#how-to-encode-attributes-that-arent-strings-or-numbers + */ +export type ExtendedGeostatsAttributeType = + | "string" + | "number" + | "boolean" + | "null" + | "mixed" + | "object" + | "array"; +interface ExtendedGeostatsAttribute extends GeostatsAttribute { + type: ExtendedGeostatsAttributeType; + typeArrayOf?: GeostatsAttributeType; +} +export interface ExtendedGeostatsLayer extends GeostatsLayer { + attributes: ExtendedGeostatsAttribute[]; +} + interface GLStyleEditorProps { initialStyle?: string; type?: "vector" | "raster"; onChange?: (newStyle: string) => void; className?: string; - geostats?: GeostatsLayer; + geostats?: ExtendedGeostatsLayer; bounds?: [number, number, number, number]; tocItemId?: string; + onRequestShowBounds?: (bounds: [number, number, number, number]) => void; } /** @@ -99,6 +125,10 @@ export default function GLStyleEditor(props: GLStyleEditorProps) { const view = editorRef.current?.view; if (view) { const keydownHandler = (e: KeyboardEvent) => { + if (e.key === "s" && e.metaKey) { + e.preventDefault(); + return; + } if ( (e.target as Element).tagName && (e.target as Element).classList.contains("cm-content") @@ -228,20 +258,24 @@ export default function GLStyleEditor(props: GLStyleEditorProps) { className="p-2 border-b border-black border-opacity-30 z-10 shadow flex space-x-2 flex-0" style={{ backgroundColor: "#303841" }} > - {props.tocItemId && ( + {(props.tocItemId || props.geostats) && ( <> - { - if (mapContext.manager && props.geostats) { - mapContext.manager.map?.fitBounds(props.bounds!); - } - }} - label="Show layer extent" - /> + {props.bounds && ( + { + if (props.bounds && props.onRequestShowBounds) { + props.onRequestShowBounds(props.bounds); + } else if (mapContext.manager && props.geostats) { + mapContext.manager.map?.fitBounds(props.bounds!); + } + }} + label="Show layer extent" + /> + )} {props.tocItemId && (

- {layer.count} features + {layer.count}{" "} + {layer.count === 1 ? "feature" : "features"}

- {layer.attributeCount} properties + {layer.attributeCount}{" "} + {layer.attributeCount === 1 ? "property" : "properties"}
@@ -87,6 +89,16 @@ export default function GeostatsModal(props: GeostatsModalProps) { : v ) .join(", ")} + {attribute.type === "boolean" && + `true, false`} + {attribute.type === "string" && + attribute.values.length === 0 ? ( + + Values unknown + + ) : ( + "" + )} ))} diff --git a/packages/client/src/admin/data/GLStyleEditor/extensions/glStyleAutocomplete.ts b/packages/client/src/admin/data/GLStyleEditor/extensions/glStyleAutocomplete.ts index d07a3bb2d..e0295b953 100644 --- a/packages/client/src/admin/data/GLStyleEditor/extensions/glStyleAutocomplete.ts +++ b/packages/client/src/admin/data/GLStyleEditor/extensions/glStyleAutocomplete.ts @@ -5,11 +5,12 @@ import { SyntaxNode } from "@lezer/common"; import styleSpec from "mapbox-gl/src/style-spec/reference/v8.json"; import { GeoJsonGeometryTypes } from "geojson"; import { formatJSONCommand } from "../formatCommand"; +import { SpriteDetailsFragment } from "../../../../generated/graphql"; +import { ExtendedGeostatsLayer } from "../GLStyleEditor"; import { schemeTableau10, interpolatePlasma as interpolateColorScale, } from "d3-scale-chromatic"; -import { SpriteDetailsFragment } from "../../../../generated/graphql"; export interface GeostatsAttribute { attribute: string; @@ -172,6 +173,7 @@ export interface InsertLayerOption { min?: number; max?: number; type: PropertyValueType; + typeArrayOf?: PropertyValueType; }; layer: any; } @@ -1574,7 +1576,7 @@ function getRoot( } export function getInsertLayerOptions( - layer: GeostatsLayer, + layer: ExtendedGeostatsLayer, sprites: SpriteDetailsFragment[] ) { const options: InsertLayerOption[] = []; @@ -1702,13 +1704,22 @@ export function getInsertLayerOptions( layout: {}, }, }); - for (const attr of layer.attributes || []) { - if (attr.type === "string") { + for (const attribute of layer.attributes || []) { + if ( + (attribute.type === "string" && + (attribute.attribute !== "name" || attribute.values.length > 1)) || + (attribute.type === "array" && + attribute.typeArrayOf === "string" && + attribute.values.length > 1) + ) { options.push({ - label: "Fill color by string property", + label: + attribute.type === "array" + ? "Fill color by first value in string array" + : "Fill color by string property", propertyChoice: { - property: attr.attribute, - ...attr, + property: attribute.attribute, + ...attribute, }, type: "fill", layer: { @@ -1716,8 +1727,10 @@ export function getInsertLayerOptions( paint: { "fill-color": [ "match", - ["get", attr.attribute], - ...attr.values + attribute.type === "array" + ? ["at", 0, ["get", attribute.attribute]] + : ["get", attribute.attribute], + ...attribute.values .filter((v) => v !== null) .map((v, i) => { return [v, schemeTableau10[i % 10]]; @@ -1809,6 +1822,47 @@ export function getInsertLayerOptions( layout: {}, }, }); + for (const attribute of layer.attributes || []) { + if ( + (attribute.type === "string" && + (attribute.attribute !== "name" || attribute.values.length > 1)) || + (attribute.type === "array" && + attribute.typeArrayOf === "string" && + attribute.values.length > 1) + ) { + options.push({ + label: + attribute.type === "array" + ? "Line color by first value in string array" + : "Line color by string property", + propertyChoice: { + property: attribute.attribute, + ...attribute, + }, + type: "line", + layer: { + type: "line", + paint: { + "line-color": [ + "match", + attribute.type === "array" + ? ["at", 0, ["get", attribute.attribute]] + : ["get", attribute.attribute], + ...attribute.values + .filter((v) => v !== null) + .map((v, i) => { + return [v, schemeTableau10[i % 10]]; + }) + .flat(), + "black", + ], + "line-width": 2, + }, + layout: {}, + }, + }); + } + } } if (layer.attributes.find((a) => a.type === "string")) { const isLine = diff --git a/packages/client/src/admin/sketchClasses/SketchClassForm.tsx b/packages/client/src/admin/sketchClasses/SketchClassForm.tsx index 1c33a82b9..bcee63b44 100644 --- a/packages/client/src/admin/sketchClasses/SketchClassForm.tsx +++ b/packages/client/src/admin/sketchClasses/SketchClassForm.tsx @@ -146,7 +146,7 @@ export default function SketchClassForm({ color="text-indigo-100" /> -
+
setSelectedTab(id)} />
diff --git a/packages/client/src/admin/sketchClasses/SketchClassStyleAdmin.tsx b/packages/client/src/admin/sketchClasses/SketchClassStyleAdmin.tsx index 4b3881629..c5360920d 100644 --- a/packages/client/src/admin/sketchClasses/SketchClassStyleAdmin.tsx +++ b/packages/client/src/admin/sketchClasses/SketchClassStyleAdmin.tsx @@ -2,9 +2,22 @@ import { useDebouncedFn } from "beautiful-react-hooks"; import { useGlobalErrorHandler } from "../../components/GlobalErrorHandler"; import { AdminSketchingDetailsFragment, + ExtendedGeostatsType, + SketchGeometryType, useUpdateSketchClassStyleMutation, } from "../../generated/graphql"; -import GLStyleEditor from "../data/GLStyleEditor/GLStyleEditor"; +import GLStyleEditor, { + ExtendedGeostatsAttributeType, + ExtendedGeostatsLayer, +} from "../data/GLStyleEditor/GLStyleEditor"; +import { FormElementOption } from "../../formElements/FormElementOptionsInput"; +import { useEffect, useMemo, useRef, useState } from "react"; +import { Map } from "mapbox-gl"; +import scorpion from "./scorpion.json"; +import point from "./point.json"; +import line from "./line.json"; +import { Feature } from "geojson"; +import bbox from "@turf/bbox"; export default function SketchClassStyleAdmin({ sketchClass, @@ -15,6 +28,64 @@ export default function SketchClassStyleAdmin({ const [mutate, mutationState] = useUpdateSketchClassStyleMutation({ onError, }); + const [map, setMap] = useState(null); + const [sketchAttributeValues, setSketchAttributeValues] = useState<{ + [exportId: string]: any; + }>(getDefaultSketchProperties(sketchClass)); + + const [bounds, setBounds] = useState<[number, number, number, number] | null>( + null + ); + + const mapContainer = useRef(null); + useEffect(() => { + if (mapContainer.current) { + if (map) { + map.remove(); + } + const m = new Map({ + container: mapContainer.current!, + center: [-119.57150866402623, 34.075223633555865], + zoom: 10.35, + style: "mapbox://styles/mapbox/streets-v12", + accessToken: process.env.REACT_APP_MAPBOX_TOKEN, + }); + let data = scorpion as any; + if (sketchClass.geometryType === SketchGeometryType.Point) { + data = point; + } else if (sketchClass.geometryType === SketchGeometryType.Linestring) { + data = line; + } + setBounds(bbox(data) as [number, number, number, number]); + data.properties = sketchAttributeValues; + m.on("load", () => { + setMap(m); + m.addSource("sketch", { + type: "geojson", + // @ts-ignore + data, + }); + const glStyle = Array.isArray(sketchClass.mapboxGlStyle) + ? sketchClass.mapboxGlStyle + : []; + let i = 0; + for (const layer of glStyle) { + m.addLayer({ + // eslint-disable-next-line i18next/no-literal-string + id: `sketch-${i++}`, + ...layer, + source: "sketch", + }); + } + }); + return () => { + m.remove(); + }; + } + // No need to include mapboxGlStyle in the dependency array + // We'll update it manually in the update function + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [mapContainer, sketchClass.geometryType]); const update = useDebouncedFn( (id: number, newStyle: string) => { @@ -31,13 +102,395 @@ export default function SketchClassStyleAdmin({ } ); + useEffect(() => { + if (map && map.getStyle()) { + // remove existing layers with ids starting with sketch- + const layers = map.getStyle().layers || []; + for (const layer of layers) { + if (layer.id.startsWith("sketch-")) { + map.removeLayer(layer.id); + } + } + let i = 0; + for (const layer of Array.isArray(sketchClass.mapboxGlStyle) + ? sketchClass.mapboxGlStyle + : []) { + map.addLayer({ + // eslint-disable-next-line i18next/no-literal-string + id: `sketch-${i++}`, + ...layer, + source: "sketch", + }); + } + } + }, [map, sketchClass.mapboxGlStyle]); + + const geostats = useMemo(() => { + return sketchClassToGeostats(sketchClass); + }, [sketchClass.form?.formElements, sketchClass.geometryType]); + + const relevantProps = useMemo(() => { + const props = extractRelevantPropsFromStyle( + sketchClass.mapboxGlStyle || [] + ); + return props.map((exportId) => { + const fe = sketchClass.form?.formElements?.find( + (fe) => + fe.exportId === exportId || + (exportId === "name" && fe.type?.componentName === "FeatureName") + ); + return fe; + }); + }, [sketchClass.mapboxGlStyle]); + + useEffect(() => { + if (map && "getSource" in map) { + try { + const data = map?.getSource("sketch"); + let newData = scorpion as any; + if (sketchClass.geometryType === SketchGeometryType.Point) { + newData = point; + } else if (sketchClass.geometryType === SketchGeometryType.Linestring) { + newData = line; + } + if (data && data.type === "geojson") { + // @ts-ignore + newData.properties = sketchAttributeValues; + // @ts-ignore + data.setData(newData); + } + } catch (e) { + console.error(e); + } + } + }, [sketchAttributeValues, map, sketchClass]); + return ( - { - update(sketchClass.id, newStyle); - }} - /> +
+
+ {/* eslint-disable-next-line i18next/no-literal-string */} +
+ {relevantProps.map((fe) => { + if (fe?.type?.componentName === "FeatureName") { + return ( + { + setSketchAttributeValues((prev) => ({ + ...prev, + name: e.target.value, + })); + }} + /> + ); + } else { + switch (fe?.type?.geostatsType) { + case ExtendedGeostatsType.String: + if ( + fe?.componentSettings?.options && + fe?.componentSettings?.options?.length > 0 + ) { + return ( + + ); + } else { + return ( + + ); + } + case ExtendedGeostatsType.Boolean: + return ( +
+ + { + setSketchAttributeValues((prev) => ({ + ...prev, + [fe.generatedExportId]: e.target.checked, + })); + }} + /> +
+ ); + case ExtendedGeostatsType.Number: + return ( + { + setSketchAttributeValues((prev) => ({ + ...prev, + [fe.generatedExportId]: e.target.value, + })); + }} + /> + ); + case ExtendedGeostatsType.Array: + if ( + fe.type?.geostatsArrayOf === ExtendedGeostatsType.String && + fe.componentSettings.options + ) { + return ( + + ); + } + break; + } + // return components[fe?.type?.componentName || ""].displayName; + } + // return fe?.type?.componentName || ""; + return null; + })} +
+ { + update(sketchClass.id, newStyle); + }} + geostats={geostats} + bounds={bounds || undefined} + onRequestShowBounds={() => { + map?.fitBounds(bounds!, { padding: 20 }); + }} + /> +
); } + +function sketchClassToGeostats( + sketchClass: AdminSketchingDetailsFragment +): ExtendedGeostatsLayer { + const inputs = (sketchClass.form?.formElements || []).filter( + (fe) => fe.isInput && fe.type?.geostatsType + ); + const geostats: ExtendedGeostatsLayer = { + layer: sketchClass.name, + geometry: coerceGeometrytype(sketchClass.geometryType), + count: 1, + attributeCount: inputs.length, + attributes: inputs.map((fe) => { + const values = fe.componentSettings.options + ? (fe.componentSettings.options as FormElementOption[]).map( + (v) => v.value || v.label + ) + : []; + return { + attribute: + fe.type?.componentName === "FeatureName" + ? "name" + : fe.generatedExportId, + type: coerceGeostatsType(fe.type?.geostatsType)!, + typeArrayOf: coerceGeostatsType(fe.type?.geostatsArrayOf, false), + count: values.length, + values, + }; + }), + }; + return geostats; +} + +function coerceGeostatsType(type: any, isRequired = true) { + if (typeof type !== "string") { + if (isRequired) { + throw new Error("expected string"); + } else { + return undefined; + } + } + return type.toLowerCase() as ExtendedGeostatsAttributeType; +} + +function coerceGeometrytype(type: SketchGeometryType) { + switch (type) { + case SketchGeometryType.Polygon: + return "Polygon"; + case SketchGeometryType.Linestring: + return "LineString"; + case SketchGeometryType.Point: + return "Point"; + default: + throw new Error("Unsupported geometry type"); + break; + } +} + +function getDefaultSketchProperties( + sketchClass: AdminSketchingDetailsFragment +) { + const props: { [exportId: string]: any } = {}; + + for (const fe of sketchClass.form?.formElements || []) { + if (fe.isInput && fe.type?.geostatsType) { + switch (fe.type?.geostatsType) { + case ExtendedGeostatsType.Number: + props[fe.generatedExportId] = 0; + break; + case ExtendedGeostatsType.String: + if ( + fe.componentSettings.options && + fe.componentSettings.options.length + ) { + const value = ( + fe.componentSettings.options as FormElementOption[] + )[0]; + props[fe.generatedExportId] = value.value || value.label; + } else if (fe.type.componentName === "FeatureName") { + props["name"] = "Sketch Name"; + } else { + props[fe.generatedExportId] = ""; + } + break; + case ExtendedGeostatsType.Boolean: + props[fe.generatedExportId] = true; + break; + case ExtendedGeostatsType.Mixed: + props[fe.generatedExportId] = ""; + break; + case ExtendedGeostatsType.Null: + props[fe.generatedExportId] = null; + break; + case ExtendedGeostatsType.Object: + props[fe.generatedExportId] = {}; + break; + case ExtendedGeostatsType.Array: + if (fe.type?.geostatsArrayOf) { + switch (fe.type?.geostatsArrayOf) { + case ExtendedGeostatsType.Number: + props[fe.generatedExportId] = [0]; + break; + case ExtendedGeostatsType.String: + if ( + fe.componentSettings.options && + fe.componentSettings.options.length > 0 + ) { + const value = ( + fe.componentSettings.options as FormElementOption[] + )[0]; + props[fe.generatedExportId] = [value.value || value.label]; + } else { + props[fe.generatedExportId] = [""]; + } + + break; + case ExtendedGeostatsType.Boolean: + props[fe.generatedExportId] = [true]; + break; + case ExtendedGeostatsType.Mixed: + props[fe.generatedExportId] = [""]; + break; + case ExtendedGeostatsType.Null: + props[fe.generatedExportId] = [null]; + break; + case ExtendedGeostatsType.Object: + props[fe.generatedExportId] = [{}]; + break; + } + } else { + props[fe.generatedExportId] = []; + } + break; + } + } + } + return props; +} + +export function extractRelevantPropsFromStyle(style: any) { + const props: string[] = []; + if (Array.isArray(style)) { + for (const layer of style) { + extractProps(layer, props); + } + } + return props; +} + +function extractProps(obj: any, props: string[]) { + for (const key in obj) { + if (Array.isArray(obj[key]) && obj[key].length > 0) { + extractPropsFromExpression(obj[key], props); + } else if (typeof obj[key] === "object") { + extractProps(obj[key], props); + } else { + // end of the line + } + } +} + +function extractPropsFromExpression(expression: Array, props: string[]) { + const expressionName = expression[0]; + if (expressionName === "get") { + if (props.indexOf(expression[1]) === -1) { + props.push(expression[1]); + } + } else { + for (const arg of expression.slice(1)) { + if (Array.isArray(arg)) { + extractPropsFromExpression(arg, props); + } + } + } +} diff --git a/packages/client/src/admin/sketchClasses/line.json b/packages/client/src/admin/sketchClasses/line.json new file mode 100644 index 000000000..1ec43a5cf --- /dev/null +++ b/packages/client/src/admin/sketchClasses/line.json @@ -0,0 +1,14 @@ +{ + "type": "Feature", + "properties": {}, + "geometry": { + "coordinates": [ + [-119.58131108157991, 34.071276446934775], + [-119.55196236376638, 34.06332311297602], + [-119.54863472104033, 34.056001770775495], + [-119.55108953944512, 34.051436910214875], + [-119.55550821257283, 34.04944818122284] + ], + "type": "LineString" + } +} diff --git a/packages/client/src/admin/sketchClasses/point.json b/packages/client/src/admin/sketchClasses/point.json new file mode 100644 index 000000000..08c7dedf4 --- /dev/null +++ b/packages/client/src/admin/sketchClasses/point.json @@ -0,0 +1,8 @@ +{ + "type": "Feature", + "properties": {}, + "geometry": { + "coordinates": [-119.55616283081406, 34.065537468443324], + "type": "Point" + } +} diff --git a/packages/client/src/admin/sketchClasses/scorpion.json b/packages/client/src/admin/sketchClasses/scorpion.json new file mode 100644 index 000000000..6879f63cf --- /dev/null +++ b/packages/client/src/admin/sketchClasses/scorpion.json @@ -0,0 +1,761 @@ +{ + "id": 18152, + "bbox": [-119.59168, 34.04495, -119.54667, 34.10602], + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-119.591680492, 34.049257396], + [-119.591667191, 34.049252649], + [-119.590856, 34.049389], + [-119.59037, 34.049182], + [-119.589528, 34.048986], + [-119.588761, 34.048977], + [-119.587956, 34.049], + [-119.58757, 34.049204], + [-119.587484, 34.0496], + [-119.587624, 34.049823], + [-119.587844, 34.04992], + [-119.587879, 34.050127], + [-119.587922, 34.050302], + [-119.587922, 34.050599], + [-119.588003, 34.050725], + [-119.588252, 34.050801], + [-119.588457, 34.050908], + [-119.588625, 34.050975], + [-119.588869, 34.051102], + [-119.589227, 34.051518], + [-119.58877, 34.051592], + [-119.588497, 34.051524], + [-119.588218, 34.05144], + [-119.58786, 34.051432], + [-119.587905, 34.051524], + [-119.587484, 34.051481], + [-119.587216, 34.051394], + [-119.586982, 34.051345], + [-119.586338, 34.051879], + [-119.58591, 34.052364], + [-119.585626, 34.05236], + [-119.58554, 34.052302], + [-119.585363, 34.052698], + [-119.584698, 34.05332], + [-119.584386, 34.053435], + [-119.583979, 34.053444], + [-119.583796, 34.053356], + [-119.583701, 34.053313], + [-119.583466, 34.053128], + [-119.583379, 34.053038], + [-119.583327, 34.052908], + [-119.583293, 34.052734], + [-119.583154, 34.052551], + [-119.583015, 34.05243], + [-119.582659, 34.052202], + [-119.582477, 34.052121], + [-119.58206, 34.051992], + [-119.581912, 34.051929], + [-119.581782, 34.051849], + [-119.581756, 34.051783], + [-119.581747, 34.051679], + [-119.581617, 34.051612], + [-119.581435, 34.05138], + [-119.581348, 34.051296], + [-119.581261, 34.051181], + [-119.581209, 34.051044], + [-119.581157, 34.050995], + [-119.580966, 34.050909], + [-119.580402, 34.050931], + [-119.580211, 34.050877], + [-119.580028, 34.050802], + [-119.579716, 34.050793], + [-119.579525, 34.050799], + [-119.57936, 34.050821], + [-119.57923, 34.050784], + [-119.579074, 34.050768], + [-119.578926, 34.050726], + [-119.578657, 34.05061], + [-119.578518, 34.05053], + [-119.578275, 34.050475], + [-119.578144, 34.050497], + [-119.577581, 34.050517], + [-119.577337, 34.050573], + [-119.577111, 34.050355], + [-119.576886, 34.05034], + [-119.576287, 34.05019], + [-119.57607, 34.050164], + [-119.575897, 34.050207], + [-119.575749, 34.050202], + [-119.575462, 34.050039], + [-119.575384, 34.049917], + [-119.575254, 34.049837], + [-119.575054, 34.04987], + [-119.574829, 34.049987], + [-119.574568, 34.050048], + [-119.574239, 34.050106], + [-119.574021, 34.050084], + [-119.573743, 34.050099], + [-119.573517, 34.050145], + [-119.573335, 34.050221], + [-119.573284, 34.050325], + [-119.573301, 34.050495], + [-119.573188, 34.050685], + [-119.573309, 34.050948], + [-119.573196, 34.051057], + [-119.57298, 34.05113], + [-119.572771, 34.05123], + [-119.572745, 34.051362], + [-119.57278, 34.051494], + [-119.572745, 34.051587], + [-119.572466, 34.051612], + [-119.572433, 34.05164], + [-119.572419, 34.051662], + [-119.572433, 34.051706], + [-119.572439, 34.051717], + [-119.572446, 34.051728], + [-119.572472, 34.051766], + [-119.572551, 34.051804], + [-119.572584, 34.051843], + [-119.572617, 34.05192], + [-119.572631, 34.051936], + [-119.572644, 34.051953], + [-119.57265, 34.051997], + [-119.572683, 34.052008], + [-119.572743, 34.052013], + [-119.572809, 34.052052], + [-119.572862, 34.052085], + [-119.572882, 34.052118], + [-119.572875, 34.052145], + [-119.572848, 34.052156], + [-119.572835, 34.052145], + [-119.572815, 34.052107], + [-119.572789, 34.052096], + [-119.572769, 34.052085], + [-119.572723, 34.052079], + [-119.572716, 34.052063], + [-119.572697, 34.052052], + [-119.572637, 34.052046], + [-119.572571, 34.052096], + [-119.572551, 34.052101], + [-119.572426, 34.052162], + [-119.572406, 34.052173], + [-119.572386, 34.052178], + [-119.572373, 34.052184], + [-119.572327, 34.052222], + [-119.572301, 34.052238], + [-119.572287, 34.052249], + [-119.572254, 34.052299], + [-119.572248, 34.05231], + [-119.572241, 34.052321], + [-119.572241, 34.052348], + [-119.572222, 34.052409], + [-119.572208, 34.052436], + [-119.572195, 34.052458], + [-119.572175, 34.05248], + [-119.572162, 34.052491], + [-119.572089, 34.052519], + [-119.572076, 34.052519], + [-119.57201, 34.05253], + [-119.571957, 34.052519], + [-119.571938, 34.052513], + [-119.571858, 34.052491], + [-119.571845, 34.05248], + [-119.571786, 34.052453], + [-119.571753, 34.052436], + [-119.571707, 34.05242], + [-119.57166, 34.052403], + [-119.571647, 34.052398], + [-119.571627, 34.052387], + [-119.571601, 34.05237], + [-119.571581, 34.052359], + [-119.571568, 34.052359], + [-119.571502, 34.052348], + [-119.571462, 34.052343], + [-119.571449, 34.052343], + [-119.571429, 34.052337], + [-119.571337, 34.052332], + [-119.571311, 34.052321], + [-119.571304, 34.052299], + [-119.571304, 34.052288], + [-119.571311, 34.052272], + [-119.571304, 34.052189], + [-119.571251, 34.052145], + [-119.571192, 34.052118], + [-119.571165, 34.052112], + [-119.570809, 34.052112], + [-119.570782, 34.052107], + [-119.570763, 34.052107], + [-119.570624, 34.052096], + [-119.570604, 34.05209], + [-119.570571, 34.052079], + [-119.570558, 34.052074], + [-119.570499, 34.052063], + [-119.57038, 34.052068], + [-119.570367, 34.052063], + [-119.570261, 34.052068], + [-119.570122, 34.052079], + [-119.570096, 34.052074], + [-119.570083, 34.052074], + [-119.570043, 34.052068], + [-119.570023, 34.052063], + [-119.569964, 34.052046], + [-119.569792, 34.052046], + [-119.569693, 34.052041], + [-119.569634, 34.052046], + [-119.569548, 34.052046], + [-119.569516, 34.052131], + [-119.568466, 34.052214], + [-119.568214, 34.05225], + [-119.567841, 34.052169], + [-119.567546, 34.052067], + [-119.567128, 34.051874], + [-119.566868, 34.051788], + [-119.566712, 34.052009], + [-119.566755, 34.052218], + [-119.566581, 34.052681], + [-119.566521, 34.052941], + [-119.566703, 34.053237], + [-119.566756, 34.053362], + [-119.566651, 34.053517], + [-119.566555, 34.053742], + [-119.566365, 34.054041], + [-119.566174, 34.054157], + [-119.565982, 34.054175], + [-119.565852, 34.054216], + [-119.565548, 34.05448], + [-119.565366, 34.054579], + [-119.56528, 34.05468], + [-119.565227, 34.055041], + [-119.565106, 34.05522], + [-119.564915, 34.055341], + [-119.564733, 34.055352], + [-119.564429, 34.055301], + [-119.564203, 34.055285], + [-119.564021, 34.055368], + [-119.563839, 34.055489], + [-119.563656, 34.055574], + [-119.563509, 34.05575], + [-119.56337, 34.055881], + [-119.563301, 34.055877], + [-119.563066, 34.055758], + [-119.562901, 34.055715], + [-119.562701, 34.055703], + [-119.56271, 34.055622], + [-119.562849, 34.0555], + [-119.562788, 34.055417], + [-119.562649, 34.055348], + [-119.562554, 34.05528], + [-119.562276, 34.055364], + [-119.562146, 34.055383], + [-119.562007, 34.055278], + [-119.561937, 34.055174], + [-119.561747, 34.055005], + [-119.561721, 34.05493], + [-119.561929, 34.054708], + [-119.561955, 34.054657], + [-119.561928, 34.054392], + [-119.561825, 34.054196], + [-119.561564, 34.053999], + [-119.561347, 34.053928], + [-119.561139, 34.053942], + [-119.560904, 34.053897], + [-119.560601, 34.053796], + [-119.560444, 34.053724], + [-119.560288, 34.053621], + [-119.560071, 34.053558], + [-119.559533, 34.053588], + [-119.559229, 34.053471], + [-119.558977, 34.0533], + [-119.558587, 34.052969], + [-119.558404, 34.052917], + [-119.557935, 34.052827], + [-119.55777, 34.052705], + [-119.557762, 34.05254], + [-119.557588, 34.052085], + [-119.557207, 34.051806], + [-119.556747, 34.051532], + [-119.556538, 34.051364], + [-119.556494, 34.051155], + [-119.556642, 34.050718], + [-119.55659, 34.050538], + [-119.556268, 34.050193], + [-119.556251, 34.050046], + [-119.556151, 34.049864], + [-119.556314, 34.049694], + [-119.55636, 34.049608], + [-119.556328, 34.04949], + [-119.556302, 34.049326], + [-119.556232, 34.049177], + [-119.556107, 34.049002], + [-119.556085, 34.04898], + [-119.555945, 34.048836], + [-119.555488, 34.048581], + [-119.555358, 34.04853], + [-119.555113, 34.048434], + [-119.554784, 34.048288], + [-119.554186, 34.048143], + [-119.553916, 34.048106], + [-119.553586, 34.048106], + [-119.553343, 34.048059], + [-119.552562, 34.047809], + [-119.552423, 34.047789], + [-119.552042, 34.047786], + [-119.551824, 34.04773], + [-119.551642, 34.047606], + [-119.551563, 34.047457], + [-119.55165, 34.047286], + [-119.551598, 34.047027], + [-119.551486, 34.04682], + [-119.551451, 34.046755], + [-119.55126, 34.046617], + [-119.551043, 34.046536], + [-119.550843, 34.046515], + [-119.550627, 34.046516], + [-119.550071, 34.046592], + [-119.549758, 34.046615], + [-119.549376, 34.046568], + [-119.548812, 34.046415], + [-119.548638, 34.046336], + [-119.548595, 34.046263], + [-119.548534, 34.046087], + [-119.548282, 34.046082], + [-119.547987, 34.046266], + [-119.547727, 34.046256], + [-119.547397, 34.046044], + [-119.547301, 34.04594], + [-119.547301, 34.045848], + [-119.547354, 34.045687], + [-119.547408, 34.045527], + [-119.54731, 34.045456], + [-119.547362, 34.045124], + [-119.54721, 34.04495], + [-119.547129, 34.044965], + [-119.547084, 34.045208], + [-119.547025, 34.045256], + [-119.546888, 34.045181], + [-119.546799, 34.045101], + [-119.546671925, 34.045077173], + [-119.546671945, 34.047079634], + [-119.54673, 34.047085], + [-119.546776, 34.04709], + [-119.54679, 34.047096], + [-119.546948, 34.047085], + [-119.5471, 34.04709], + [-119.547133, 34.04709], + [-119.547172, 34.047096], + [-119.547186, 34.047096], + [-119.547324, 34.047118], + [-119.547377, 34.047184], + [-119.54739, 34.047195], + [-119.547416, 34.047217], + [-119.54745, 34.04725], + [-119.547476, 34.047294], + [-119.547489, 34.047299], + [-119.547562, 34.04737], + [-119.547568, 34.047381], + [-119.547582, 34.047387], + [-119.547614, 34.047409], + [-119.547707, 34.047464], + [-119.547813, 34.047497], + [-119.547846, 34.047508], + [-119.547865, 34.047513], + [-119.547879, 34.047524], + [-119.547892, 34.047524], + [-119.547925, 34.047557], + [-119.547977, 34.047585], + [-119.547997, 34.04759], + [-119.547997, 34.047601], + [-119.548017, 34.047623], + [-119.548037, 34.047645], + [-119.548057, 34.047733], + [-119.54805, 34.047783], + [-119.548037, 34.047799], + [-119.548024, 34.04781], + [-119.54801, 34.047821], + [-119.547984, 34.047849], + [-119.547964, 34.047865], + [-119.547945, 34.047876], + [-119.547892, 34.047898], + [-119.547859, 34.047914], + [-119.547839, 34.047914], + [-119.547812, 34.04792], + [-119.547799, 34.04792], + [-119.547786, 34.047914], + [-119.547674, 34.047903], + [-119.547634, 34.047909], + [-119.547588, 34.04792], + [-119.547542, 34.047947], + [-119.547489, 34.047964], + [-119.547377, 34.047964], + [-119.547364, 34.047964], + [-119.54735, 34.047964], + [-119.547331, 34.047969], + [-119.547284, 34.04798], + [-119.547278, 34.047997], + [-119.547251, 34.048002], + [-119.547232, 34.048002], + [-119.547212, 34.048002], + [-119.547166, 34.047991], + [-119.547126, 34.047991], + [-119.546994, 34.047953], + [-119.546908, 34.047931], + [-119.546908, 34.04792], + [-119.546875, 34.047892], + [-119.546869, 34.047881], + [-119.546855, 34.047859], + [-119.546849, 34.047848], + [-119.546849, 34.047832], + [-119.546842, 34.047799], + [-119.546789, 34.047771], + [-119.546776, 34.04776], + [-119.54675, 34.047749], + [-119.54673, 34.047738], + [-119.546671951, 34.047694903], + [-119.546672527, 34.104182491], + [-119.547302304, 34.104326726], + [-119.548365332, 34.104556024], + [-119.549665085, 34.104811208], + [-119.5513866, 34.105110202], + [-119.55339206, 34.105402368], + [-119.554146899, 34.105491127], + [-119.556490382, 34.105744142], + [-119.558723634, 34.105903168], + [-119.560702296, 34.105988228], + [-119.562823886, 34.106021513], + [-119.565145079, 34.1059885], + [-119.566458231, 34.105929328], + [-119.568092972, 34.105829474], + [-119.570121425, 34.105662735], + [-119.572426142, 34.105463027], + [-119.573926887, 34.105318794], + [-119.574989915, 34.10519675], + [-119.576513543, 34.104993267], + [-119.577585504, 34.104823144], + [-119.578786994, 34.104634529], + [-119.580019749, 34.104394137], + [-119.580774588, 34.104242505], + [-119.581510235, 34.104088047], + [-119.582608995, 34.103840257], + [-119.583354901, 34.103666434], + [-119.583989145, 34.103684926], + [-119.58494051, 34.103703417], + [-119.585882943, 34.103707116], + [-119.587017435, 34.103707116], + [-119.587754117, 34.103680403], + [-119.588540222, 34.103658212], + [-119.589317393, 34.103632324], + [-119.590568015, 34.103562055], + [-119.591675708, 34.103495484], + [-119.591680492, 34.049257396] + ], + [ + [-119.577911, 34.050562], + [-119.577898, 34.050513], + [-119.577792, 34.050529], + [-119.577766, 34.050535], + [-119.57768, 34.050568], + [-119.577673, 34.050579], + [-119.577687, 34.050612], + [-119.57768, 34.050634], + [-119.577667, 34.05065], + [-119.57766, 34.050672], + [-119.57766, 34.050683], + [-119.57766, 34.050705], + [-119.577733, 34.050689], + [-119.577753, 34.050689], + [-119.577772, 34.050689], + [-119.577792, 34.050678], + [-119.577812, 34.050661], + [-119.577825, 34.050656], + [-119.577891, 34.050601], + [-119.577898, 34.050584], + [-119.577911, 34.050562] + ], + [ + [-119.573924, 34.052425], + [-119.573918, 34.052414], + [-119.573911, 34.052392], + [-119.573865, 34.052354], + [-119.573845, 34.052348], + [-119.57372, 34.052519], + [-119.573707, 34.052541], + [-119.57368, 34.052579], + [-119.573667, 34.052612], + [-119.573654, 34.052639], + [-119.573654, 34.052661], + [-119.573654, 34.052678], + [-119.573654, 34.052733], + [-119.573674, 34.052744], + [-119.573733, 34.052738], + [-119.573872, 34.052606], + [-119.573924, 34.052579], + [-119.573918, 34.052464], + [-119.573924, 34.052447], + [-119.573924, 34.052425] + ], + [ + [-119.573641, 34.052337], + [-119.573601, 34.052167], + [-119.573548, 34.052118], + [-119.573535, 34.052112], + [-119.573509, 34.052107], + [-119.573377, 34.052057], + [-119.573363, 34.052063], + [-119.573337, 34.052068], + [-119.573258, 34.052079], + [-119.573258, 34.052101], + [-119.573251, 34.052118], + [-119.573251, 34.052167], + [-119.573244, 34.052189], + [-119.573225, 34.052233], + [-119.573225, 34.052326], + [-119.573231, 34.05237], + [-119.573212, 34.052403], + [-119.573218, 34.052425], + [-119.573205, 34.052458], + [-119.573198, 34.052475], + [-119.573179, 34.052486], + [-119.573172, 34.052497], + [-119.573139, 34.052535], + [-119.573132, 34.052546], + [-119.573073, 34.052612], + [-119.57306, 34.052623], + [-119.573033, 34.052656], + [-119.572987, 34.052733], + [-119.572981, 34.052744], + [-119.572981, 34.052755], + [-119.572967, 34.052793], + [-119.572967, 34.052804], + [-119.572967, 34.052826], + [-119.572974, 34.052837], + [-119.572981, 34.052848], + [-119.573, 34.052865], + [-119.573033, 34.052914], + [-119.573047, 34.052925], + [-119.573066, 34.052931], + [-119.573106, 34.052947], + [-119.573126, 34.052953], + [-119.573165, 34.052958], + [-119.573179, 34.052958], + [-119.573251, 34.052958], + [-119.573271, 34.052953], + [-119.57339, 34.052903], + [-119.573403, 34.052892], + [-119.573482, 34.052793], + [-119.573489, 34.052782], + [-119.573502, 34.052771], + [-119.573515, 34.052744], + [-119.573561, 34.052656], + [-119.573588, 34.052552], + [-119.573443, 34.052612], + [-119.573436, 34.052628], + [-119.57339, 34.052656], + [-119.573337, 34.052656], + [-119.573324, 34.052634], + [-119.573311, 34.052612], + [-119.573311, 34.052508], + [-119.573311, 34.052497], + [-119.573377, 34.052354], + [-119.57341, 34.052326], + [-119.573436, 34.052326], + [-119.573449, 34.052343], + [-119.573469, 34.052365], + [-119.573509, 34.052359], + [-119.573542, 34.052359], + [-119.573588, 34.052392], + [-119.573641, 34.052337] + ], + [ + [-119.567956, 34.053502], + [-119.567936, 34.053491], + [-119.567923, 34.053491], + [-119.567903, 34.053485], + [-119.56785, 34.053419], + [-119.567797, 34.053403], + [-119.567784, 34.053403], + [-119.567771, 34.053403], + [-119.567758, 34.053403], + [-119.567712, 34.053458], + [-119.567705, 34.053491], + [-119.567731, 34.053518], + [-119.567751, 34.053524], + [-119.567804, 34.05354], + [-119.567844, 34.053551], + [-119.567857, 34.053557], + [-119.56787, 34.053551], + [-119.56789, 34.053546], + [-119.56791, 34.053551], + [-119.567929, 34.05354], + [-119.567949, 34.053524], + [-119.567956, 34.053502] + ], + [ + [-119.566263, 34.055471], + [-119.566256, 34.055443], + [-119.566236, 34.055432], + [-119.566223, 34.055427], + [-119.56619, 34.055416], + [-119.566124, 34.055427], + [-119.566098, 34.055438], + [-119.566098, 34.055465], + [-119.566098, 34.055504], + [-119.566098, 34.055564], + [-119.566117, 34.055586], + [-119.56617, 34.055586], + [-119.566197, 34.055575], + [-119.566223, 34.055547], + [-119.56623, 34.055536], + [-119.566249, 34.055509], + [-119.566263, 34.055471] + ], + [ + [-119.552908, 34.049283], + [-119.552882, 34.049255], + [-119.552862, 34.049239], + [-119.552716, 34.049255], + [-119.552637, 34.049288], + [-119.552644, 34.049305], + [-119.552644, 34.049338], + [-119.55265, 34.049354], + [-119.55267, 34.049371], + [-119.552697, 34.049376], + [-119.552769, 34.049376], + [-119.552789, 34.049382], + [-119.552815, 34.049387], + [-119.552848, 34.049387], + [-119.552888, 34.049349], + [-119.552908, 34.049305], + [-119.552908, 34.049283] + ], + [ + [-119.550855, 34.048552], + [-119.550849, 34.048525], + [-119.550842, 34.048497], + [-119.550809, 34.048475], + [-119.550776, 34.048464], + [-119.550657, 34.048486], + [-119.550604, 34.048508], + [-119.550598, 34.048596], + [-119.550631, 34.048618], + [-119.550657, 34.048634], + [-119.550717, 34.048634], + [-119.550816, 34.048607], + [-119.550849, 34.048585], + [-119.550855, 34.048552] + ], + [ + [-119.548585, 34.046365], + [-119.548565, 34.046316], + [-119.548558, 34.046305], + [-119.548552, 34.046288], + [-119.548512, 34.046255], + [-119.548479, 34.046255], + [-119.548459, 34.046261], + [-119.548433, 34.046272], + [-119.548433, 34.046283], + [-119.5484, 34.04631], + [-119.54838, 34.046321], + [-119.54838, 34.046349], + [-119.548387, 34.046371], + [-119.548433, 34.046393], + [-119.548446, 34.046404], + [-119.54846, 34.046404], + [-119.548479, 34.046404], + [-119.548539, 34.046404], + [-119.548578, 34.046382], + [-119.548585, 34.046365] + ], + [ + [-119.548374, 34.047343], + [-119.548367, 34.047277], + [-119.548367, 34.047266], + [-119.54836, 34.047244], + [-119.54836, 34.047233], + [-119.548321, 34.047162], + [-119.548308, 34.047145], + [-119.548275, 34.047118], + [-119.548248, 34.047107], + [-119.548215, 34.047079], + [-119.548182, 34.047063], + [-119.548169, 34.047052], + [-119.548149, 34.047046], + [-119.548096, 34.047008], + [-119.548083, 34.047002], + [-119.548057, 34.046991], + [-119.54803, 34.046958], + [-119.548024, 34.046876], + [-119.548024, 34.046865], + [-119.548011, 34.046849], + [-119.547978, 34.04681], + [-119.547964, 34.046805], + [-119.547925, 34.046799], + [-119.547865, 34.046777], + [-119.547852, 34.046772], + [-119.54776, 34.046777], + [-119.5477, 34.046799], + [-119.547634, 34.046816], + [-119.547628, 34.046827], + [-119.547575, 34.04686], + [-119.547562, 34.046865], + [-119.547549, 34.046881], + [-119.547522, 34.046898], + [-119.547496, 34.046909], + [-119.547489, 34.04692], + [-119.547463, 34.046936], + [-119.547476, 34.046964], + [-119.547496, 34.046969], + [-119.547509, 34.04698], + [-119.547522, 34.04698], + [-119.547621, 34.046997], + [-119.547634, 34.047002], + [-119.547661, 34.047008], + [-119.547674, 34.047013], + [-119.5477, 34.047024], + [-119.547727, 34.047035], + [-119.54776, 34.047057], + [-119.547786, 34.047074], + [-119.547799, 34.047096], + [-119.547813, 34.047107], + [-119.547905, 34.047184], + [-119.547938, 34.047217], + [-119.547984, 34.047239], + [-119.548004, 34.04725], + [-119.548024, 34.047272], + [-119.548044, 34.047277], + [-119.548063, 34.047288], + [-119.548083, 34.047305], + [-119.54811, 34.047321], + [-119.548242, 34.047398], + [-119.548281, 34.047398], + [-119.548354, 34.047376], + [-119.548374, 34.047343] + ], + [ + [-119.547311, 34.04709], + [-119.547265, 34.047024], + [-119.547232, 34.046997], + [-119.547212, 34.046986], + [-119.547199, 34.046975], + [-119.547146, 34.046931], + [-119.547133, 34.04692], + [-119.54712, 34.046914], + [-119.547106, 34.046903], + [-119.54706, 34.046881], + [-119.54704, 34.046881], + [-119.547027, 34.046881], + [-119.546968, 34.046892], + [-119.546941, 34.046898], + [-119.546941, 34.046964], + [-119.546968, 34.046991], + [-119.546988, 34.047002], + [-119.547093, 34.047068], + [-119.54712, 34.047063], + [-119.547133, 34.047068], + [-119.547205, 34.047074], + [-119.547245, 34.047079], + [-119.547278, 34.04709], + [-119.547311, 34.04709] + ] + ] + }, + "properties": {} +} diff --git a/packages/client/src/dataLayers/MapContextManager.ts b/packages/client/src/dataLayers/MapContextManager.ts index f7ec12705..ce44efdc1 100644 --- a/packages/client/src/dataLayers/MapContextManager.ts +++ b/packages/client/src/dataLayers/MapContextManager.ts @@ -11,6 +11,7 @@ import mapboxgl, { AnySourceData, AnyLayer, Sources, + GeoJSONSource, } from "mapbox-gl"; import { createContext, diff --git a/packages/client/src/generated/graphql.ts b/packages/client/src/generated/graphql.ts index 8b3d2b708..454b346c2 100644 --- a/packages/client/src/generated/graphql.ts +++ b/packages/client/src/generated/graphql.ts @@ -4075,6 +4075,16 @@ export type EnableOfflineSupportPayloadProjectEdgeArgs = { orderBy?: Maybe>; }; +export enum ExtendedGeostatsType { + Array = 'ARRAY', + Boolean = 'BOOLEAN', + Mixed = 'MIXED', + Null = 'NULL', + Number = 'NUMBER', + Object = 'OBJECT', + String = 'STRING' +} + /** All input for the `failDataUpload` mutation. */ export type FailDataUploadInput = { /** @@ -4601,6 +4611,8 @@ export type FormElementType = Node & { allowAdminUpdates: Scalars['Boolean']; allowedLayouts?: Maybe>>; componentName: Scalars['String']; + geostatsArrayOf?: Maybe; + geostatsType?: Maybe; isHidden: Scalars['Boolean']; /** * Whether the element is an input that collects information from users or @@ -16920,7 +16932,7 @@ export type SketchFormElementFragment = ( & Pick & { type?: Maybe<( { __typename?: 'FormElementType' } - & Pick + & Pick )> } ); @@ -17252,7 +17264,7 @@ export type UpdateSketchClassStyleMutation = ( { __typename?: 'Mutation' } & { updateSketchClassMapboxGLStyle: ( { __typename?: 'SketchClass' } - & Pick + & Pick ) } ); @@ -19858,6 +19870,8 @@ export const SketchFormElementFragmentDoc = gql` isSurveysOnly label isHidden + geostatsType + geostatsArrayOf } } `; @@ -26131,6 +26145,7 @@ export const UpdateSketchClassStyleDocument = gql` mutation UpdateSketchClassStyle($id: Int!, $style: JSON) { updateSketchClassMapboxGLStyle(sketchClassId: $id, style: $style) { id + mapboxGlStyle } } `; diff --git a/packages/client/src/generated/queries.ts b/packages/client/src/generated/queries.ts index bf88da85c..3090d6edc 100644 --- a/packages/client/src/generated/queries.ts +++ b/packages/client/src/generated/queries.ts @@ -4073,6 +4073,16 @@ export type EnableOfflineSupportPayloadProjectEdgeArgs = { orderBy?: Maybe>; }; +export enum ExtendedGeostatsType { + Array = 'ARRAY', + Boolean = 'BOOLEAN', + Mixed = 'MIXED', + Null = 'NULL', + Number = 'NUMBER', + Object = 'OBJECT', + String = 'STRING' +} + /** All input for the `failDataUpload` mutation. */ export type FailDataUploadInput = { /** @@ -4599,6 +4609,8 @@ export type FormElementType = Node & { allowAdminUpdates: Scalars['Boolean']; allowedLayouts?: Maybe>>; componentName: Scalars['String']; + geostatsArrayOf?: Maybe; + geostatsType?: Maybe; isHidden: Scalars['Boolean']; /** * Whether the element is an input that collects information from users or @@ -16918,7 +16930,7 @@ export type SketchFormElementFragment = ( & Pick & { type?: Maybe<( { __typename?: 'FormElementType' } - & Pick + & Pick )> } ); @@ -17250,7 +17262,7 @@ export type UpdateSketchClassStyleMutation = ( { __typename?: 'Mutation' } & { updateSketchClassMapboxGLStyle: ( { __typename?: 'SketchClass' } - & Pick + & Pick ) } ); @@ -19856,6 +19868,8 @@ export const SketchFormElementFragmentDoc = /*#__PURE__*/ gql` isSurveysOnly label isHidden + geostatsType + geostatsArrayOf } } `; @@ -22278,6 +22292,7 @@ export const UpdateSketchClassStyleDocument = /*#__PURE__*/ gql` mutation UpdateSketchClassStyle($id: Int!, $style: JSON) { updateSketchClassMapboxGLStyle(sketchClassId: $id, style: $style) { id + mapboxGlStyle } } `; diff --git a/packages/client/src/projects/Sketches/SketchEditorModal.tsx b/packages/client/src/projects/Sketches/SketchEditorModal.tsx index 103356d86..51d49675e 100644 --- a/packages/client/src/projects/Sketches/SketchEditorModal.tsx +++ b/packages/client/src/projects/Sketches/SketchEditorModal.tsx @@ -37,6 +37,7 @@ import SketchForm from "./SketchForm"; import { useTranslatedProps } from "../../components/TranslatedPropControl"; import { FormElementLayoutContext } from "../../surveys/SurveyAppLayout"; import { defaultStyle } from "../../surveys/appearance"; +// import { extractRelevantPropsFromStyle } from "../../admin/sketchClasses/SketchClassStyleAdmin"; export default function SketchEditorModal({ sketch, @@ -69,6 +70,34 @@ export default function SketchEditorModal({ const [properties, setProperties] = useState(sketch?.properties || {}); const [hasValidationErrors, setHasValidationErrors] = useState(false); + // const styleRelevantProps = useMemo(() => { + // return extractRelevantPropsFromStyle(sketchClass.mapboxGlStyle || []); + // }, [sketchClass]); + // // + // console.log(styleRelevantProps, properties); + + // useEffect(() => { + // const elements = sketchClass.form?.formElements || []; + // if (styleRelevantProps.length && elements.length && mapContext.manager) { + // const newProperties: { [key: string]: any } = {}; + // styleRelevantProps.forEach((prop) => { + // const element = + // prop === "name" + // ? elements.find((e) => e.type?.componentName === "FeatureName") + // : elements.find((e) => e.generatedExportId === prop); + // if (element) { + // newProperties[prop] = properties[element.id]; + // } + // }); + // mapContext.manager.updateSketchProperties(sketch?.id || 0, newProperties); + // } + // }, [ + // styleRelevantProps, + // properties, + // sketchClass.form?.formElements, + // mapContext.manager, + // ]); + const formElements = useMemo(() => { const elements = sketchClass.form?.formElements || []; let sorted = [...elements].sort((a, b) => a.position - b.position); diff --git a/packages/client/src/queries/SketchClassAdmin.graphql b/packages/client/src/queries/SketchClassAdmin.graphql index 0a251b485..c2255b821 100644 --- a/packages/client/src/queries/SketchClassAdmin.graphql +++ b/packages/client/src/queries/SketchClassAdmin.graphql @@ -17,6 +17,8 @@ fragment SketchFormElement on FormElement { isSurveysOnly label isHidden + geostatsType + geostatsArrayOf } } @@ -301,5 +303,6 @@ mutation DeleteVisibilityRuleCondition($id: Int!) { mutation UpdateSketchClassStyle($id: Int!, $style: JSON) { updateSketchClassMapboxGLStyle(sketchClassId: $id, style: $style) { id + mapboxGlStyle } }