diff --git a/app/components/domain/CompareScenarioPreview/index.tsx b/app/components/domain/CompareScenarioPreview/index.tsx
index bfad1c7c..a7458924 100644
--- a/app/components/domain/CompareScenarioPreview/index.tsx
+++ b/app/components/domain/CompareScenarioPreview/index.tsx
@@ -67,6 +67,16 @@ function CompareScenarioPreview(props: Props) {
return createGeoJsonFromTiles(tiles);
}, [scenario]);
+ const tileServerPropertySafe = useMemo(
+ () => removeNull(tileServerProperty),
+ [tileServerProperty],
+ );
+
+ const tileServerBPropertySafe = useMemo(
+ () => removeNull(tileServerBProperty),
+ [tileServerBProperty],
+ );
+
return (
@@ -91,7 +101,7 @@ function CompareScenarioPreview(props: Props) {
tileSize={280}
className={styles.mapContainer}
geoJson={generatedGeojson}
- baseTileServer={removeNull(tileServerBProperty)}
+ baseTileServer={tileServerBPropertySafe}
geoJsonLayerOptions={layerOptions}
disablePan
/>
diff --git a/app/components/domain/FindScenarioPreview/index.tsx b/app/components/domain/FindScenarioPreview/index.tsx
index d7675cca..ea06c10a 100644
--- a/app/components/domain/FindScenarioPreview/index.tsx
+++ b/app/components/domain/FindScenarioPreview/index.tsx
@@ -66,6 +66,11 @@ function FindScenarioPreview(props: Props) {
return createGeoJsonFromTiles(tiles);
}, [scenario]);
+ const tileServerPropertySafe = useMemo(
+ () => removeNull(tileServerProperty),
+ [tileServerProperty],
+ );
+
return (
diff --git a/app/components/domain/LocateFeaturesScenarioPreview/index.tsx b/app/components/domain/LocateFeaturesScenarioPreview/index.tsx
new file mode 100644
index 00000000..372104cb
--- /dev/null
+++ b/app/components/domain/LocateFeaturesScenarioPreview/index.tsx
@@ -0,0 +1,150 @@
+import { useMemo } from 'react';
+import {
+ _cs,
+ isNotDefined,
+} from '@togglecorp/fujs';
+import {
+ PartialForm,
+ removeNull,
+} from '@togglecorp/toggle-form';
+import { FillLayerSpecification } from 'maplibre-gl';
+
+import GeoJsonPreview from '#components/domain/GeoJsonPreview';
+import Icon from '#components/domain/Icon';
+import { PreviewItem } from '#components/domain/TutorialPreviewScreenSelectInput';
+import ListLayout from '#components/ListLayout';
+import MobilePreview from '#components/MobilePreview';
+import {
+ ProjectRasterTileServerConfig,
+ SubGridSizeEnum,
+ TutorialScenarioPageCreateInput,
+} from '#generated/types/graphql';
+import { subgridSizeToValueMap } from '#utils/common';
+import { createSubGridGeoJsonFromTile } from '#utils/geo';
+
+import { PartialCustomOptionInputFields } from '../CustomOptionInput/schema';
+
+import styles from './styles.module.css';
+
+interface Props {
+ className?: string;
+ tileServerProperty: ProjectRasterTileServerConfig | undefined;
+ projectInstruction: string | undefined | null;
+ scenario: PartialForm
| undefined;
+ preview: PreviewItem | undefined;
+ subgridSize: SubGridSizeEnum | undefined;
+ customOptions: PartialCustomOptionInputFields[] | undefined;
+}
+
+function LocateFeaturesScenarioPreview(props: Props) {
+ const {
+ className,
+ scenario,
+ projectInstruction,
+ tileServerProperty,
+ preview,
+ subgridSize,
+ customOptions,
+ } = props;
+
+ // @ts-expect-error potentially wrong typing
+ const layerOptions: Omit | undefined = useMemo(() => {
+ if (isNotDefined(customOptions)) {
+ return undefined;
+ }
+
+ const colorAndValueMapping = customOptions.map((option) => ([
+ option.value!,
+ option.iconColor!,
+ ])).flat();
+
+ return {
+ type: 'fill',
+ paint: {
+ // @ts-expect-error potentially wrong typing
+ 'fill-color': [
+ 'match',
+ ['get', 'reference'],
+ ...colorAndValueMapping,
+ 'transparent',
+ ],
+ 'fill-outline-color': '#ffffff',
+ 'fill-opacity': 0.5,
+ },
+ } satisfies Omit;
+ }, [customOptions]);
+
+ const references = useMemo(() => (
+ scenario?.tasks?.map(({ reference }) => reference)
+ ), [scenario?.tasks]);
+
+ const firstTask = scenario?.tasks?.[0];
+ const [tileX, tileY, tileZ] = useMemo(() => {
+ if (isNotDefined(firstTask)) {
+ return [];
+ }
+
+ const projectTypeSpecifics = firstTask.projectTypeSpecifics?.locate;
+
+ if (isNotDefined(projectTypeSpecifics)) {
+ return [];
+ }
+
+ return [
+ projectTypeSpecifics.tileX,
+ projectTypeSpecifics.tileY,
+ projectTypeSpecifics.tileZ,
+ ];
+ }, [firstTask]);
+
+ const generatedGeojson = useMemo(() => {
+ if (isNotDefined(tileX)
+ || isNotDefined(tileY)
+ || isNotDefined(tileZ)
+ || isNotDefined(subgridSize)
+ ) {
+ return undefined;
+ }
+
+ return createSubGridGeoJsonFromTile(
+ tileX,
+ tileY,
+ tileZ,
+ subgridSizeToValueMap[subgridSize],
+ references,
+ );
+ }, [tileX, tileY, tileZ, references, subgridSize]);
+
+ const tileServerPropertySafe = useMemo(
+ () => removeNull(tileServerProperty),
+ [tileServerProperty],
+ );
+
+ return (
+
+ }
+ popupTitle={preview?.title || '{title}'}
+ popupDescription={preview?.description || '{description}'}
+ popupVariant={preview?.popupVariant}
+ contentClassName={styles.content}
+ >
+
+
+
+ );
+}
+
+export default LocateFeaturesScenarioPreview;
diff --git a/app/components/domain/LocateFeaturesScenarioPreview/styles.module.css b/app/components/domain/LocateFeaturesScenarioPreview/styles.module.css
new file mode 100644
index 00000000..2d1bd6c9
--- /dev/null
+++ b/app/components/domain/LocateFeaturesScenarioPreview/styles.module.css
@@ -0,0 +1,17 @@
+.locate-features-scenario-preview {
+ isolation: isolate;
+
+ .content {
+ display: flex;
+ align-items: center;
+ flex-direction: column;
+ justify-content: center;
+ padding: 0 var(--spacing-lg);
+
+ .map-container {
+ z-index: 0;
+ width: var(--size-tile-validate);
+ height: var(--size-tile-validate);
+ }
+ }
+}
diff --git a/app/components/domain/ProjectSpecificDetails/LocateFeatures/index.tsx b/app/components/domain/ProjectSpecificDetails/LocateFeatures/index.tsx
new file mode 100644
index 00000000..f3253978
--- /dev/null
+++ b/app/components/domain/ProjectSpecificDetails/LocateFeatures/index.tsx
@@ -0,0 +1,72 @@
+import { useState } from 'react';
+import { isNotDefined } from '@togglecorp/fujs';
+import { removeNull } from '@togglecorp/toggle-form';
+
+import DefaultMapContainer from '#components/DefaultMapContainer';
+import BaseMap from '#components/domain/BaseMap';
+import GeoJsonAssetMapSource from '#components/domain/GeoJsonAssetMapSource';
+import MapZoomViewSelectInput, { MapZoomViewType } from '#components/domain/MapZoomViewSelectInput';
+import RasterTileServerOutput from '#components/domain/RasterTileServerOutput';
+import InlineLayout from '#components/InlineLayout';
+import ListLayout from '#components/ListLayout';
+import TextOutput from '#components/TextOutput';
+import ZoomLevelOutput from '#components/ZoomLevelOutput';
+import { ProjectSpecificDetailsQuery } from '#generated/types/graphql';
+
+interface Props {
+ data: ProjectSpecificDetailsQuery['project']['projectTypeSpecifics'];
+ defaultBounds: GeoJSON.Polygon | undefined | null;
+}
+
+function LocateFeaturesDetails(props: Props) {
+ const {
+ data,
+ defaultBounds,
+ } = props;
+ const [zoomView, setZoomView] = useState('aoiBounds');
+
+ // eslint-disable-next-line no-underscore-dangle
+ if (isNotDefined(data) || data.__typename !== 'LocateProjectPropertyType') {
+ return null;
+ }
+
+ const tileZ = data.zoomLevel;
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
+
+export default LocateFeaturesDetails;
diff --git a/app/components/domain/ProjectSpecificDetails/index.tsx b/app/components/domain/ProjectSpecificDetails/index.tsx
index 61aa989d..c602015c 100644
--- a/app/components/domain/ProjectSpecificDetails/index.tsx
+++ b/app/components/domain/ProjectSpecificDetails/index.tsx
@@ -11,6 +11,7 @@ import ProjectTypeOutput from '../ProjectTypeOutput';
import CompareDetails from './CompareDetails';
import CompletenessDetails from './CompletenessDetails';
import FindDetails from './FindDetails';
+import LocateFeaturesDetails from './LocateFeatures';
import StreetDetails from './StreetDetails';
import ValidateDetails from './ValidateDetails';
import ValidateImageDetails from './ValidateImageDetails';
@@ -134,6 +135,13 @@ function ProjectSpecificDetails(props: Props) {
data={projectData.project.projectTypeSpecifics}
/>
)}
+ {/* eslint-disable-next-line no-underscore-dangle */}
+ {projectData?.project.projectTypeSpecifics?.__typename === 'LocateProjectPropertyType' && (
+
+ )}
);
}
diff --git a/app/components/domain/ProjectTypeIcon/index.tsx b/app/components/domain/ProjectTypeIcon/index.tsx
index ed59a9be..3c0f599b 100644
--- a/app/components/domain/ProjectTypeIcon/index.tsx
+++ b/app/components/domain/ProjectTypeIcon/index.tsx
@@ -1,3 +1,4 @@
+import { PiGridFour } from 'react-icons/pi';
import { _cs } from '@togglecorp/fujs';
import { ProjectTypeEnum } from '#generated/types/graphql';
@@ -119,6 +120,10 @@ function ProjectTypeIcon(props: Props) {
);
}
+ if (type === ProjectTypeEnum.Locate) {
+ return ;
+ }
+
const remainingTypes = type;
remainingTypes satisfies never;
diff --git a/app/contexts/EnumsContext.ts b/app/contexts/EnumsContext.ts
index b46213dd..8c4b80d8 100644
--- a/app/contexts/EnumsContext.ts
+++ b/app/contexts/EnumsContext.ts
@@ -12,6 +12,7 @@ export interface EnumsContextProps {
overlayLayerTypeOptions: AllEnumsQuery['enums']['OverlayLayerTypeEnum'],
tutorialStatusOptions: AllEnumsQuery['enums']['TutorialStatusEnum'],
firebasePushStatusOptions: AllEnumsQuery['enums']['FirebasePushStatusEnum'],
+ subGridSizeOptions: AllEnumsQuery['enums']['SubGridSizeEnum'],
validateObjectSourceTypeMapping: Record<
AllEnumsQuery['enums']['ValidateObjectSourceTypeEnum'][number]['key'],
@@ -61,6 +62,7 @@ export const defaultAllEnumsValue: EnumsContextProps = {
overlayLayerTypeOptions: [],
tutorialStatusOptions: [],
firebasePushStatusOptions: [],
+ subGridSizeOptions: [],
validateObjectSourceTypeMapping: undefined,
validateImageSourceTypeMapping: undefined,
diff --git a/app/utils/common.ts b/app/utils/common.ts
index c6aa4284..5308eae1 100644
--- a/app/utils/common.ts
+++ b/app/utils/common.ts
@@ -9,6 +9,7 @@ import {
import {
ProjectTypeEnum,
ProjectTypeSpecificInput,
+ SubGridSizeEnum,
} from '#generated/types/graphql';
export const DEFAULT_ALERT_DISMISS_DURATION = 4500;
@@ -156,6 +157,7 @@ export const projectTypeToKeyMap: Record = {
+ [SubGridSizeEnum.Size_2X2]: 1,
+ [SubGridSizeEnum.Size_4X4]: 2,
+ [SubGridSizeEnum.Size_8X8]: 3,
+};
diff --git a/app/utils/geo.ts b/app/utils/geo.ts
index 03113f4a..731e1e25 100644
--- a/app/utils/geo.ts
+++ b/app/utils/geo.ts
@@ -1,8 +1,10 @@
import {
bboxToTile,
+ getChildren,
tileToGeoJSON,
} from '@mapbox/tilebelt';
import {
+ compareNumber,
isDefined,
isNotDefined,
} from '@togglecorp/fujs';
@@ -44,6 +46,54 @@ export function standardizeQuadKey(url: string) {
return url.replace('{quad_key}', '{quadkey}');
}
+export function createSubGridGeoJsonFromTile(
+ x: number,
+ y: number,
+ z: number,
+ subgridSize: 1 | 2 | 3,
+ reference: (number | undefined)[] | undefined,
+) {
+ let childrenTiles = getChildren(
+ [x, y, z],
+ );
+
+ if (subgridSize > 1) {
+ new Array(subgridSize - 1).keys().forEach(() => {
+ childrenTiles = childrenTiles.flatMap((tile) => (
+ getChildren(tile)
+ ));
+ });
+ }
+
+ // 1, 2 -> 1.1 1.2 2.1 2.2
+ // 3, 4 1.3 1.4 2.3 2.4
+ //
+ // 3.1 3.2 4.1 4.2
+ // 3.3 3.4 4.3 4.4
+ // For this, list would look like [1.1, 1.2, 1.3, 1.4, 2.1, 2.2, 2,3, ...]
+ // Ordering it to [1.1, 1.2, 2.1, 2.2, 1.3, 1.4, 2.3, ...]
+ // Fixing the order of subgrids (since they're created recursively)
+ childrenTiles.sort(
+ (a, b) => compareNumber(a[1], b[1]) || compareNumber(a[0], b[0]),
+ );
+
+ const geojson: GeoJSON.GeoJSON = {
+ type: 'FeatureCollection' as const,
+ features: childrenTiles.map((tile, i) => ({
+ type: 'Feature' as const,
+ geometry: tileToGeoJSON(tile),
+ properties: {
+ tile_x: x,
+ tile_y: y,
+ tile_z: z,
+ reference: reference?.[i],
+ },
+ })),
+ };
+
+ return geojson;
+}
+
export function createGeoJsonFromTiles(
tiles: {
tileX: number | undefined,
diff --git a/app/utils/query.ts b/app/utils/query.ts
index 9a919eeb..53088253 100644
--- a/app/utils/query.ts
+++ b/app/utils/query.ts
@@ -24,6 +24,8 @@ fragment RasterTileServerPropertyFields on ProjectRasterTileServerConfig {
custom {
credits
url
+ minZoom
+ maxZoom
}
}
`;
@@ -56,7 +58,7 @@ fragment VectorTileServerPropertyFields on ProjectVectorTileServerConfig {
export const PROJECT_TYPE_SPECIFIC_FRAGMENT = gql`
${TILE_SERVER_PROPERTY_FRAGMENT}
${VECTOR_TILE_SERVER_PROPERTY_FRAGMENT}
-fragment ProjectTypeSpecificFields on CompareProjectPropertyTypeFindProjectPropertyTypeValidateProjectPropertyTypeValidateImageProjectPropertyTypeCompletenessProjectPropertyTypeStreetProjectPropertyType {
+fragment ProjectTypeSpecificFields on CompareProjectPropertyTypeFindProjectPropertyTypeValidateProjectPropertyTypeValidateImageProjectPropertyTypeCompletenessProjectPropertyTypeStreetProjectPropertyTypeLocateProjectPropertyType {
... on CompareProjectPropertyType {
__typename
aoiGeometry
@@ -146,6 +148,20 @@ fragment ProjectTypeSpecificFields on CompareProjectPropertyTypeFindProjectPrope
startTime
}
}
+ ... on LocateProjectPropertyType {
+ __typename
+ aoiGeometry
+ tileServerProperty {
+ ...RasterTileServerPropertyFields
+ }
+ zoomLevel
+ subGridSize
+ exportMetaKey
+ exportMetaValue
+ customOptions {
+ ...ProjectCustomOptionFields
+ }
+ }
}
`;
@@ -288,6 +304,7 @@ fragment TutorialDetailFields on TutorialType {
clientId
reference
scenarioId
+ taskPartitionIndex
projectTypeSpecifics {
... on FindTutorialTaskPropertyType {
__typename
@@ -333,6 +350,12 @@ fragment TutorialDetailFields on TutorialType {
mapillaryImageId
geometry
}
+ ... on LocateTutorialTaskPropertyType {
+ __typename
+ tileX
+ tileY
+ tileZ
+ }
}
}
}
diff --git a/app/views/EditProject/UpdateProjectForm/LocateFeaturesProjectSpecifics/index.tsx b/app/views/EditProject/UpdateProjectForm/LocateFeaturesProjectSpecifics/index.tsx
new file mode 100644
index 00000000..febe0e68
--- /dev/null
+++ b/app/views/EditProject/UpdateProjectForm/LocateFeaturesProjectSpecifics/index.tsx
@@ -0,0 +1,128 @@
+import { useContext } from 'react';
+import { isDefined } from '@togglecorp/fujs';
+import {
+ EntriesAsList,
+ getErrorObject,
+ LeafError,
+ ObjectError,
+ useFormObject,
+} from '@togglecorp/toggle-form';
+
+import Container from '#components/Container';
+import AssetInput from '#components/domain/AssetInput';
+import RasterTileServerInput from '#components/domain/RasterTileServerInput';
+import {
+ defaultRasterTileServerInputValue,
+ type PartialRasterTileServerInputFields,
+} from '#components/domain/RasterTileServerInput/schema';
+import ListLayout from '#components/ListLayout';
+import RadioInput from '#components/RadioInput';
+import TextInput from '#components/TextInput';
+import ZoomLevelSelectInput from '#components/ZoomLevelSelectInput';
+import EnumsContext from '#contexts/EnumsContext';
+import { ProjectAssetInputTypeEnum } from '#generated/types/graphql';
+import {
+ keySelector,
+ labelSelector,
+} from '#utils/common';
+
+import { type PartialLocateFeaturesSpecificFields } from './schema';
+
+interface Props {
+ projectId: string;
+ value: PartialLocateFeaturesSpecificFields | undefined | null;
+ error: LeafError | ObjectError;
+ setFieldValue: (...entries: EntriesAsList) => void;
+ disabled?: boolean;
+}
+
+function LocateFeaturesProjectSpecifics(props: Props) {
+ const {
+ projectId,
+ value,
+ error: formError,
+ setFieldValue,
+ disabled,
+ } = props;
+
+ const { subGridSizeOptions } = useContext(EnumsContext);
+
+ const error = getErrorObject(formError);
+
+ const setTileServerInputFieldValue = useFormObject<'tileServerProperty', PartialRasterTileServerInputFields>(
+ 'tileServerProperty' as const,
+ setFieldValue,
+ defaultRasterTileServerInputValue,
+ );
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
+
+export default LocateFeaturesProjectSpecifics;
diff --git a/app/views/EditProject/UpdateProjectForm/LocateFeaturesProjectSpecifics/schema.ts b/app/views/EditProject/UpdateProjectForm/LocateFeaturesProjectSpecifics/schema.ts
new file mode 100644
index 00000000..a2914343
--- /dev/null
+++ b/app/views/EditProject/UpdateProjectForm/LocateFeaturesProjectSpecifics/schema.ts
@@ -0,0 +1,57 @@
+import {
+ greaterThanOrEqualToCondition,
+ lessThanOrEqualToCondition,
+ ObjectSchema,
+ PartialForm,
+ requiredStringCondition,
+} from '@togglecorp/toggle-form';
+
+import customOptionSchema from '#components/domain/CustomOptionInput/schema';
+import rasterTileServerFormSchema, { defaultRasterTileServerInputValue } from '#components/domain/RasterTileServerInput/schema';
+import { LocateProjectPropertyInput } from '#generated/types/graphql';
+import { DeepNonNullable } from '#utils/types';
+
+import {
+ type PartialProjectUpdateInput,
+ type UpdateProjectContext,
+} from '../schema';
+
+export type PartialLocateFeaturesSpecificFields = PartialForm<
+ DeepNonNullable,
+ 'clientId'
+>;
+type LocateFeaturesSpecificFormSchema = ObjectSchema<
+ PartialLocateFeaturesSpecificFields,
+ PartialProjectUpdateInput,
+ UpdateProjectContext
+>;
+
+export const defaultLocateFeaturesSpecificFormValue: PartialLocateFeaturesSpecificFields = {
+ tileServerProperty: defaultRasterTileServerInputValue,
+ zoomLevel: 18,
+};
+
+const locateFeaturesSpecificFormSchema: LocateFeaturesSpecificFormSchema = {
+ fields: (): ReturnType => ({
+ zoomLevel: {
+ required: true,
+ validations: [greaterThanOrEqualToCondition(14), lessThanOrEqualToCondition(22)],
+ },
+ tileServerProperty: rasterTileServerFormSchema,
+ aoiGeometry: {
+ required: true,
+ requiredValidation: requiredStringCondition,
+ },
+ subGridSize: {
+ required: true,
+ },
+ exportMetaKey: {},
+ exportMetaValue: {},
+ customOptions: {
+ keySelector: (value) => value.clientId,
+ member: () => customOptionSchema,
+ },
+ }),
+};
+
+export default locateFeaturesSpecificFormSchema;
diff --git a/app/views/EditProject/UpdateProjectForm/index.tsx b/app/views/EditProject/UpdateProjectForm/index.tsx
index c3274323..776fd36f 100644
--- a/app/views/EditProject/UpdateProjectForm/index.tsx
+++ b/app/views/EditProject/UpdateProjectForm/index.tsx
@@ -59,6 +59,11 @@ import {
defaultFindSpecificFormValue,
PartialFindSpecificFields,
} from './FindProjectSpecifics/schema';
+import LocateFeaturesProjectSpecifics from './LocateFeaturesProjectSpecifics/index.tsx';
+import {
+ defaultLocateFeaturesSpecificFormValue,
+ PartialLocateFeaturesSpecificFields,
+} from './LocateFeaturesProjectSpecifics/schema.ts';
import StreetProjectSpecifics from './StreetProjectSpecifics/index.tsx';
import {
defaultStreetSpecificFormValue,
@@ -165,13 +170,23 @@ function UpdateProjectForm(props: Props) {
if (projectData.project.projectType === ProjectTypeEnum.Street) {
return {
...defaultStreetSpecificFormValue,
- // FIXME: use custom options from street project
- customOptions: projectData.defaultValidateCustomOptions.map((customOption) => ({
+ customOptions: projectData.defaultStreetCustomOptions.map((customOption) => ({
clientId: ulid(),
...customOption,
})),
};
}
+ if (projectData.project.projectType === ProjectTypeEnum.Locate) {
+ return {
+ ...defaultLocateFeaturesSpecificFormValue,
+ customOptions: projectData.defaultLocateFeaturesCustomOptions.map(
+ (customOption) => ({
+ clientId: ulid(),
+ ...customOption,
+ }),
+ ),
+ };
+ }
projectData.project.projectType satisfies never;
@@ -385,7 +400,13 @@ function UpdateProjectForm(props: Props) {
const setStreetProjectSpecificsFieldValue = useFormObject<'street', PartialStreetSpecificFields>(
'street',
setProjectSpecificFieldValue,
- defaultValidateSpecificFormValue,
+ defaultStreetSpecificFormValue,
+ );
+
+ const setLocateFeaturesProjectSpecificsFieldValue = useFormObject<'locate', PartialLocateFeaturesSpecificFields>(
+ 'locate',
+ setProjectSpecificFieldValue,
+ defaultLocateFeaturesSpecificFormValue,
);
const pending = updateProjectPending;
@@ -416,6 +437,8 @@ function UpdateProjectForm(props: Props) {
?.validateImage as PartialValidateSpecificFields | undefined;
const streetProjectTypeSpecifics = value.projectTypeSpecifics
?.street as PartialStreetSpecificFields | undefined;
+ const locateFeaturesProjectTypeSpecifics = value.projectTypeSpecifics
+ ?.locate as PartialLocateFeaturesSpecificFields | undefined;
return (
)}
+ {projectContext.projectType === ProjectTypeEnum.Locate && (
+
+ )}
);
diff --git a/app/views/EditProject/UpdateProjectForm/schema.ts b/app/views/EditProject/UpdateProjectForm/schema.ts
index 1004f09c..730559e4 100644
--- a/app/views/EditProject/UpdateProjectForm/schema.ts
+++ b/app/views/EditProject/UpdateProjectForm/schema.ts
@@ -1,3 +1,4 @@
+import { isNotDefined } from '@togglecorp/fujs';
import {
ObjectSchema,
PartialForm,
@@ -15,6 +16,7 @@ import { DeepNonNullable } from '#utils/types';
import compareSpecificFormSchema from './CompareProjectSpecifics/schema';
import completenessSpecificFormSchema from './CompletenessProjectSpecifics/schema';
import findSpecificFormSchema from './FindProjectSpecifics/schema';
+import locateFeaturesSpecificFormSchema from './LocateFeaturesProjectSpecifics/schema.ts';
import streetSpecificFormSchema from './StreetProjectSpecifics/schema.ts';
import validateImageSpecificFormSchema from './ValidateImageProjectSpecifics/schema.ts';
import validateSpecificFormSchema from './ValidateProjectSpecifics/schema';
@@ -76,82 +78,77 @@ const projectUpdateFormSchema: ProjectUpdateFormSchema = {
image: {},
projectTypeSpecifics: {
fields: (): ProjectTypeSpecificFormFields => {
- if (context?.projectType === ProjectTypeEnum.Find) {
+ const defaultValue = {
+ street: { forceValue: undefinedValue },
+ find: { forceValue: undefinedValue },
+ compare: { forceValue: undefinedValue },
+ completeness: { forceValue: undefinedValue },
+ validate: { forceValue: undefinedValue },
+ validateImage: { forceValue: undefinedValue },
+ } satisfies ProjectTypeSpecificFormFields;
+
+ if (isNotDefined(context)) {
+ return defaultValue;
+ }
+
+ const { projectType } = context;
+
+ if (isNotDefined(projectType)) {
+ return defaultValue;
+ }
+
+ if (projectType === ProjectTypeEnum.Find) {
return {
- street: { forceValue: undefinedValue },
+ ...defaultValue,
find: findSpecificFormSchema,
- compare: { forceValue: undefinedValue },
- completeness: { forceValue: undefinedValue },
- validate: { forceValue: undefinedValue },
- validateImage: { forceValue: undefinedValue },
};
}
- if (context?.projectType === ProjectTypeEnum.Compare) {
+ if (projectType === ProjectTypeEnum.Compare) {
return {
- street: { forceValue: undefinedValue },
+ ...defaultValue,
compare: compareSpecificFormSchema,
- find: { forceValue: undefinedValue },
- completeness: { forceValue: undefinedValue },
- validate: { forceValue: undefinedValue },
- validateImage: { forceValue: undefinedValue },
};
}
- if (context?.projectType === ProjectTypeEnum.Completeness) {
+ if (projectType === ProjectTypeEnum.Completeness) {
return {
- street: { forceValue: undefinedValue },
+ ...defaultValue,
completeness: completenessSpecificFormSchema,
- find: { forceValue: undefinedValue },
- compare: { forceValue: undefinedValue },
- validate: { forceValue: undefinedValue },
- validateImage: { forceValue: undefinedValue },
};
}
- if (context?.projectType === ProjectTypeEnum.Validate) {
+ if (projectType === ProjectTypeEnum.Validate) {
return {
- street: { forceValue: undefinedValue },
+ ...defaultValue,
validate: validateSpecificFormSchema,
- completeness: { forceValue: undefinedValue },
- find: { forceValue: undefinedValue },
- compare: { forceValue: undefinedValue },
- validateImage: { forceValue: undefinedValue },
};
}
- if (context?.projectType === ProjectTypeEnum.ValidateImage) {
+ if (projectType === ProjectTypeEnum.ValidateImage) {
return {
- street: { forceValue: undefinedValue },
+ ...defaultValue,
validateImage: validateImageSpecificFormSchema,
- completeness: { forceValue: undefinedValue },
- find: { forceValue: undefinedValue },
- compare: { forceValue: undefinedValue },
- validate: { forceValue: undefinedValue },
};
}
- if (context?.projectType === ProjectTypeEnum.Street) {
+ if (projectType === ProjectTypeEnum.Street) {
return {
+ ...defaultValue,
street: streetSpecificFormSchema,
- validateImage: { forceValue: undefinedValue },
- completeness: { forceValue: undefinedValue },
- find: { forceValue: undefinedValue },
- compare: { forceValue: undefinedValue },
- validate: { forceValue: undefinedValue },
};
}
- // context?.projectType satisfies never;
+ if (projectType === ProjectTypeEnum.Locate) {
+ return {
+ ...defaultValue,
+ locate: locateFeaturesSpecificFormSchema,
+ };
+ }
- return {
- street: { forceValue: undefinedValue },
- find: { forceValue: undefinedValue },
- compare: { forceValue: undefinedValue },
- completeness: { forceValue: undefinedValue },
- validate: { forceValue: undefinedValue },
- validateImage: { forceValue: undefinedValue },
- };
+ projectType satisfies never;
+
+ return defaultValue;
},
},
}),
diff --git a/app/views/EditProject/query.ts b/app/views/EditProject/query.ts
index 4165fda6..a38f5002 100644
--- a/app/views/EditProject/query.ts
+++ b/app/views/EditProject/query.ts
@@ -23,5 +23,19 @@ query ProjectDetails($id: ID!) {
title
value
}
+ defaultStreetCustomOptions: defaultCustomOptions(projectType: STREET) {
+ description
+ icon
+ iconColor
+ title
+ value
+ }
+ defaultLocateFeaturesCustomOptions: defaultCustomOptions(projectType: LOCATE) {
+ description
+ icon
+ iconColor
+ title
+ value
+ }
}
`;
diff --git a/app/views/EditTutorial/ScenarioPageInput/TaskInput/ComparePropertyInput/index.tsx b/app/views/EditTutorial/ScenarioPageInput/TaskInput/ComparePropertyInput/index.tsx
index 1b6546ab..5f200562 100644
--- a/app/views/EditTutorial/ScenarioPageInput/TaskInput/ComparePropertyInput/index.tsx
+++ b/app/views/EditTutorial/ScenarioPageInput/TaskInput/ComparePropertyInput/index.tsx
@@ -35,6 +35,7 @@ function ComparePropertyInput(props: Props) {
layout="grid"
numPreferredGridColumns={3}
minGridColumnSize="6rem"
+ spacing="sm"
>
) => void;
+ error: LeafError | ObjectError;
+ disabled?: boolean;
+}
+
+function LocateFeaturesPropertyInput(props: Props) {
+ const {
+ className,
+ value,
+ setFieldValue,
+ error: formError,
+ disabled,
+ } = props;
+
+ const error = getErrorObject(formError);
+
+ return (
+
+
+
+
+
+ );
+}
+
+export default LocateFeaturesPropertyInput;
diff --git a/app/views/EditTutorial/ScenarioPageInput/TaskInput/LocateFeaturesPropertyInput/schema.ts b/app/views/EditTutorial/ScenarioPageInput/TaskInput/LocateFeaturesPropertyInput/schema.ts
new file mode 100644
index 00000000..a58e7e21
--- /dev/null
+++ b/app/views/EditTutorial/ScenarioPageInput/TaskInput/LocateFeaturesPropertyInput/schema.ts
@@ -0,0 +1,24 @@
+import {
+ ObjectSchema,
+ PartialForm,
+} from '@togglecorp/toggle-form';
+
+import { LocateTutorialTaskPropertyInput } from '#generated/types/graphql';
+import { DeepNonNullable } from '#utils/types';
+
+export type LocateFeaturesPropertyInputFields = DeepNonNullable;
+export type PartialLocateFeaturesPropertyInputFields = PartialForm<
+ LocateFeaturesPropertyInputFields
+>;
+
+type TaskSchema = ObjectSchema;
+
+const locateFeaturesPropertyInputSchema: TaskSchema = {
+ fields: (): ReturnType => ({
+ tileX: {},
+ tileY: {},
+ tileZ: {},
+ }),
+};
+
+export default locateFeaturesPropertyInputSchema;
diff --git a/app/views/EditTutorial/ScenarioPageInput/TaskInput/ValidateImagePropertyInput/index.tsx b/app/views/EditTutorial/ScenarioPageInput/TaskInput/ValidateImagePropertyInput/index.tsx
index e1fe054c..78f421ee 100644
--- a/app/views/EditTutorial/ScenarioPageInput/TaskInput/ValidateImagePropertyInput/index.tsx
+++ b/app/views/EditTutorial/ScenarioPageInput/TaskInput/ValidateImagePropertyInput/index.tsx
@@ -38,6 +38,7 @@ function ValidateImagePropertyInput(props: Props) {
layout="grid"
numPreferredGridColumns={2}
minGridColumnSize="6rem"
+ spacing="sm"
>
-
+
(
+ 'locate' as const,
+ setProjectSpecificFieldValue,
+ {},
+ );
+
+ const referenceInput = useMemo(
+ () => {
+ const specifics = projectData?.projectTypeSpecifics;
+
+ if (isNotDefined(specifics)) {
+ return null;
+ }
+
+ // eslint-disable-next-line no-underscore-dangle
+ if (specifics.__typename === 'ValidateProjectPropertyType'
+ // eslint-disable-next-line no-underscore-dangle
+ || specifics.__typename === 'ValidateImageProjectPropertyType'
+ // eslint-disable-next-line no-underscore-dangle
+ || specifics.__typename === 'StreetProjectPropertyType'
+ // eslint-disable-next-line no-underscore-dangle
+ || specifics.__typename === 'LocateProjectPropertyType'
+ ) {
+ return (
+
+ );
+ }
+
+ // eslint-disable-next-line no-underscore-dangle
+ if (specifics.__typename === 'FindProjectPropertyType'
+ // eslint-disable-next-line no-underscore-dangle
+ || specifics.__typename === 'CompareProjectPropertyType'
+ // eslint-disable-next-line no-underscore-dangle
+ || specifics.__typename === 'CompletenessProjectPropertyType'
+ ) {
+ return (
+
+ );
+ }
+
+ specifics satisfies never;
+
+ return null;
+ },
+ [
+ disabled,
+ error?.reference,
+ projectData?.projectTypeSpecifics,
+ setFieldValue,
+ value.reference,
+ ],
+ );
+
return (
-
- {`#${index + 1}`}
+
+ {`${index + 1}.`}
- {/* eslint-disable-next-line no-underscore-dangle */}
- {(projectData?.projectTypeSpecifics?.__typename === 'ValidateProjectPropertyType'
- // eslint-disable-next-line no-underscore-dangle
- || projectData?.projectTypeSpecifics?.__typename === 'ValidateImageProjectPropertyType')
- ? (
-
- ) : (
-
- )}
+ {referenceInput}
{projectData?.projectType === ProjectTypeEnum.Find && (
)}
+ {projectData?.projectType === ProjectTypeEnum.Locate && (
+
+ )}
);
diff --git a/app/views/EditTutorial/ScenarioPageInput/TaskInput/schema.ts b/app/views/EditTutorial/ScenarioPageInput/TaskInput/schema.ts
index da7dc5cb..45380459 100644
--- a/app/views/EditTutorial/ScenarioPageInput/TaskInput/schema.ts
+++ b/app/views/EditTutorial/ScenarioPageInput/TaskInput/schema.ts
@@ -19,6 +19,7 @@ import {
import comparePropertyInputSchema from './ComparePropertyInput/schema';
import completenessPropertyInputSchema from './CompletenessPropertyInput/schema';
import findPropertyInputSchema from './FindPropertyInput/schema';
+import locateFeaturesPropertyInputSchema from './LocateFeaturesPropertyInput/schema';
import streetPropertyInputSchema from './StreetPropertyInput/schema';
import validateImagePropertyInputSchema from './ValidateImagePropertyInput/schema';
import validatePropertyInputSchema from './ValidatePropertyInput/schema';
@@ -39,101 +40,83 @@ type ProjectTypeSpecificsSchema = ObjectSchema<
TutorialFormContext
>;
+type ProjectTypeSpecificFields = ReturnType
;
+
const taskSchema: TaskSchema = {
fields: (_, __, context): ReturnType => ({
clientId: {},
reference: {},
+ taskPartitionIndex: {},
projectTypeSpecifics: {
- fields: (): ReturnType => {
+ fields: (): ProjectTypeSpecificFields => {
const projectType = context?.projectType;
+ const defaultSchema = {
+ find: { forceValue: undefinedValue },
+ compare: { forceValue: undefinedValue },
+ completeness: { forceValue: undefinedValue },
+ validate: { forceValue: undefinedValue },
+ validateImage: { forceValue: undefinedValue },
+ street: { forceValue: undefinedValue },
+ locate: { forceValue: undefinedValue },
+ } satisfies ProjectTypeSpecificFields;
+
if (isNotDefined(projectType)) {
- return {
- find: { forceValue: undefinedValue },
- compare: { forceValue: undefinedValue },
- completeness: { forceValue: undefinedValue },
- validate: { forceValue: undefinedValue },
- validateImage: { forceValue: undefinedValue },
- street: { forceValue: undefinedValue },
- };
+ return defaultSchema;
}
if (projectType === ProjectTypeEnum.Find) {
return {
+ ...defaultSchema,
find: findPropertyInputSchema,
- completeness: { forceValue: undefinedValue },
- compare: { forceValue: undefinedValue },
- validate: { forceValue: undefinedValue },
- validateImage: { forceValue: undefinedValue },
- street: { forceValue: undefinedValue },
};
}
if (projectType === ProjectTypeEnum.Compare) {
return {
+ ...defaultSchema,
compare: comparePropertyInputSchema,
- find: { forceValue: undefinedValue },
- completeness: { forceValue: undefinedValue },
- validate: { forceValue: undefinedValue },
- validateImage: { forceValue: undefinedValue },
- street: { forceValue: undefinedValue },
};
}
if (projectType === ProjectTypeEnum.Completeness) {
return {
+ ...defaultSchema,
completeness: completenessPropertyInputSchema,
- find: { forceValue: undefinedValue },
- compare: { forceValue: undefinedValue },
- validate: { forceValue: undefinedValue },
- validateImage: { forceValue: undefinedValue },
- street: { forceValue: undefinedValue },
};
}
if (projectType === ProjectTypeEnum.Validate) {
return {
+ ...defaultSchema,
validate: validatePropertyInputSchema,
- find: { forceValue: undefinedValue },
- compare: { forceValue: undefinedValue },
- completeness: { forceValue: undefinedValue },
- validateImage: { forceValue: undefinedValue },
- street: { forceValue: undefinedValue },
};
}
if (projectType === ProjectTypeEnum.ValidateImage) {
return {
+ ...defaultSchema,
validateImage: validateImagePropertyInputSchema,
- find: { forceValue: undefinedValue },
- compare: { forceValue: undefinedValue },
- completeness: { forceValue: undefinedValue },
- validate: { forceValue: undefinedValue },
- street: { forceValue: undefinedValue },
};
}
if (projectType === ProjectTypeEnum.Street) {
return {
+ ...defaultSchema,
street: streetPropertyInputSchema,
- find: { forceValue: undefinedValue },
- compare: { forceValue: undefinedValue },
- completeness: { forceValue: undefinedValue },
- validate: { forceValue: undefinedValue },
- validateImage: { forceValue: undefinedValue },
+ };
+ }
+
+ if (projectType === ProjectTypeEnum.Locate) {
+ return {
+ ...defaultSchema,
+ locate: locateFeaturesPropertyInputSchema,
};
}
projectType satisfies never;
- return {
- find: { forceValue: undefinedValue },
- compare: { forceValue: undefinedValue },
- completeness: { forceValue: undefinedValue },
- validate: { forceValue: undefinedValue },
- validateImage: { forceValue: undefinedValue },
- street: { forceValue: undefinedValue },
- };
+ return defaultSchema;
},
},
}),
diff --git a/app/views/EditTutorial/ScenarioPageInput/TaskInput/styles.module.css b/app/views/EditTutorial/ScenarioPageInput/TaskInput/styles.module.css
index e789cc89..71eda03d 100644
--- a/app/views/EditTutorial/ScenarioPageInput/TaskInput/styles.module.css
+++ b/app/views/EditTutorial/ScenarioPageInput/TaskInput/styles.module.css
@@ -3,9 +3,14 @@
.content {
display: grid;
- grid-template-columns: max-content 9rem auto;
- gap: var(--spacing-md);
+ grid-template-columns: max-content 12rem auto;
+ gap: var(--spacing-sm);
align-items: baseline;
+
+ .sn {
+ width: 1rem;
+ text-align: end;
+ }
}
@container (max-width: 30rem) {
diff --git a/app/views/EditTutorial/ScenarioPageInput/index.tsx b/app/views/EditTutorial/ScenarioPageInput/index.tsx
index 052fd1d1..fe1d626a 100644
--- a/app/views/EditTutorial/ScenarioPageInput/index.tsx
+++ b/app/views/EditTutorial/ScenarioPageInput/index.tsx
@@ -26,6 +26,7 @@ import CompareScenarioPreview from '#components/domain/CompareScenarioPreview';
import CompletenessScenarioPreview from '#components/domain/CompletenessScenarioPreview';
import FindScenarioPreview from '#components/domain/FindScenarioPreview';
import IconSelectInput from '#components/domain/IconSelectInput';
+import LocateFeaturesScenarioPreview from '#components/domain/LocateFeaturesScenarioPreview';
import StreetScenarioPreview from '#components/domain/StreetScenarioPreview';
import TutorialPreviewScreenSelectInput, {
PreviewItem,
@@ -146,7 +147,10 @@ function ScenarioPageInput(props: Props) {
fullWidth
/>
)}
-
+
- {value.tasks?.map((task, taskIndex) => (
-
- ))}
+
+ {value.tasks?.map((task, taskIndex) => (
+
+ ))}
+
)}
@@ -347,6 +356,17 @@ function ScenarioPageInput(props: Props) {
preview={preview}
/>
)}
+ {/* eslint-disable-next-line no-underscore-dangle */}
+ {projectData?.projectTypeSpecifics?.__typename === 'LocateProjectPropertyType' && (
+
+ )}
feature.properties.screen,
+ );
+
+ // eslint-disable-next-line no-underscore-dangle
+ const subgridSize = projectDetailResponse.project.projectTypeSpecifics?.__typename === 'LocateProjectPropertyType'
+ ? projectDetailResponse.project.projectTypeSpecifics.subGridSize
+ : undefined;
+
+ const subGridValue = isDefined(subgridSize)
+ ? subgridSizeToValueMap[subgridSize]
+ : 0;
+
+ const numSubGrids = (2 ** subGridValue) ** 2;
+
+ const scenarioPages: PartialScenarioPageInputFields[] = unique(
+ result.features,
+ (feature) => feature.properties.screen,
+ ).toSorted(
+ (a, b) => compareNumber(a.properties.screen, b.properties.screen),
+ ).map(({ properties }) => ({
+ clientId: ulid(),
+ scenarioPageNumber: properties.screen,
+ tasks: featuresByScreen[properties.screen].flatMap((feature) => (
+ Array.from(new Array(numSubGrids).keys()).map((index) => ({
+ clientId: ulid(),
+ reference: feature.properties.references[index],
+ taskPartitionIndex: index,
+ projectTypeSpecifics: {
+ locate: {
+ tileX: feature.properties.tile_x,
+ tileY: feature.properties.tile_y,
+ tileZ: feature.properties.tile_z,
+ } satisfies LocateFeaturesPropertyInputFields,
+ },
+ }))
+ )),
+ }));
+
+ setFieldValue(scenarioPages, 'scenarios');
+ }
}
}, [projectDetailResponse, setError, setFieldValue]);
diff --git a/app/views/NewProject/ProjectGeneralInputs/index.tsx b/app/views/NewProject/ProjectGeneralInputs/index.tsx
index 8111eea4..89dfbbf3 100644
--- a/app/views/NewProject/ProjectGeneralInputs/index.tsx
+++ b/app/views/NewProject/ProjectGeneralInputs/index.tsx
@@ -60,6 +60,7 @@ const hintText: Record<
[ProjectTypeEnum.ValidateImage]: 'Enter the description for your project. (markdown syntax is supported)',
[ProjectTypeEnum.Completeness]: 'Enter the description for your project. (markdown syntax is supported)',
[ProjectTypeEnum.Street]: 'Enter the description for your project. (markdown syntax is supported)',
+ [ProjectTypeEnum.Locate]: 'Enter the description for your project. (markdown syntax is supported)',
},
topic: {
[ProjectTypeEnum.Find]: 'Enter the title of your project.',
@@ -68,6 +69,7 @@ const hintText: Record<
[ProjectTypeEnum.ValidateImage]: 'Enter the title of your project.',
[ProjectTypeEnum.Completeness]: 'Enter the title of your project.',
[ProjectTypeEnum.Street]: 'Enter the title of your project.',
+ [ProjectTypeEnum.Locate]: 'Enter the title of your project.',
},
projectInstruction: {
[ProjectTypeEnum.Find]: 'What should the users look for (e.g. You are looking for: buildings, destroyed buildings, cars, trees, etc.)',
@@ -76,6 +78,7 @@ const hintText: Record<
[ProjectTypeEnum.ValidateImage]: 'What should the users look to assess? (e.g., You are looking for trees)',
[ProjectTypeEnum.Completeness]: undefined,
[ProjectTypeEnum.Street]: undefined,
+ [ProjectTypeEnum.Locate]: undefined,
},
lookFor: {
[ProjectTypeEnum.Find]: '[This field is used only for legacy app!] What should the users look for? (e.g., buildings, cars, trees)',
@@ -84,6 +87,7 @@ const hintText: Record<
[ProjectTypeEnum.ValidateImage]: '[This field is used only for legacy app!] What should the users look for? (e.g., buildings, cars, trees)',
[ProjectTypeEnum.Completeness]: '[This field is used only for legacy app!] What should the users look for? (e.g., buildings, cars, trees)',
[ProjectTypeEnum.Street]: '[This field is used only for legacy app!] What should the users look for? (e.g., buildings, cars, trees)',
+ [ProjectTypeEnum.Locate]: '[This field is used only for legacy app!] What should the users look for? (e.g., buildings, cars, trees)',
},
additionalInfoUrl: {
[ProjectTypeEnum.Find]: 'Provide an optional link to a resource with additional information on the project (only visible in the MapSwipe web app)',
@@ -92,14 +96,16 @@ const hintText: Record<
[ProjectTypeEnum.ValidateImage]: 'Provide an optional link to a resource with additional information on the project (only visible in the MapSwipe web app)',
[ProjectTypeEnum.Completeness]: 'Provide an optional link to a resource with additional information on the project (only visible in the MapSwipe web app)',
[ProjectTypeEnum.Street]: 'Provide an optional link to a resource with additional information on the project (only visible in the MapSwipe web app)',
+ [ProjectTypeEnum.Locate]: 'Provide an optional link to a resource with additional information on the project (only visible in the MapSwipe web app)',
},
projectNumber: {
- [ProjectTypeEnum.Find]: 'Is this project part of a bigger campaign with multiple projects? If so, increment this number up by one each time you create a new project in the series. ',
- [ProjectTypeEnum.Compare]: 'Is this project part of a bigger campaign with multiple projects? If so, increment this number up by one each time you create a new project in the series. ',
- [ProjectTypeEnum.Validate]: 'Is this project part of a bigger campaign with multiple projects? If so, increment this number up by one each time you create a new project in the series. ',
- [ProjectTypeEnum.ValidateImage]: 'Is this project part of a bigger campaign with multiple projects? If so, increment this number up by one each time you create a new project in the series. ',
- [ProjectTypeEnum.Completeness]: 'Is this project part of a bigger campaign with multiple projects? If so, increment this number up by one each time you create a new project in the series. ',
- [ProjectTypeEnum.Street]: 'Is this project part of a bigger campaign with multiple projects? If so, increment this number up by one each time you create a new project in the series. ',
+ [ProjectTypeEnum.Find]: 'Is this project part of a bigger campaign with multiple projects? If so, increment this number up by one each time you create a new project in the series.',
+ [ProjectTypeEnum.Compare]: 'Is this project part of a bigger campaign with multiple projects? If so, increment this number up by one each time you create a new project in the series.',
+ [ProjectTypeEnum.Validate]: 'Is this project part of a bigger campaign with multiple projects? If so, increment this number up by one each time you create a new project in the series.',
+ [ProjectTypeEnum.ValidateImage]: 'Is this project part of a bigger campaign with multiple projects? If so, increment this number up by one each time you create a new project in the series.',
+ [ProjectTypeEnum.Completeness]: 'Is this project part of a bigger campaign with multiple projects? If so, increment this number up by one each time you create a new project in the series.',
+ [ProjectTypeEnum.Street]: 'Is this project part of a bigger campaign with multiple projects? If so, increment this number up by one each time you create a new project in the series.',
+ [ProjectTypeEnum.Locate]: 'Is this project part of a bigger campaign with multiple projects? If so, increment this number up by one each time you create a new project in the series.',
},
region: {
[ProjectTypeEnum.Find]: 'Enter the region/location of your project (eg: City, Country)',
@@ -108,6 +114,7 @@ const hintText: Record<
[ProjectTypeEnum.ValidateImage]: 'Enter the region/location of your project (eg: City, Country)',
[ProjectTypeEnum.Completeness]: 'Enter the region/location of your project (eg: City, Country)',
[ProjectTypeEnum.Street]: 'Enter the region/location of your project (eg: City, Country)',
+ [ProjectTypeEnum.Locate]: 'Enter the region/location of your project (eg: City, Country)',
},
requestingOrganization: {
[ProjectTypeEnum.Find]: 'Which group, institution or community is requesting this project?',
@@ -116,6 +123,7 @@ const hintText: Record<
[ProjectTypeEnum.ValidateImage]: 'Which group, institution or community is requesting this project?',
[ProjectTypeEnum.Completeness]: 'Which group, institution or community is requesting this project?',
[ProjectTypeEnum.Street]: 'Which group, institution or community is requesting this project?',
+ [ProjectTypeEnum.Locate]: 'Which group, institution or community is requesting this project?',
},
team: {
[ProjectTypeEnum.Find]: 'Please note that if \'private\', this project will only be visible to the assigned team members. Data results will still be public.',
@@ -124,6 +132,7 @@ const hintText: Record<
[ProjectTypeEnum.ValidateImage]: 'Please note that if \'private\', this project will only be visible to the assigned team members. Data results will still be public.',
[ProjectTypeEnum.Completeness]: 'Please note that if \'private\', this project will only be visible to the assigned team members. Data results will still be public.',
[ProjectTypeEnum.Street]: 'Please note that if \'private\', this project will only be visible to the assigned team members. Data results will still be public.',
+ [ProjectTypeEnum.Locate]: 'Please note that if \'private\', this project will only be visible to the assigned team members. Data results will still be public.',
},
};
diff --git a/app/views/NewProject/index.tsx b/app/views/NewProject/index.tsx
index d84b397d..391e2099 100644
--- a/app/views/NewProject/index.tsx
+++ b/app/views/NewProject/index.tsx
@@ -162,6 +162,7 @@ const projectTypeDescriptions: Record = {
/>
),
+ [ProjectTypeEnum.Locate]: 'Locate features',
};
function NewProject() {
@@ -173,7 +174,19 @@ function NewProject() {
createNewProject,
] = useNewProjectMutation();
- const { projectTypeOptions } = useContext(EnumsContext);
+ const { projectTypeOptions: fullProjectTypeOptions } = useContext(EnumsContext);
+
+ const projectTypeOptions = useMemo(() => {
+ const { APP_ENVIRONMENT } = import.meta.env;
+
+ if (APP_ENVIRONMENT !== 'PROD') {
+ return fullProjectTypeOptions;
+ }
+
+ return fullProjectTypeOptions?.filter(
+ (option) => option.key !== ProjectTypeEnum.Locate,
+ );
+ }, [fullProjectTypeOptions]);
const defaultBaseProjectFormValue = useMemo(() => ({
clientId: ulid(),
diff --git a/app/views/RootLayout/index.tsx b/app/views/RootLayout/index.tsx
index fcb41b93..5053b6be 100644
--- a/app/views/RootLayout/index.tsx
+++ b/app/views/RootLayout/index.tsx
@@ -107,6 +107,10 @@ query AllEnums {
key
label
}
+ SubGridSizeEnum {
+ key
+ label
+ }
}
}
`;
@@ -200,6 +204,7 @@ function RootLayout() {
overlayLayerTypeOptions: allEnumsResponse?.enums.OverlayLayerTypeEnum ?? [],
tutorialStatusOptions: allEnumsResponse?.enums.TutorialStatusEnum ?? [],
firebasePushStatusOptions: allEnumsResponse?.enums.FirebasePushStatusEnum ?? [],
+ subGridSizeOptions: allEnumsResponse?.enums.SubGridSizeEnum ?? [],
validateObjectSourceTypeMapping: listToMap(
allEnumsResponse?.enums.ValidateObjectSourceTypeEnum,
({ key }) => key,
diff --git a/backend b/backend
index c0282605..1c83e8f3 160000
--- a/backend
+++ b/backend
@@ -1 +1 @@
-Subproject commit c028260535afd6a8fe1518d501e7536d1e6ac99e
+Subproject commit 1c83e8f39713984cba3a9a19e9f7565d5626608b