diff --git a/app/components/domain/ConflationOptionSelectInput/index.tsx b/app/components/domain/ConflationOptionSelectInput/index.tsx
new file mode 100644
index 0000000..e3147ef
--- /dev/null
+++ b/app/components/domain/ConflationOptionSelectInput/index.tsx
@@ -0,0 +1,95 @@
+import {
+ isDefined,
+ listToMap,
+} from '@togglecorp/fujs';
+
+import InlineLayout from '#components/InlineLayout';
+import SelectInput from '#components/SelectInput';
+import { SearchSelectInputProps } from '#components/SelectInput/SearchSelectInput';
+import {
+ conflationTileOptions,
+ labelSelector,
+ OPACITY_TILE_SELECTED,
+ TileSelectOption,
+ valueSelector,
+} from '#utils/common';
+
+interface ColorPreviewProps {
+ value: string | undefined | null;
+}
+
+function ColorPreview(props: ColorPreviewProps) {
+ const {
+ value,
+ } = props;
+
+ return (
+
+ );
+}
+
+function iconOptionLabelSelector(option: TileSelectOption) {
+ return (
+ }
+ >
+ {option.label}
+
+ );
+}
+
+type Def = { containerClassName?: string };
+
+type Props = SearchSelectInputProps<
+number,
+NAME,
+TileSelectOption,
+Def,
+'onSearchValueChange'
+| 'searchOptions'
+| 'onShowDropdownChange'
+| 'totalOptionsCount'
+| 'value'
+| 'options'
+| 'keySelector'
+| 'labelSelector'
+| 'optionLabelSelector'
+| 'icons'
+> & { value: number | undefined | null};
+
+function TileOptionSelectInput(props: Props) {
+ const {
+ value,
+ ...otherProps
+ } = props;
+
+ const optionToColorMapping = listToMap(
+ conflationTileOptions,
+ (option) => option.value,
+ (option) => option.color,
+ );
+
+ return (
+
+ )}
+ />
+ );
+}
+
+export default TileOptionSelectInput;
diff --git a/app/utils/common.ts b/app/utils/common.ts
index c6aa428..cb8dd52 100644
--- a/app/utils/common.ts
+++ b/app/utils/common.ts
@@ -22,6 +22,11 @@ export const COLOR_TILE_OPTION_YES = 'green';
export const COLOR_TILE_OPTION_MAYBE = 'orange';
export const COLOR_TILE_OPTION_BAD_IMAGERY = 'red';
+export const COLOR_CONFLATION_OPTION_NO = 'red';
+export const COLOR_CONFLATION_OPTION_YES = 'green';
+export const COLOR_CONFLATION_OPTION_NOT_SURE = 'grey';
+export const COLOR_CONFLATION_OPTION_SKIP = 'orange';
+
export const VALUE_TILE_OPTION_NO = 0;
export const VALUE_TILE_OPTION_YES = 1;
export const VALUE_TILE_OPTION_MAYBE = 2;
@@ -56,6 +61,29 @@ export const defaultTileOptions: TileSelectOption[] = [
},
];
+export const conflationTileOptions: TileSelectOption[] = [
+ {
+ value: VALUE_TILE_OPTION_NO,
+ label: 'No | OSM',
+ color: COLOR_CONFLATION_OPTION_NO,
+ },
+ {
+ value: VALUE_TILE_OPTION_YES,
+ label: 'Yes | fAIr',
+ color: COLOR_CONFLATION_OPTION_YES,
+ },
+ {
+ value: VALUE_TILE_OPTION_MAYBE,
+ label: 'Not sure | Neither',
+ color: COLOR_CONFLATION_OPTION_NOT_SURE,
+ },
+ {
+ value: VALUE_TILE_OPTION_BAD_IMAGERY,
+ label: 'Skip',
+ color: COLOR_CONFLATION_OPTION_SKIP,
+ },
+];
+
export const defaultMarkdownPreviewOptions: MarkdownViewProps['options'] = {
simpleLineBreaks: true,
headerLevelStart: 3,
@@ -156,6 +184,7 @@ export const projectTypeToKeyMap: Record,
+ setFieldValue: (...entries: EntriesAsList) => void;
+}
+
+function ObjectSourceInput(props: Props) {
+ const {
+ value,
+ error: formError,
+ setFieldValue,
+ } = props;
+
+ const error = getErrorObject(formError);
+
+ return (
+
+
+
+ );
+}
+
+export default ObjectSourceInput;
diff --git a/app/views/EditProject/UpdateProjectForm/ConflationProjectSpecifics/ObjectSourceInput/schema.ts b/app/views/EditProject/UpdateProjectForm/ConflationProjectSpecifics/ObjectSourceInput/schema.ts
new file mode 100644
index 0000000..d0c8907
--- /dev/null
+++ b/app/views/EditProject/UpdateProjectForm/ConflationProjectSpecifics/ObjectSourceInput/schema.ts
@@ -0,0 +1,34 @@
+import {
+ ObjectSchema,
+ PartialForm,
+} from '@togglecorp/toggle-form';
+
+import {
+ ConflationObjectSourceConfig,
+ ConflationObjectSourceConfigInput,
+} from '#generated/types/graphql';
+import { DeepNonNullable } from '#utils/types';
+
+export type ConflationSourceConfigKeys = Exclude;
+
+export type PartialConflationObjectSourceInputFields = PartialForm<
+ DeepNonNullable
+>;
+type ConflationSourceFormSchema = ObjectSchema;
+type ConflationSourceInputFields = ReturnType;
+
+export const defaultObjectSourceInputFormValue: PartialConflationObjectSourceInputFields = {
+ objectGeojsonUrl: '',
+};
+
+const objectSourceFormSchema: ConflationSourceFormSchema = {
+ fields: (): ConflationSourceInputFields => {
+ const schema: ConflationSourceInputFields = {
+ objectGeojsonUrl: { required: true },
+ };
+
+ return schema;
+ },
+};
+
+export default objectSourceFormSchema;
diff --git a/app/views/EditProject/UpdateProjectForm/ConflationProjectSpecifics/index.tsx b/app/views/EditProject/UpdateProjectForm/ConflationProjectSpecifics/index.tsx
new file mode 100644
index 0000000..bf7d68f
--- /dev/null
+++ b/app/views/EditProject/UpdateProjectForm/ConflationProjectSpecifics/index.tsx
@@ -0,0 +1,69 @@
+import {
+ EntriesAsList,
+ getErrorObject,
+ LeafError,
+ ObjectError,
+ useFormObject,
+} from '@togglecorp/toggle-form';
+
+import RasterTileServerInput from '#components/domain/RasterTileServerInput';
+import {
+ defaultRasterTileServerInputValue,
+ type PartialRasterTileServerInputFields,
+} from '#components/domain/RasterTileServerInput/schema';
+
+import {
+ defaultObjectSourceInputFormValue,
+ PartialConflationObjectSourceInputFields,
+} from './ObjectSourceInput/schema';
+import ObjectSourceInput from './ObjectSourceInput';
+import { type PartialConflationSpecificFields } from './schema';
+
+interface Props {
+ value: PartialConflationSpecificFields | undefined | null;
+ error: LeafError | ObjectError;
+ setFieldValue: (...entries: EntriesAsList) => void;
+ disabled?: boolean;
+}
+
+function ConflationProjectSpecifics(props: Props) {
+ const {
+ value,
+ error: formError,
+ setFieldValue,
+ disabled,
+ } = props;
+
+ const error = getErrorObject(formError);
+
+ const setTileServerInputFieldValue = useFormObject<'tileServerProperty', PartialRasterTileServerInputFields>(
+ 'tileServerProperty' as const,
+ setFieldValue,
+ defaultRasterTileServerInputValue,
+ );
+
+ const setObjectSourceInputFieldValue = useFormObject<'objectSource', PartialConflationObjectSourceInputFields>(
+ 'objectSource' as const,
+ setFieldValue,
+ defaultObjectSourceInputFormValue,
+ );
+
+ return (
+ <>
+
+
+ >
+ );
+}
+
+export default ConflationProjectSpecifics;
diff --git a/app/views/EditProject/UpdateProjectForm/ConflationProjectSpecifics/schema.ts b/app/views/EditProject/UpdateProjectForm/ConflationProjectSpecifics/schema.ts
new file mode 100644
index 0000000..97defd4
--- /dev/null
+++ b/app/views/EditProject/UpdateProjectForm/ConflationProjectSpecifics/schema.ts
@@ -0,0 +1,38 @@
+import {
+ ObjectSchema,
+ PartialForm,
+} from '@togglecorp/toggle-form';
+
+import rasterTileServerFormSchema, { defaultRasterTileServerInputValue } from '#components/domain/RasterTileServerInput/schema';
+import { ConflationProjectPropertyInput } from '#generated/types/graphql';
+import { DeepNonNullable } from '#utils/types';
+
+import {
+ type PartialProjectUpdateInput,
+ type UpdateProjectContext,
+} from '../schema';
+import objectSourceFormSchema, { defaultObjectSourceInputFormValue } from './ObjectSourceInput/schema';
+
+export type PartialConflationSpecificFields = PartialForm<
+ DeepNonNullable,
+ 'clientId'
+>;
+type ConflationSpecificFormSchema = ObjectSchema<
+ PartialConflationSpecificFields,
+ PartialProjectUpdateInput,
+ UpdateProjectContext
+>;
+
+export const defaultConflationSpecificFormValue: PartialConflationSpecificFields = {
+ objectSource: defaultObjectSourceInputFormValue,
+ tileServerProperty: defaultRasterTileServerInputValue,
+};
+
+const conflationSpecificFormSchema: ConflationSpecificFormSchema = {
+ fields: (): ReturnType => ({
+ objectSource: objectSourceFormSchema,
+ tileServerProperty: rasterTileServerFormSchema,
+ }),
+};
+
+export default conflationSpecificFormSchema;
diff --git a/app/views/EditProject/UpdateProjectForm/index.tsx b/app/views/EditProject/UpdateProjectForm/index.tsx
index c327432..fbe7f4d 100644
--- a/app/views/EditProject/UpdateProjectForm/index.tsx
+++ b/app/views/EditProject/UpdateProjectForm/index.tsx
@@ -55,6 +55,11 @@ import {
defaultCompletenessSpecificFormValue,
PartialCompletenessSpecificFields,
} from './CompletenessProjectSpecifics/schema.ts';
+import ConflationProjectSpecifics from './ConflationProjectSpecifics/index.tsx';
+import {
+ defaultConflationSpecificFormValue,
+ PartialConflationSpecificFields,
+} from './ConflationProjectSpecifics/schema.ts';
import {
defaultFindSpecificFormValue,
PartialFindSpecificFields,
@@ -134,6 +139,10 @@ function UpdateProjectForm(props: Props) {
return defaultFindSpecificFormValue;
}
+ if (projectData.project.projectType === ProjectTypeEnum.Conflation) {
+ return defaultConflationSpecificFormValue;
+ }
+
if (projectData.project.projectType === ProjectTypeEnum.Compare) {
return defaultCompareSpecificFormValue;
}
@@ -373,7 +382,7 @@ function UpdateProjectForm(props: Props) {
const setValidateImageProjectSpecificsFieldValue = useFormObject<'validateImage', PartialValidateImageSpecificFields>(
'validateImage',
setProjectSpecificFieldValue,
- defaultValidateSpecificFormValue,
+ defaultValidateImageSpecificFormValue,
);
const setCompletenessProjectSpecificsFieldValue = useFormObject<'completeness', PartialCompletenessSpecificFields>(
@@ -385,7 +394,13 @@ function UpdateProjectForm(props: Props) {
const setStreetProjectSpecificsFieldValue = useFormObject<'street', PartialStreetSpecificFields>(
'street',
setProjectSpecificFieldValue,
- defaultValidateSpecificFormValue,
+ defaultStreetSpecificFormValue,
+ );
+
+ const setConflationSpecificsFieldValue = useFormObject<'conflation', PartialConflationSpecificFields>(
+ 'conflation',
+ setProjectSpecificFieldValue,
+ defaultConflationSpecificFormValue,
);
const pending = updateProjectPending;
@@ -416,6 +431,8 @@ function UpdateProjectForm(props: Props) {
?.validateImage as PartialValidateSpecificFields | undefined;
const streetProjectTypeSpecifics = value.projectTypeSpecifics
?.street as PartialStreetSpecificFields | undefined;
+ const conflationProjectTypeSpecifics = value.projectTypeSpecifics
+ ?.conflation as PartialConflationSpecificFields | undefined;
return (
)}
+ {projectContext.projectType === ProjectTypeEnum.Conflation && (
+
+ )}
);
diff --git a/app/views/EditProject/UpdateProjectForm/schema.ts b/app/views/EditProject/UpdateProjectForm/schema.ts
index 1004f09..d19facd 100644
--- a/app/views/EditProject/UpdateProjectForm/schema.ts
+++ b/app/views/EditProject/UpdateProjectForm/schema.ts
@@ -14,6 +14,7 @@ import { DeepNonNullable } from '#utils/types';
import compareSpecificFormSchema from './CompareProjectSpecifics/schema';
import completenessSpecificFormSchema from './CompletenessProjectSpecifics/schema';
+import conflationSpecificFormSchema from './ConflationProjectSpecifics/schema';
import findSpecificFormSchema from './FindProjectSpecifics/schema';
import streetSpecificFormSchema from './StreetProjectSpecifics/schema.ts';
import validateImageSpecificFormSchema from './ValidateImageProjectSpecifics/schema.ts';
@@ -84,6 +85,7 @@ const projectUpdateFormSchema: ProjectUpdateFormSchema = {
completeness: { forceValue: undefinedValue },
validate: { forceValue: undefinedValue },
validateImage: { forceValue: undefinedValue },
+ conflation: { forceValue: undefinedValue },
};
}
@@ -95,6 +97,7 @@ const projectUpdateFormSchema: ProjectUpdateFormSchema = {
completeness: { forceValue: undefinedValue },
validate: { forceValue: undefinedValue },
validateImage: { forceValue: undefinedValue },
+ conflation: { forceValue: undefinedValue },
};
}
@@ -106,6 +109,19 @@ const projectUpdateFormSchema: ProjectUpdateFormSchema = {
compare: { forceValue: undefinedValue },
validate: { forceValue: undefinedValue },
validateImage: { forceValue: undefinedValue },
+ conflation: { forceValue: undefinedValue },
+ };
+ }
+
+ if (context?.projectType === ProjectTypeEnum.Conflation) {
+ return {
+ street: { forceValue: undefinedValue },
+ validate: { forceValue: undefinedValue },
+ completeness: { forceValue: undefinedValue },
+ find: { forceValue: undefinedValue },
+ compare: { forceValue: undefinedValue },
+ validateImage: { forceValue: undefinedValue },
+ conflation: conflationSpecificFormSchema,
};
}
@@ -117,6 +133,7 @@ const projectUpdateFormSchema: ProjectUpdateFormSchema = {
find: { forceValue: undefinedValue },
compare: { forceValue: undefinedValue },
validateImage: { forceValue: undefinedValue },
+ conflation: { forceValue: undefinedValue },
};
}
@@ -128,6 +145,7 @@ const projectUpdateFormSchema: ProjectUpdateFormSchema = {
find: { forceValue: undefinedValue },
compare: { forceValue: undefinedValue },
validate: { forceValue: undefinedValue },
+ conflation: { forceValue: undefinedValue },
};
}
@@ -139,6 +157,7 @@ const projectUpdateFormSchema: ProjectUpdateFormSchema = {
find: { forceValue: undefinedValue },
compare: { forceValue: undefinedValue },
validate: { forceValue: undefinedValue },
+ conflation: { forceValue: undefinedValue },
};
}
@@ -151,6 +170,7 @@ const projectUpdateFormSchema: ProjectUpdateFormSchema = {
completeness: { forceValue: undefinedValue },
validate: { forceValue: undefinedValue },
validateImage: { forceValue: undefinedValue },
+ conflation: { forceValue: undefinedValue },
};
},
},
diff --git a/app/views/EditTutorial/ScenarioPageInput/TaskInput/ConflationPropertyInput/index.tsx b/app/views/EditTutorial/ScenarioPageInput/TaskInput/ConflationPropertyInput/index.tsx
new file mode 100644
index 0000000..652a0aa
--- /dev/null
+++ b/app/views/EditTutorial/ScenarioPageInput/TaskInput/ConflationPropertyInput/index.tsx
@@ -0,0 +1,59 @@
+import { _cs } from '@togglecorp/fujs';
+import {
+ EntriesAsList,
+ getErrorObject,
+ LeafError,
+ ObjectError,
+} from '@togglecorp/toggle-form';
+
+import NumberInput from '#components/NumberInput';
+import TextArea from '#components/TextArea';
+
+import { PartialConflationPropertyInputFields } from './schema.ts';
+
+import styles from './styles.module.css';
+
+interface Props {
+ className?: string;
+ value: PartialConflationPropertyInputFields | undefined;
+ setFieldValue: (...entries: EntriesAsList) => void;
+ error: LeafError | ObjectError;
+ disabled?: boolean;
+}
+
+function ConflationPropertyInput(props: Props) {
+ const {
+ className,
+ value,
+ setFieldValue,
+ error: formError,
+ disabled,
+ } = props;
+
+ const error = getErrorObject(formError);
+
+ return (
+
+
+
+
+ );
+}
+
+export default ConflationPropertyInput;
diff --git a/app/views/EditTutorial/ScenarioPageInput/TaskInput/ConflationPropertyInput/schema.ts b/app/views/EditTutorial/ScenarioPageInput/TaskInput/ConflationPropertyInput/schema.ts
new file mode 100644
index 0000000..81136fa
--- /dev/null
+++ b/app/views/EditTutorial/ScenarioPageInput/TaskInput/ConflationPropertyInput/schema.ts
@@ -0,0 +1,21 @@
+import {
+ ObjectSchema,
+ PartialForm,
+} from '@togglecorp/toggle-form';
+
+import { ConflationTutorialTaskPropertyInput } from '#generated/types/graphql';
+import { DeepNonNullable } from '#utils/types';
+
+export type ConflationPropertyInputFields = DeepNonNullable;
+export type PartialConflationPropertyInputFields = PartialForm;
+
+type TaskSchema = ObjectSchema;
+
+const conflationPropertyInputSchema: TaskSchema = {
+ fields: (): ReturnType => ({
+ identifier: {},
+ objectGeometry: {},
+ }),
+};
+
+export default conflationPropertyInputSchema;
diff --git a/app/views/EditTutorial/ScenarioPageInput/TaskInput/ConflationPropertyInput/styles.module.css b/app/views/EditTutorial/ScenarioPageInput/TaskInput/ConflationPropertyInput/styles.module.css
new file mode 100644
index 0000000..1f1e0d0
--- /dev/null
+++ b/app/views/EditTutorial/ScenarioPageInput/TaskInput/ConflationPropertyInput/styles.module.css
@@ -0,0 +1,8 @@
+.conflation-property-input {
+ .geometry {
+ textarea {
+ font-family: var(--font-family-monospace);
+ font-size: var(--font-size-2xs);
+ }
+ }
+}
diff --git a/app/views/EditTutorial/ScenarioPageInput/TaskInput/index.tsx b/app/views/EditTutorial/ScenarioPageInput/TaskInput/index.tsx
index 6b538e7..671d889 100644
--- a/app/views/EditTutorial/ScenarioPageInput/TaskInput/index.tsx
+++ b/app/views/EditTutorial/ScenarioPageInput/TaskInput/index.tsx
@@ -8,6 +8,7 @@ import {
} from '@togglecorp/toggle-form';
import { ulid } from 'ulid';
+import ConflationOptionSelectInput from '#components/domain/ConflationOptionSelectInput';
import CustomOptionSelectInput from '#components/domain/CustomOptionSelectInput';
import TileOptionSelectInput from '#components/domain/TileOptionSelectInput';
import {
@@ -17,12 +18,14 @@ import {
import { PartialComparePropertyInputFields } from './ComparePropertyInput/schema';
import { PartialCompletenessPropertyInputFields } from './CompletenessPropertyInput/schema';
+import { PartialConflationPropertyInputFields } from './ConflationPropertyInput/schema';
import { PartialFindPropertyInputFields } from './FindPropertyInput/schema';
import { PartialStreetPropertyInputFields } from './StreetPropertyInput/schema';
import { PartialValidateImagePropertyInputFields } from './ValidateImagePropertyInput/schema';
import { PartialValidatePropertyInputFields } from './ValidatePropertyInput/schema';
import ComparePropertyInput from './ComparePropertyInput';
import CompletenessPropertyInput from './CompletenessPropertyInput';
+import ConflationPropertyInput from './ConflationPropertyInput';
import FindPropertyInput from './FindPropertyInput';
import {
PartialProjectTypeSpecifics,
@@ -108,38 +111,40 @@ function TaskInput(props: Props) {
{},
);
+ const setConflationProjectSpecificsFieldValue = useFormObject<'conflation', PartialConflationPropertyInputFields>(
+ 'conflation' as const,
+ setProjectSpecificFieldValue,
+ {},
+ );
+
+ const componentMap: Record = {
+ ValidateProjectPropertyType: CustomOptionSelectInput,
+ ValidateImageProjectPropertyType: CustomOptionSelectInput,
+ ConflationProjectPropertyType: ConflationOptionSelectInput,
+ };
+
+ /* eslint-disable-next-line no-underscore-dangle */
+ const typeName = projectData?.projectTypeSpecifics?.__typename;
+ const OptionComponent = typeName && componentMap[typeName]
+ ? componentMap[typeName]
+ : TileOptionSelectInput;
+
return (
{`#${index + 1}`}
- {/* eslint-disable-next-line no-underscore-dangle */}
- {(projectData?.projectTypeSpecifics?.__typename === 'ValidateProjectPropertyType'
- // eslint-disable-next-line no-underscore-dangle
- || projectData?.projectTypeSpecifics?.__typename === 'ValidateImageProjectPropertyType')
- ? (
-
- ) : (
-
- )}
+
{projectData?.projectType === ProjectTypeEnum.Find && (
)}
+ {projectData?.projectType === ProjectTypeEnum.Conflation && (
+
+ )}
);
diff --git a/app/views/EditTutorial/ScenarioPageInput/TaskInput/schema.ts b/app/views/EditTutorial/ScenarioPageInput/TaskInput/schema.ts
index da7dc5c..209f211 100644
--- a/app/views/EditTutorial/ScenarioPageInput/TaskInput/schema.ts
+++ b/app/views/EditTutorial/ScenarioPageInput/TaskInput/schema.ts
@@ -18,6 +18,7 @@ import {
import comparePropertyInputSchema from './ComparePropertyInput/schema';
import completenessPropertyInputSchema from './CompletenessPropertyInput/schema';
+import conflationPropertyInputSchema from './ConflationPropertyInput/schema';
import findPropertyInputSchema from './FindPropertyInput/schema';
import streetPropertyInputSchema from './StreetPropertyInput/schema';
import validateImagePropertyInputSchema from './ValidateImagePropertyInput/schema';
@@ -66,6 +67,7 @@ const taskSchema: TaskSchema = {
validate: { forceValue: undefinedValue },
validateImage: { forceValue: undefinedValue },
street: { forceValue: undefinedValue },
+ conflation: { forceValue: undefinedValue },
};
}
@@ -77,6 +79,7 @@ const taskSchema: TaskSchema = {
validate: { forceValue: undefinedValue },
validateImage: { forceValue: undefinedValue },
street: { forceValue: undefinedValue },
+ conflation: { forceValue: undefinedValue },
};
}
@@ -88,6 +91,19 @@ const taskSchema: TaskSchema = {
validate: { forceValue: undefinedValue },
validateImage: { forceValue: undefinedValue },
street: { forceValue: undefinedValue },
+ conflation: { forceValue: undefinedValue },
+ };
+ }
+
+ if (projectType === ProjectTypeEnum.Conflation) {
+ return {
+ validate: { forceValue: undefinedValue },
+ find: { forceValue: undefinedValue },
+ compare: { forceValue: undefinedValue },
+ completeness: { forceValue: undefinedValue },
+ validateImage: { forceValue: undefinedValue },
+ street: { forceValue: undefinedValue },
+ conflation: conflationPropertyInputSchema,
};
}
@@ -99,6 +115,7 @@ const taskSchema: TaskSchema = {
completeness: { forceValue: undefinedValue },
validateImage: { forceValue: undefinedValue },
street: { forceValue: undefinedValue },
+ conflation: { forceValue: undefinedValue },
};
}
@@ -110,6 +127,7 @@ const taskSchema: TaskSchema = {
completeness: { forceValue: undefinedValue },
validate: { forceValue: undefinedValue },
street: { forceValue: undefinedValue },
+ conflation: { forceValue: undefinedValue },
};
}
@@ -121,6 +139,7 @@ const taskSchema: TaskSchema = {
completeness: { forceValue: undefinedValue },
validate: { forceValue: undefinedValue },
validateImage: { forceValue: undefinedValue },
+ conflation: { forceValue: undefinedValue },
};
}
@@ -133,6 +152,7 @@ const taskSchema: TaskSchema = {
validate: { forceValue: undefinedValue },
validateImage: { forceValue: undefinedValue },
street: { forceValue: undefinedValue },
+ conflation: { forceValue: undefinedValue },
};
},
},
diff --git a/app/views/EditTutorial/index.tsx b/app/views/EditTutorial/index.tsx
index 3c9d7a9..5b1414b 100644
--- a/app/views/EditTutorial/index.tsx
+++ b/app/views/EditTutorial/index.tsx
@@ -61,6 +61,7 @@ import { PartialInformationPageInputFields } from './InformationPageInput/schema
import { PartialScenarioPageInputFields } from './ScenarioPageInput/schema';
import { ComparePropertyInputFields } from './ScenarioPageInput/TaskInput/ComparePropertyInput/schema';
import { CompletenessPropertyInputFields } from './ScenarioPageInput/TaskInput/CompletenessPropertyInput/schema';
+import { ConflationPropertyInputFields } from './ScenarioPageInput/TaskInput/ConflationPropertyInput/schema';
import { FindPropertyInputFields } from './ScenarioPageInput/TaskInput/FindPropertyInput/schema';
import { StreetPropertyInputFields } from './ScenarioPageInput/TaskInput/StreetPropertyInput/schema';
import { ValidatePropertyInputFields } from './ScenarioPageInput/TaskInput/ValidatePropertyInput/schema';
@@ -146,6 +147,15 @@ const StreetFeaturePropertyType = type({
id: 'string',
});
+const ConflationFeaturePropertyType = type.merge(
+ CommonFeaturePropertyType,
+ {
+ // This is not used anymore
+ // id: '"string" | "number"',
+ id: type.number,
+ },
+);
+
const ValidateTutorialGeoJsonType = type({
type: '"FeatureCollection"',
features: type({
@@ -154,6 +164,14 @@ const ValidateTutorialGeoJsonType = type({
}).array(),
});
+const ConflationTutorialGeoJsonType = type({
+ type: '"FeatureCollection"',
+ features: type({
+ geometry: PolygonType.or(MultiPolygonType),
+ properties: ConflationFeaturePropertyType,
+ }).array(),
+});
+
const FindTutorialGeoJsonType = type({
type: '"FeatureCollection"',
features: type({
@@ -385,6 +403,16 @@ function NewTutorial() {
};
}
+ // eslint-disable-next-line no-underscore-dangle
+ if (task.projectTypeSpecifics?.__typename === 'ConflationTutorialTaskPropertyType') {
+ return {
+ ...task,
+ projectTypeSpecifics: {
+ conflation: task.projectTypeSpecifics,
+ },
+ };
+ }
+
task.projectTypeSpecifics satisfies undefined;
return { ...task };
@@ -665,6 +693,42 @@ function NewTutorial() {
],
}));
+ setFieldValue(
+ scenarioPages.toSorted((a, b) => (
+ compareNumber(a.scenarioPageNumber, b.scenarioPageNumber)
+ )),
+ 'scenarios',
+ );
+ }
+ } else if (projectType === ProjectTypeEnum.Conflation) {
+ const result = ConflationTutorialGeoJsonType(geoJson);
+ if (result instanceof type.errors) {
+ setError({
+ scenarios: {
+ [nonFieldError]: result.summary,
+ },
+ });
+ } else {
+ const scenarioPages = result.features.map((feature, i) => ({
+ clientId: ulid(),
+ scenarioPageNumber: isDefined(feature.properties.screen)
+ ? feature.properties.screen
+ : i + 1,
+ tasks: [
+ {
+ clientId: ulid(),
+ reference: feature.properties.reference,
+ projectTypeSpecifics: {
+ // FIXME: Why objectGeometry is string?
+ conflation: {
+ identifier: feature.properties.id,
+ objectGeometry: JSON.stringify(feature.geometry, null, 4),
+ } satisfies ConflationPropertyInputFields,
+ },
+ },
+ ],
+ }));
+
setFieldValue(
scenarioPages.toSorted((a, b) => (
compareNumber(a.scenarioPageNumber, b.scenarioPageNumber)
diff --git a/app/views/NewProject/ProjectGeneralInputs/index.tsx b/app/views/NewProject/ProjectGeneralInputs/index.tsx
index 8111eea..aa93c02 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.Conflation]: '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.Conflation]: '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.Conflation]: 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.Conflation]: undefined,
},
additionalInfoUrl: {
[ProjectTypeEnum.Find]: 'Provide an optional link to a resource with additional information on the project (only visible in the MapSwipe web app)',
@@ -92,6 +96,7 @@ 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.Conflation]: '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. ',
@@ -100,6 +105,7 @@ const hintText: Record<
[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.Conflation]: '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.Conflation]: '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.Conflation]: '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.Conflation]: 'Please note that if \'private\', this project will only be visible to the assigned team members. Data results will still be public.',
},
};
@@ -265,24 +274,32 @@ function ProjectGeneralInputs(props: Props) {
layout="grid"
spacing="lg"
>
-
-
+ {/*
+ Remove instruction and lookFor inputs if project type is conflation,
+ as values are hard-coded on form submission
+ */}
+ {projectType !== ProjectTypeEnum.Conflation && (
+ <>
+
+
+ >
+ )}
form.projectType !== ProjectTypeEnum.Conflation,
+ requiredValidation: (value, form) => (form.projectType !== ProjectTypeEnum.Conflation
+ ? requiredStringCondition(value)
+ : undefined),
},
lookFor: {
- required: true,
- requiredValidation: requiredStringCondition,
+ required: (form) => form.projectType !== ProjectTypeEnum.Conflation,
+ requiredValidation: (value, form) => (form.projectType !== ProjectTypeEnum.Conflation
+ ? requiredStringCondition(value)
+ : undefined),
},
description: {},
team: {},
@@ -156,7 +160,23 @@ const projectTypeDescriptions: Record = {
name="street-alert"
title="MapSwipe Web only"
type="warning"
- description="Street project are currently only available in the MapSwipe web app."
+ description="Street projects are currently only available in the MapSwipe web app."
+ fullWidth
+ withoutShadow
+ />
+
+ ),
+ [ProjectTypeEnum.Conflation]: (
+
+
+ Validate AI generated features and compare with
+ existing features on OpenStreetMap.
+
+
@@ -198,6 +218,17 @@ function NewProject() {
async (submittedFormValues: PartialProjectCreateInputFields) => {
const finalValues = submittedFormValues as ProjectCreateInput;
+ /*
+ Conflation projects: instructions change per task depending
+ on the presence of OSM features. Currently, only building
+ features are supported.
+ */
+
+ if (finalValues.projectType === ProjectTypeEnum.Conflation) {
+ finalValues.lookFor = 'building';
+ finalValues.projectInstruction = 'Compare buildings';
+ }
+
try {
const result = await createNewProject({
data: finalValues,
diff --git a/backend b/backend
index c028260..271860f 160000
--- a/backend
+++ b/backend
@@ -1 +1 @@
-Subproject commit c028260535afd6a8fe1518d501e7536d1e6ac99e
+Subproject commit 271860fcf749a8f0c4c5eef8abe0ebb2de127f94