Skip to content

Commit

Permalink
Merge branch 'master' into h3-filter-tool
Browse files Browse the repository at this point in the history
  • Loading branch information
underbluewaters committed Oct 2, 2024
2 parents 5d560bf + f977f36 commit 3d32087
Show file tree
Hide file tree
Showing 33 changed files with 732 additions and 48 deletions.
81 changes: 54 additions & 27 deletions packages/client/src/MeasureControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ import { useMediaQuery } from "beautiful-react-hooks";
let featureId = 0;

const emptyFeatureCollection = () =>
({
type: "FeatureCollection",
features: [],
} as FeatureCollection);
({
type: "FeatureCollection",
features: [],
} as FeatureCollection);

export const MeasureControlLockId = "MeasureControl";

Expand Down Expand Up @@ -150,6 +150,15 @@ class MeasureControl extends EventEmitter {
private isDestroyed = false;
private length = 0;
private mapContextManager: MapContextManager;
/**
* During the React component lifecycle, sometimes the MeasureControl
* is created and destroyed out of sync with the map. This counter
* is used to enable repeated calls to destroy when even listeners are
* fired on destroyed MeasureControls, so that event listeners can be
* removed. Using the counter, the number of times this will be
* attempted before throwing an exception can be limited (3 tries).
*/
private destroyCount = 0;

constructor(mapContextManager: MapContextManager) {
super();
Expand Down Expand Up @@ -301,6 +310,10 @@ class MeasureControl extends EventEmitter {
* map. Call before discarding this instance of MeasureControl.
*/
destroy = () => {
if (this.destroyCount > 3) {
throw new Error("MeasureControl is already destroyed");
}
this.destroyCount++;
if (this.map && this.map.loaded()) {
for (const layer of measureLayers) {
this.map.removeLayer(layer.id);
Expand All @@ -315,12 +328,16 @@ class MeasureControl extends EventEmitter {
// unregister all event handlers
this.removeEventListeners(this.map);
}
if (this.map && !this.map.loaded()) {
this.map.once("load", this.destroy);
}
document.body.removeEventListener("keydown", this.onKeyDown);
this.isDestroyed = true;
};

onKeyDown = (e: KeyboardEvent) => {
if (this.isDestroyed) {
this.destroy();
return;
}
if (e.key === "Escape" && this.state === "drawing") {
Expand Down Expand Up @@ -348,7 +365,8 @@ class MeasureControl extends EventEmitter {

onMouseOverPoint = async (e: any) => {
if (this.isDestroyed) {
throw new Error("MeasureControl is destroyed");
this.destroy();
return;
}
if (this.state === "disabled" || this.state === "dragging") {
return;
Expand Down Expand Up @@ -428,7 +446,8 @@ class MeasureControl extends EventEmitter {

onClick = (e: mapboxgl.MapMouseEvent) => {
if (this.isDestroyed) {
throw new Error("MeasureControl is destroyed");
this.destroy();
return;
}
if (this.state === "drawing") {
// Check if on last point
Expand Down Expand Up @@ -477,7 +496,8 @@ class MeasureControl extends EventEmitter {
private cursorOverRuler = false;
onMouseOutPoint = (e: any) => {
if (this.isDestroyed) {
throw new Error("MeasureControl is destroyed");
this.destroy();
return;
}

if (this.state === "drawing") {
Expand Down Expand Up @@ -526,7 +546,8 @@ class MeasureControl extends EventEmitter {

onMouseDownPoint = (e: any) => {
if (this.isDestroyed) {
throw new Error("MeasureControl is destroyed");
this.destroy();
return;
}
this.onMouseDownOnPoint = true;
if (this.state === "editing" && this.draggedPointIndex > -1) {
Expand All @@ -537,7 +558,8 @@ class MeasureControl extends EventEmitter {

onMouseUp = (e: any) => {
if (this.isDestroyed) {
throw new Error("MeasureControl is destroyed");
this.destroy();
return;
}
this.onMouseDownOnPoint = false;
if (this.state === "dragging") {
Expand Down Expand Up @@ -602,7 +624,8 @@ class MeasureControl extends EventEmitter {

onMouseMove = (e: any) => {
if (this.isDestroyed) {
throw new Error("MeasureControl is destroyed");
this.destroy();
return;
}
if (this.state === "drawing") {
// update cursor
Expand Down Expand Up @@ -767,11 +790,11 @@ export function MeasurementToolsOverlay({
placement,
}: {
placement:
| "top-right"
| "top-left"
| "bottom-right"
| "bottom-left"
| "top-right-homepage";
| "top-right"
| "top-left"
| "bottom-right"
| "bottom-left"
| "top-right-homepage";
}) {
const mapContext = useContext(MapContext);
const measureContext = useContext(MeasureControlContext);
Expand Down Expand Up @@ -845,15 +868,19 @@ export function MeasurementToolsOverlay({
transition={{
duration: 0.15,
}}
className={`${placement === "top-right"
? isSmall
? "right-20 top-5"
: "right-24 mr-1 top-5"
: placement === "top-right-homepage"
className={`${
placement === "top-right"
? isSmall
? "right-20 top-5"
: "right-24 mr-1 top-5"
: placement === "top-right-homepage"
? "right-14 top-2.5"
: placement === "bottom-right" ? "right-2 bottom-14" : "left-20 top-5"
} ${state === "paused" ? "pointer-events-none" : "pointer-events-auto"
} absolute z-10 bg-white shadow rounded border w-72`}
: placement === "bottom-right"
? "right-2 bottom-14"
: "left-20 top-5"
} ${
state === "paused" ? "pointer-events-none" : "pointer-events-auto"
} absolute z-10 bg-white shadow rounded border w-72`}
>
<div className="flex mt-2">
<div className="text-xl font-semibold flex-1 truncate text-center mx-5">
Expand All @@ -873,11 +900,11 @@ export function MeasurementToolsOverlay({
length > 0 &&
(mouseHoverNotAvailable
? t(
"Tap again to measure a path, or use the buttons below to finish or start over."
)
"Tap again to measure a path, or use the buttons below to finish or start over."
)
: t(
"Double-click to finish measuring or click to draw a path."
))}
"Double-click to finish measuring or click to draw a path."
))}
{(state === "editing" || state === "dragging") &&
(mouseHoverNotAvailable
? t("")
Expand Down
40 changes: 34 additions & 6 deletions packages/client/src/formElements/FormElementOptionsInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { FormLanguageContext, SurveyContext } from "./FormElement";
export type FormElementOption = {
value?: string;
label: string;
description?: string;
};

export default function FormElementOptionsInput({
Expand All @@ -17,13 +18,15 @@ export default function FormElementOptionsInput({
onChange,
heading,
description,
promptForDescription,
}: {
prop: string;
componentSettings?: { [key: string]: any };
alternateLanguageSettings?: { [lang: string]: { [key: string]: any } };
onChange: (options: FormElementOption[]) => void;
heading?: string;
description?: ReactNode | string;
promptForDescription?: boolean;
}) {
const context = useContext(FormLanguageContext);
const { t } = useTranslation("admin:surveys");
Expand Down Expand Up @@ -91,7 +94,7 @@ export default function FormElementOptionsInput({
<Trans ns="admin:surveys">
List options as{" "}
<code className="font-mono bg-gray-100 p-1 rounded">
label,value{" "}
label,value{promptForDescription ? ",description" : ""}{" "}
</code>
each on a new line. Use quotes to escape commas. Values are not
required but will keep data consistent if text changes are needed.
Expand Down Expand Up @@ -154,16 +157,32 @@ export default function FormElementOptionsInput({
function toText(options: FormElementOption[]): string {
return Papa.unparse(
options.map((option) =>
option.value ? [option.label, option.value] : [option.label]
)
);
option.value
? option.description
? [
option.label,
option.value,
`${option.description.replace(/^"/, "").replace(/"$/, "")}`,
]
: [option.label, option.value]
: [option.label]
),
{
// using the Mongolian vowel separator as a quote character, as it
// is unlikely to be used in user-generated content
quoteChar: "\u180E",
}
).replace(/\u180E/g, "");
}

function fromText(text: string) {
let options: FormElementOption[] = [];
let errors: string[] = [];
let delimiter: string | undefined = undefined;
const result = Papa.parse(text, { skipEmptyLines: true });
// eslint-disable-next-line i18next/no-literal-string
const result = Papa.parse(text, {
skipEmptyLines: true,
});
if (
result.errors?.length &&
result.errors.filter((e) => e.type !== "Delimiter").length
Expand All @@ -173,7 +192,16 @@ function fromText(text: string) {
options = result.data.map((r: any) =>
r.length === 1
? { label: r[0].trim() }
: { label: r[0].trim(), value: r[1].trim() }
: {
label: r[0].trim(),
value: r[1].trim(),
description: r
.slice(2)
?.join(",")
.trim()
.replace(/^"/, "")
.replace(/"$/, ""),
}
);
if (options.length === 0) {
errors = ["No options specified"];
Expand Down
24 changes: 20 additions & 4 deletions packages/client/src/formElements/Matrix.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import { TableIcon } from "@heroicons/react/outline";
import React, { useContext } from "react";
import { Trans, useTranslation } from "react-i18next";
import { Trans } from "react-i18next";
import { SurveyStyleContext } from "../surveys/appearance";
import {
FormElementBody,
FormElementComponent,
FormElementEditorPortal,
FormLanguageContext,
SurveyContext,
useLocalizedComponentSetting,
} from "./FormElement";
import FormElementOptionsInput, {
FormElementOption,
} from "./FormElementOptionsInput";
import { questionBodyFromMarkdown } from "./fromMarkdown";
import { InfoCircledIcon } from "@radix-ui/react-icons";
import useDialog from "../components/useDialog";

export type MatrixProps = {
options?: FormElementOption[];
Expand All @@ -36,6 +37,8 @@ const Matrix: FormElementComponent<MatrixProps, MatrixValue> = (props) => {
props
) as FormElementOption[];

const { alert } = useDialog();

function updateValue(row: FormElementOption, option: string) {
const newValue = { ...props.value, [row.value || row.label]: option };
if (option === "") {
Expand Down Expand Up @@ -89,6 +92,18 @@ const Matrix: FormElementComponent<MatrixProps, MatrixValue> = (props) => {
<React.Fragment key={row.label}>
<div className="px-2 items-center flex bg-black bg-opacity-10 ltr:rounded-l rtl:rounded-r">
{row.label}
{row.description && row.description.length > 0 && (
<button
className="ml-1.5"
onClick={() => {
alert(row.label, {
description: row.description,
});
}}
>
<InfoCircledIcon />
</button>
)}
</div>
<div
className={`flex w-32 sm:w-auto items-center bg-black bg-opacity-10 p-2 ltr:rounded-r rtl:rounded-l`}
Expand Down Expand Up @@ -149,8 +164,9 @@ const Matrix: FormElementComponent<MatrixProps, MatrixValue> = (props) => {
return (
<>
<FormElementOptionsInput
promptForDescription
heading="Rows"
key={props.id}
key={props.id + "rows"}
prop="rows"
componentSettings={props.componentSettings}
alternateLanguageSettings={props.alternateLanguageSettings}
Expand All @@ -162,7 +178,7 @@ const Matrix: FormElementComponent<MatrixProps, MatrixValue> = (props) => {
)}
/>
<FormElementOptionsInput
key={props.id}
key={props.id + "options"}
prop="options"
componentSettings={props.componentSettings}
alternateLanguageSettings={props.alternateLanguageSettings}
Expand Down
2 changes: 2 additions & 0 deletions packages/client/src/lang/en/admin.json
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@
"Publish Overlays": "Publish Overlays",
"Published layer lists include all authorization settings, data layer and source changes, and z-ordering specifications. Once published, project users will have access to the new list upon reloading the page. Edits to the overlay list from the administrator page will not be available to end-users until they are published.": "Published layer lists include all authorization settings, data layer and source changes, and z-ordering specifications. Once published, project users will have access to the new list upon reloading the page. Edits to the overlay list from the administrator page will not be available to end-users until they are published.",
"Radio children": "Radio children",
"Reading XML metadata": "Reading XML metadata",
"Recently Used Servers": "Recently Used Servers",
"Recipient": "Recipient",
"Recipient has created an account and shared a profile. They should appear in the participants list.": "Recipient has created an account and shared a profile. They should appear in the participants list.",
Expand Down Expand Up @@ -291,6 +292,7 @@
"Unshared sketches visible on the map": "Unshared sketches visible on the map",
"Upload": "Upload",
"Uploading": "Uploading",
"Uploading XML metadata": "Uploading XML metadata",
"User groups can be used to grant permission to access certain layers, forums, or sketching tools.": "User groups can be used to grant permission to access certain layers, forums, or sketching tools.",
"User profiles are entered and maintained by the user. This information is shared along with any forum posts the user makes.": "User profiles are entered and maintained by the user. This information is shared along with any forum posts the user makes.",
"Users can turn off 3d terrain if desired": "Users can turn off 3d terrain if desired",
Expand Down
3 changes: 3 additions & 0 deletions packages/client/src/lang/en/admin:data.json
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,9 @@
"Loading usage info...": "Loading usage info...",
"Manual": "Manual",
"MapBox fees to generate package (w/o free tier)": "MapBox fees to generate package (w/o free tier)",
"Max": "Max",
"Metadata from this ArcGIS Service has been customized within SeaSketch and will no longer show changes published from the origin server.": "Metadata from this ArcGIS Service has been customized within SeaSketch and will no longer show changes published from the origin server.",
"Min": "Min",
"Min, Max Zoom": "Min, Max Zoom",
"Natural Breaks": "Natural Breaks",
"No Tile Packages found. Related maps cannot be used offline until created. <2>Generating tile packages may incure fees from MapBox</2>": "No Tile Packages found. Related maps cannot be used offline until created. <2>Generating tile packages may incure fees from MapBox</2>",
Expand Down Expand Up @@ -152,6 +154,7 @@
"Show all categories": "Show all categories",
"Show layer extent": "Show layer extent",
"Show less": "Show less",
"Show values outside range": "Show values outside range",
"Single band": "Single band",
"Solid": "Solid",
"Some Esri basemaps include blank tiles that read \"Map data not yet available\" at higher zoom levels. You may be able to set the max zoom level lower to avoid this issue. Otherwise, this should be set to auto to use the service metadata.": "Some Esri basemaps include blank tiles that read \"Map data not yet available\" at higher zoom levels. You may be able to set the max zoom level lower to avoid this issue. Otherwise, this should be set to auto to use the service metadata.",
Expand Down
Loading

0 comments on commit 3d32087

Please sign in to comment.