From 8b612115287acef9fbee33ff1357887e3bba4c6c Mon Sep 17 00:00:00 2001 From: frozenhelium Date: Mon, 8 Sep 2025 15:10:20 +0545 Subject: [PATCH 1/3] Add tutorial for street project --- app/Root/index.tsx | 1 + .../MapillaryImagePreview/index.tsx | 45 ++++ .../MapillaryImagePreview/styles.module.css | 3 + app/components/TextOutput/index.tsx | 20 +- .../StreetDetails/index.tsx | 88 +++++++ .../domain/ProjectSpecificDetails/index.tsx | 7 + .../domain/ProjectTaskDetails/index.tsx | 93 +++++++ .../domain/StreetScenarioPreview/index.tsx | 67 +++++ .../StreetScenarioPreview/styles.module.css | 15 ++ .../ValidateImageScenarioPreview/index.tsx | 26 +- .../styles.module.css | 5 +- .../domain/ValidateScenarioPreview/index.tsx | 31 ++- .../ValidateScenarioPreview/styles.module.css | 5 +- app/index.css | 43 ++-- app/utils/common.ts | 13 +- app/utils/query.ts | 2 + .../ProjectAdditionalInputs/index.tsx | 4 + .../UpdateProcessedProjectForm/index.tsx | 5 + .../CompareProjectSpecifics/index.tsx | 2 +- .../CompletenessProjectSpecifics/index.tsx | 2 +- .../FindProjectSpecifics/index.tsx | 4 +- .../index.tsx | 12 +- .../schema.ts | 14 +- .../StreetProjectSpecifics/index.tsx | 17 +- .../StreetProjectSpecifics/schema.ts | 6 +- .../ObjectSourceInput/index.tsx | 6 +- .../InformationPageInput/BlockInput/index.tsx | 1 + .../TaskInput/StreetPropertyInput/index.tsx | 59 +++++ .../TaskInput/StreetPropertyInput/schema.ts | 21 ++ .../StreetPropertyInput/styles.module.css | 8 + .../ScenarioPageInput/TaskInput/index.tsx | 25 +- .../ScenarioPageInput/TaskInput/schema.ts | 44 +++- .../EditTutorial/ScenarioPageInput/index.tsx | 9 + .../EditTutorial/ScenarioPageInput/schema.ts | 83 +++--- app/views/EditTutorial/index.tsx | 172 ++++--------- .../NewProject/ProjectGeneralInputs/index.tsx | 13 +- app/views/NewProject/index.tsx | 4 +- app/views/NewTutorial/index.tsx | 2 +- app/views/Projects/ProjectListItem/index.tsx | 1 + .../Tutorials/TutorialListItem/index.tsx | 1 + backend | 2 +- env.ts | 2 + package.json | 1 + pnpm-lock.yaml | 240 ++++++++++++++++++ 44 files changed, 967 insertions(+), 257 deletions(-) create mode 100644 app/components/MapillaryImagePreview/index.tsx create mode 100644 app/components/MapillaryImagePreview/styles.module.css create mode 100644 app/components/domain/ProjectSpecificDetails/StreetDetails/index.tsx create mode 100644 app/components/domain/ProjectTaskDetails/index.tsx create mode 100644 app/components/domain/StreetScenarioPreview/index.tsx create mode 100644 app/components/domain/StreetScenarioPreview/styles.module.css rename app/views/EditProject/UpdateProjectForm/StreetProjectSpecifics/{StreetMapilaryImageFiltersInput => StreetMapillaryImageFiltersInput}/index.tsx (87%) rename app/views/EditProject/UpdateProjectForm/StreetProjectSpecifics/{StreetMapilaryImageFiltersInput => StreetMapillaryImageFiltersInput}/schema.ts (50%) create mode 100644 app/views/EditTutorial/ScenarioPageInput/TaskInput/StreetPropertyInput/index.tsx create mode 100644 app/views/EditTutorial/ScenarioPageInput/TaskInput/StreetPropertyInput/schema.ts create mode 100644 app/views/EditTutorial/ScenarioPageInput/TaskInput/StreetPropertyInput/styles.module.css diff --git a/app/Root/index.tsx b/app/Root/index.tsx index fd1e9c3e..3fd418b8 100644 --- a/app/Root/index.tsx +++ b/app/Root/index.tsx @@ -1,5 +1,6 @@ import 'react-mde/lib/styles/css/react-mde-all.css'; import 'maplibre-gl/dist/maplibre-gl.css'; +import 'mapillary-js/dist/mapillary.css'; import { useCallback, diff --git a/app/components/MapillaryImagePreview/index.tsx b/app/components/MapillaryImagePreview/index.tsx new file mode 100644 index 00000000..823946b4 --- /dev/null +++ b/app/components/MapillaryImagePreview/index.tsx @@ -0,0 +1,45 @@ +import { + useEffect, + useRef, +} from 'react'; +import { + _cs, + isDefined, +} from '@togglecorp/fujs'; +import { Viewer } from 'mapillary-js'; + +import styles from './styles.module.css'; + +interface Props { + className?: string; + imageId: string | undefined; +} + +function MapillaryImagePreview(props: Props) { + const { + className, + imageId, + } = props; + + const containerRef = useRef(null); + const viewerRef = useRef(); + + useEffect(() => { + if (containerRef.current && isDefined(imageId)) { + viewerRef.current = new Viewer({ + accessToken: import.meta.env.APP_MAPILLARY_API_KEY, + container: containerRef.current, + imageId, + }); + } + }, [imageId]); + + return ( +
+ ); +} + +export default MapillaryImagePreview; diff --git a/app/components/MapillaryImagePreview/styles.module.css b/app/components/MapillaryImagePreview/styles.module.css new file mode 100644 index 00000000..6448b58f --- /dev/null +++ b/app/components/MapillaryImagePreview/styles.module.css @@ -0,0 +1,3 @@ +.mapillary-image-preview { + isolation: isolate; +} diff --git a/app/components/TextOutput/index.tsx b/app/components/TextOutput/index.tsx index 70dafc7a..35afa83a 100644 --- a/app/components/TextOutput/index.tsx +++ b/app/components/TextOutput/index.tsx @@ -43,6 +43,18 @@ function formatDate( return formattedDate; } +function formatBoolean(value: boolean | null | undefined) { + if (value === true) { + return 'Yes'; + } + + if (value === false) { + return 'No'; + } + + return null; +} + interface BaseProps { className?: string; icon?: React.ReactNode @@ -53,6 +65,7 @@ interface BaseProps { withWrap?: boolean; spacing?: SpacingType; withCenterAlign?: boolean; + emptyValueDisplay?: React.ReactNode; } interface BooleanProps { @@ -96,6 +109,7 @@ function TextOutput(props: Props) { spacing, withWrap, withCenterAlign, + emptyValueDisplay = '--', } = props; const spacingClassName = useSpacingToken({ @@ -113,6 +127,10 @@ function TextOutput(props: Props) { return formatDate(value); } + if (valueType === 'boolean') { + return formatBoolean(value); + } + return value; }, [value, valueType]); @@ -142,7 +160,7 @@ function TextOutput(props: Props) {
)}
- {formattedValue} + {formattedValue ?? emptyValueDisplay}
{description && (
diff --git a/app/components/domain/ProjectSpecificDetails/StreetDetails/index.tsx b/app/components/domain/ProjectSpecificDetails/StreetDetails/index.tsx new file mode 100644 index 00000000..404db765 --- /dev/null +++ b/app/components/domain/ProjectSpecificDetails/StreetDetails/index.tsx @@ -0,0 +1,88 @@ +import { isNotDefined } from '@togglecorp/fujs'; +import { removeNull } from '@togglecorp/toggle-form'; + +import Container from '#components/Container'; +import CustomOptionPreview from '#components/domain/CustomOptionsPreview'; +import ProjectAssetPreview from '#components/domain/ProjectAssetPreview'; +import ListLayout from '#components/ListLayout'; +import TextOutput from '#components/TextOutput'; +import { StreetProjectPropertyType } from '#generated/types/graphql'; + +interface Props { + data: StreetProjectPropertyType | undefined; +} + +function StreetDetails(props: Props) { + const { data } = props; + + if (isNotDefined(data)) { + return null; + } + + return ( + <> + + + + + + + + + + + + + + + + + + + + + ); +} + +export default StreetDetails; diff --git a/app/components/domain/ProjectSpecificDetails/index.tsx b/app/components/domain/ProjectSpecificDetails/index.tsx index 130ad3df..ba659e7d 100644 --- a/app/components/domain/ProjectSpecificDetails/index.tsx +++ b/app/components/domain/ProjectSpecificDetails/index.tsx @@ -10,6 +10,7 @@ import ProjectTypeOutput from '../ProjectTypeOutput'; import CompareDetails from './CompareDetails'; import CompletenessDetails from './CompletenessDetails'; import FindDetails from './FindDetails'; +import StreetDetails from './StreetDetails'; import ValidateDetails from './ValidateDetails'; import ValidateImageDetails from './ValidateImageDetails'; @@ -103,6 +104,12 @@ function ProjectSpecificDetails(props: Props) { data={projectData.project.projectTypeSpecifics} /> )} + {/* eslint-disable-next-line no-underscore-dangle */} + {projectData?.project.projectTypeSpecifics?.__typename === 'StreetProjectPropertyType' && ( + + )} ); } diff --git a/app/components/domain/ProjectTaskDetails/index.tsx b/app/components/domain/ProjectTaskDetails/index.tsx new file mode 100644 index 00000000..d4674a08 --- /dev/null +++ b/app/components/domain/ProjectTaskDetails/index.tsx @@ -0,0 +1,93 @@ +import { PiInfo } from 'react-icons/pi'; +import { isNotDefined } from '@togglecorp/fujs'; + +import Container from '#components/Container'; +import PopupButton from '#components/PopupButton'; +import TextOutput from '#components/TextOutput'; +import { ProjectType } from '#generated/types/graphql'; + +type ProjectAdditionalInputFields = Pick< +ProjectType, +'verificationNumber' +| 'groupSize' +| 'maxTasksPerUser' +> + +interface Props { + value: ProjectAdditionalInputFields | undefined; +} + +function ProjectTaskDetails(props: Props) { + const { + value, + } = props; + + if (isNotDefined(value)) { + return null; + } + + return ( + + } + withoutDropdownIcon + styleVariant="transparent" + withoutPadding + > + How many people do you want to see every + tile before you consider it finished? + (default is 3 - more is recommended for harder tasks, + but this will also make project take longer) + + )} + /> + } + withoutDropdownIcon + styleVariant="transparent" + withoutPadding + > + How big should a mapping session be? + Group size refers to the number of tasks per mapping session. + + )} + /> + } + withoutDropdownIcon + styleVariant="transparent" + withoutPadding + > + How many tasks each user is allowed to work on for this project. + Empty indicates that no limit is set. + + )} + /> + + ); +} + +export default ProjectTaskDetails; diff --git a/app/components/domain/StreetScenarioPreview/index.tsx b/app/components/domain/StreetScenarioPreview/index.tsx new file mode 100644 index 00000000..5e5c4852 --- /dev/null +++ b/app/components/domain/StreetScenarioPreview/index.tsx @@ -0,0 +1,67 @@ +import { useState } from 'react'; +import { _cs } from '@togglecorp/fujs'; +import { PartialForm } from '@togglecorp/toggle-form'; + +import Icon from '#components/domain/Icon'; +import TutorialPreviewScreenSelectInput, { PreviewItem } from '#components/domain/TutorialPreviewScreenSelectInput'; +import InlineLayout from '#components/InlineLayout'; +import ListLayout from '#components/ListLayout'; +import MapillaryImagePreview from '#components/MapillaryImagePreview'; +import MobilePreview from '#components/MobilePreview'; +import { TutorialScenarioPageCreateInput } from '#generated/types/graphql'; + +import { PartialCustomOptionInputFields } from '../CustomOptionInput/schema'; +import CustomOptionPreview from '../CustomOptionsPreview'; + +import styles from './styles.module.css'; + +interface Props { + className?: string; + projectInstruction: string | undefined | null; + scenario: PartialForm | undefined; + customOptions: PartialCustomOptionInputFields[] | undefined; +} + +function StreetScenarioPreview(props: Props) { + const { + className, + scenario, + projectInstruction, + customOptions, + } = props; + + const [preview, setPreview] = useState(); + const imageId = scenario?.tasks?.[0].projectTypeSpecifics?.street?.mapillaryImageId; + + return ( + + } + popupTitle={preview?.title || '{title}'} + popupDescription={preview?.description || '{description}'} + popupVariant={preview?.popupVariant} + contentClassName={styles.content} + > + + + + + + + + ); +} + +export default StreetScenarioPreview; diff --git a/app/components/domain/StreetScenarioPreview/styles.module.css b/app/components/domain/StreetScenarioPreview/styles.module.css new file mode 100644 index 00000000..17bde00a --- /dev/null +++ b/app/components/domain/StreetScenarioPreview/styles.module.css @@ -0,0 +1,15 @@ +.find-scenario-preview { + isolation: isolate; + + .content { + align-items: center; + justify-content: space-between; + padding: var(--spacing-lg); + + .street-preview { + margin-top: var(--spacing-lg); + width: var(--size-tile-validate); + height: calc(1.5 * var(--size-tile-validate)); + } + } +} diff --git a/app/components/domain/ValidateImageScenarioPreview/index.tsx b/app/components/domain/ValidateImageScenarioPreview/index.tsx index 99acb4c9..04ecd795 100644 --- a/app/components/domain/ValidateImageScenarioPreview/index.tsx +++ b/app/components/domain/ValidateImageScenarioPreview/index.tsx @@ -13,9 +13,13 @@ import { PartialForm } from '@togglecorp/toggle-form'; import { PartialCustomOptionInputFields } from '#components/domain/CustomOptionInput/schema'; import CustomOptionPreview from '#components/domain/CustomOptionsPreview'; import Icon from '#components/domain/Icon'; +import InlineLayout from '#components/InlineLayout'; +import ListLayout from '#components/ListLayout'; import MobilePreview from '#components/MobilePreview'; import { TutorialScenarioPageCreateInput } from '#generated/types/graphql'; +import TutorialPreviewScreenSelectInput, { PreviewItem } from '../TutorialPreviewScreenSelectInput'; + import styles from './styles.module.css'; interface Props { @@ -33,6 +37,8 @@ function ValidateImageScenarioPreview(props: Props) { customOptions, } = props; + const [preview, setPreview] = useState(); + const imgRef = useRef(null); const task = scenario?.tasks?.[0]?.projectTypeSpecifics?.validateImage; const [bbox, setBbox] = useState<{ @@ -89,12 +95,16 @@ function ValidateImageScenarioPreview(props: Props) { }, [task]); return ( -
+ } - popupTitle={scenario?.instructionsTitle || '{title}'} - popupDescription={scenario?.instructionsDescription || '{description}'} + popupIcons={} + popupTitle={preview?.title || '{title}'} + popupDescription={preview?.description || '{description}'} + popupVariant={preview?.popupVariant} contentClassName={styles.content} >
@@ -120,7 +130,13 @@ function ValidateImageScenarioPreview(props: Props) { value={customOptions} /> -
+ + + +
); } diff --git a/app/components/domain/ValidateImageScenarioPreview/styles.module.css b/app/components/domain/ValidateImageScenarioPreview/styles.module.css index b64a75a6..bdd2fb6c 100644 --- a/app/components/domain/ValidateImageScenarioPreview/styles.module.css +++ b/app/components/domain/ValidateImageScenarioPreview/styles.module.css @@ -1,8 +1,5 @@ .validate-image-scenario-preview { - display: flex; - align-items: center; - flex-direction: column; - gap: var(--spacing-sm); + isolation: isolate; .content { align-items: center; diff --git a/app/components/domain/ValidateScenarioPreview/index.tsx b/app/components/domain/ValidateScenarioPreview/index.tsx index d7ed6b7a..20ac580e 100644 --- a/app/components/domain/ValidateScenarioPreview/index.tsx +++ b/app/components/domain/ValidateScenarioPreview/index.tsx @@ -1,4 +1,7 @@ -import { useMemo } from 'react'; +import { + useMemo, + useState, +} from 'react'; import { _cs, isDefined, @@ -14,12 +17,16 @@ import { PartialCustomOptionInputFields } from '#components/domain/CustomOptionI import CustomOptionPreview from '#components/domain/CustomOptionsPreview'; import GeoJsonPreview from '#components/domain/GeoJsonPreview'; import Icon from '#components/domain/Icon'; +import InlineLayout from '#components/InlineLayout'; +import ListLayout from '#components/ListLayout'; import MobilePreview from '#components/MobilePreview'; import { ProjectRasterTileServerConfig, TutorialScenarioPageCreateInput, } from '#generated/types/graphql'; +import TutorialPreviewScreenSelectInput, { PreviewItem } from '../TutorialPreviewScreenSelectInput'; + import styles from './styles.module.css'; const layerOptions: Omit = { @@ -49,6 +56,8 @@ function ValidateScenarioPreview(props: Props) { customOptions, } = props; + const [preview, setPreview] = useState(); + const generatedGeojson = useMemo(() => { const features: Array = scenario?.tasks?.map((task) => { if (isNotDefined(task.projectTypeSpecifics?.validate?.objectGeometry)) { @@ -71,12 +80,16 @@ function ValidateScenarioPreview(props: Props) { }, [scenario]); return ( -
+ } - popupTitle={scenario?.instructionsTitle || '{title}'} - popupDescription={scenario?.instructionsDescription || '{description}'} + popupIcons={} + popupTitle={preview?.title || '{title}'} + popupDescription={preview?.description || '{description}'} + popupVariant={preview?.popupVariant} contentClassName={styles.content} > -
+ + + + ); } diff --git a/app/components/domain/ValidateScenarioPreview/styles.module.css b/app/components/domain/ValidateScenarioPreview/styles.module.css index 4ac7c6a4..2b2d5a42 100644 --- a/app/components/domain/ValidateScenarioPreview/styles.module.css +++ b/app/components/domain/ValidateScenarioPreview/styles.module.css @@ -1,8 +1,5 @@ .validate-scenario-preview { - display: flex; - align-items: center; - flex-direction: column; - gap: var(--spacing-sm); + isolation: isolate; .content { align-items: center; diff --git a/app/index.css b/app/index.css index 13d2d525..3d96f9a6 100644 --- a/app/index.css +++ b/app/index.css @@ -6,13 +6,31 @@ --font-family-monospace: 'Source Code Pro', monospace; --font-family-sans-serif: 'Source Sans 3', sans-serif; - --spacing-2xs: 0.15rem; - --spacing-xs: 0.25rem; - --spacing-sm: 0.45rem; - --spacing-md: 0.7rem; - --spacing-lg: 1.1rem; - --spacing-xl: 1.625rem; - --spacing-2xl: 2.625rem; + --spacing-base: 1rem; + --font-size-base: 1rem; + + @media screen and (max-width: 40rem) { + --spacing-base: 0.8rem; + --font-size-base: 0.8rem; + } + + --spacing-2xs: calc(var(--spacing-base) * 0.15); + --spacing-xs: calc(var(--spacing-base) * 0.25); + --spacing-sm: calc(var(--spacing-base) * 0.45); + --spacing-md: calc(var(--spacing-base) * 0.7); + --spacing-lg: calc(var(--spacing-base) * 1.1); + --spacing-xl: calc(var(--spacing-base) * 1.625); + --spacing-2xl: calc(var(--spacing-base) * 2.625); + + --font-size-2xs: calc(var(--font-size-base) * 0.625); + --font-size-xs: calc(var(--font-size-base) * 0.75); + --font-size-sm: calc(var(--font-size-base) * 0.875); + --font-size-md: calc(var(--font-size-base) * 1); + --font-size-lg: calc(var(--font-size-base) * 1.15); + --font-size-xl: calc(var(--font-size-base) * 1.33); + --font-size-2xl: calc(var(--font-size-base) * 1.65); + --font-size-3xl: calc(var(--font-size-base) * 2.0); + --font-size-4xl: calc(var(--font-size-base) * 2.5); --color-primary: #001958; --color-text-on-dark: #ffffff; @@ -44,17 +62,6 @@ --color-input-border: rgba(0, 0, 0, 0.1); --color-active-input-border: var(--color-accent); - --font-size-base: 1rem; - - --font-size-2xs: calc(var(--font-size-base) * 0.625); - --font-size-xs: calc(var(--font-size-base) * 0.75); - --font-size-sm: calc(var(--font-size-base) * 0.875); - --font-size-md: calc(var(--font-size-base) * 1); - --font-size-lg: calc(var(--font-size-base) * 1.15); - --font-size-xl: calc(var(--font-size-base) * 1.33); - --font-size-2xl: calc(var(--font-size-base) * 1.65); - --font-size-3xl: calc(var(--font-size-base) * 2.0); - --font-size-4xl: calc(var(--font-size-base) * 2.5); --font-weight-light: 300; --font-weight-medium: 360; diff --git a/app/utils/common.ts b/app/utils/common.ts index aa27d2ed..c6b2050b 100644 --- a/app/utils/common.ts +++ b/app/utils/common.ts @@ -462,19 +462,8 @@ export type ZoomLeveOption = { area: number; }; +// TODO: verify scale and area export const zoomLevelOptions: ZoomLeveOption[] = [ - { - value: 10, label: 'Town View', description: 'Towns, small cities', scale: 152, area: 1500, - }, - { - value: 11, label: 'Neighborhood View', description: 'Urban neighborhoods', scale: 76, area: 400, - }, - { - value: 12, label: 'Street View', description: 'Street-level navigation', scale: 38, area: 100, - }, - { - value: 13, label: 'Local Street View', description: 'Blocks, parks, schools', scale: 19, area: 25, - }, { value: 14, label: 'Sub-Street View', description: 'Individual buildings', scale: 9.5, area: 5, }, diff --git a/app/utils/query.ts b/app/utils/query.ts index 7ec36204..92ffac7a 100644 --- a/app/utils/query.ts +++ b/app/utils/query.ts @@ -309,6 +309,8 @@ fragment TutorialDetailFields on TutorialType { } ... on StreetTutorialTaskPropertyType { __typename + mapillaryImageId + geometry } } } diff --git a/app/views/EditProject/ProjectAdditionalInputs/index.tsx b/app/views/EditProject/ProjectAdditionalInputs/index.tsx index ea39a3da..3748df2b 100644 --- a/app/views/EditProject/ProjectAdditionalInputs/index.tsx +++ b/app/views/EditProject/ProjectAdditionalInputs/index.tsx @@ -63,6 +63,7 @@ function ProjectAdditionalInputs(props: Props) { onChange={setFieldValue} error={error?.verificationNumber} disabled={disabled} + hint="How many people do you want to see every tile before you consider it finished? (default is 3 - more is recommended for harder tasks, but this will also make project take longer)" /> diff --git a/app/views/EditProject/UpdateProcessedProjectForm/index.tsx b/app/views/EditProject/UpdateProcessedProjectForm/index.tsx index d19f287e..0d525fa4 100644 --- a/app/views/EditProject/UpdateProcessedProjectForm/index.tsx +++ b/app/views/EditProject/UpdateProcessedProjectForm/index.tsx @@ -21,6 +21,7 @@ import Container from '#components/Container'; import AssetInput from '#components/domain/AssetInput'; import ProjectSpecificDetails from '#components/domain/ProjectSpecificDetails'; import ProjectStatusTimeline from '#components/domain/ProjectStatusTimeline'; +import ProjectTaskDetails from '#components/domain/ProjectTaskDetails'; import InputError from '#components/InputError'; import ListLayout from '#components/ListLayout'; import NonFieldError from '#components/NonFieldError'; @@ -296,8 +297,12 @@ function UpdateProcessedProjectForm(props: Props) { onChange={setFieldValue} error={error?.image} disabled={baseInputsDisabled} + hint="Make sure you have the rights to use the image. It should end with .jpg or .png." /> + diff --git a/app/views/EditProject/UpdateProjectForm/CompletenessProjectSpecifics/index.tsx b/app/views/EditProject/UpdateProjectForm/CompletenessProjectSpecifics/index.tsx index c1eed46f..451c7d03 100644 --- a/app/views/EditProject/UpdateProjectForm/CompletenessProjectSpecifics/index.tsx +++ b/app/views/EditProject/UpdateProjectForm/CompletenessProjectSpecifics/index.tsx @@ -60,7 +60,7 @@ function CompletenessProjectSpecifics(props: Props) { value={value?.aoiGeometry} error={error?.aoiGeometry} inputType={ProjectAssetInputTypeEnum.AoiGeometry} - hint="Upload your project area as GeoJSON File (max. 1MB). Make sure that you provide a single polygon geometry." + hint="Upload your project area as GeoJSON File (max. 1MB)" disabled={disabled} withoutPreview /> diff --git a/app/views/EditProject/UpdateProjectForm/FindProjectSpecifics/index.tsx b/app/views/EditProject/UpdateProjectForm/FindProjectSpecifics/index.tsx index 7711ae40..a9ed64c0 100644 --- a/app/views/EditProject/UpdateProjectForm/FindProjectSpecifics/index.tsx +++ b/app/views/EditProject/UpdateProjectForm/FindProjectSpecifics/index.tsx @@ -52,9 +52,7 @@ function FindProjectSpecifics(props: Props) { value={value?.aoiGeometry} error={error?.aoiGeometry} inputType={ProjectAssetInputTypeEnum.AoiGeometry} - // FIXME: add appropriate hint and validation - // hint="Upload your project area as GeoJSON File (max. 1MB). - // Make sure that you provide a single polygon geometry." + hint="Upload your project area as GeoJSON File (max. 1MB)." disabled={disabled} withoutPreview /> diff --git a/app/views/EditProject/UpdateProjectForm/StreetProjectSpecifics/StreetMapilaryImageFiltersInput/index.tsx b/app/views/EditProject/UpdateProjectForm/StreetProjectSpecifics/StreetMapillaryImageFiltersInput/index.tsx similarity index 87% rename from app/views/EditProject/UpdateProjectForm/StreetProjectSpecifics/StreetMapilaryImageFiltersInput/index.tsx rename to app/views/EditProject/UpdateProjectForm/StreetProjectSpecifics/StreetMapillaryImageFiltersInput/index.tsx index 5dcbc4cd..2e69d7cd 100644 --- a/app/views/EditProject/UpdateProjectForm/StreetProjectSpecifics/StreetMapilaryImageFiltersInput/index.tsx +++ b/app/views/EditProject/UpdateProjectForm/StreetProjectSpecifics/StreetMapillaryImageFiltersInput/index.tsx @@ -11,18 +11,18 @@ import ListLayout from '#components/ListLayout'; import NumberInput from '#components/NumberInput'; import TextInput from '#components/TextInput'; -import { PartialStreetMapilaryImageFiltersInputFields } from './schema'; +import { PartialStreetMapillaryImageFiltersInputFields } from './schema'; interface Props { - value: PartialStreetMapilaryImageFiltersInputFields | undefined | null; - error: LeafError | ObjectError; + value: PartialStreetMapillaryImageFiltersInputFields | undefined | null; + error: LeafError | ObjectError; setFieldValue: ( - ...entries: EntriesAsList + ...entries: EntriesAsList ) => void; disabled?: boolean; } -function StreetMapilaryImageFiltersInput(props: Props) { +function StreetMapillaryImageFiltersInput(props: Props) { const { value, error: formError, @@ -99,4 +99,4 @@ function StreetMapilaryImageFiltersInput(props: Props) { ); } -export default StreetMapilaryImageFiltersInput; +export default StreetMapillaryImageFiltersInput; diff --git a/app/views/EditProject/UpdateProjectForm/StreetProjectSpecifics/StreetMapilaryImageFiltersInput/schema.ts b/app/views/EditProject/UpdateProjectForm/StreetProjectSpecifics/StreetMapillaryImageFiltersInput/schema.ts similarity index 50% rename from app/views/EditProject/UpdateProjectForm/StreetProjectSpecifics/StreetMapilaryImageFiltersInput/schema.ts rename to app/views/EditProject/UpdateProjectForm/StreetProjectSpecifics/StreetMapillaryImageFiltersInput/schema.ts index d76e59e3..4f68b54c 100644 --- a/app/views/EditProject/UpdateProjectForm/StreetProjectSpecifics/StreetMapilaryImageFiltersInput/schema.ts +++ b/app/views/EditProject/UpdateProjectForm/StreetProjectSpecifics/StreetMapillaryImageFiltersInput/schema.ts @@ -6,22 +6,22 @@ import { import { StreetMapillaryImageFiltersInput } from '#generated/types/graphql'; import { DeepNonNullable } from '#utils/types'; -export type PartialStreetMapilaryImageFiltersInputFields = PartialForm< +export type PartialStreetMapillaryImageFiltersInputFields = PartialForm< DeepNonNullable >; -type StreetMapilaryImageFiltersFormSchema = ObjectSchema< - PartialStreetMapilaryImageFiltersInputFields +type StreetMapillaryImageFiltersFormSchema = ObjectSchema< + PartialStreetMapillaryImageFiltersInputFields >; // eslint-disable-next-line max-len -export const defaultStreetMapilaryImageFiltersInputFormValue: PartialStreetMapilaryImageFiltersInputFields = { +export const defaultStreetMapillaryImageFiltersInputFormValue: PartialStreetMapillaryImageFiltersInputFields = { randomizeOrder: false, isPano: false, }; -const streetMapilaryimageFiltersFormSchema: StreetMapilaryImageFiltersFormSchema = { - fields: (): ReturnType => ({ +const streetMapillaryimageFiltersFormSchema: StreetMapillaryImageFiltersFormSchema = { + fields: (): ReturnType => ({ isPano: {}, creatorId: {}, organizationId: {}, @@ -32,4 +32,4 @@ const streetMapilaryimageFiltersFormSchema: StreetMapilaryImageFiltersFormSchema }), }; -export default streetMapilaryimageFiltersFormSchema; +export default streetMapillaryimageFiltersFormSchema; diff --git a/app/views/EditProject/UpdateProjectForm/StreetProjectSpecifics/index.tsx b/app/views/EditProject/UpdateProjectForm/StreetProjectSpecifics/index.tsx index 0c3462ad..c65cbdc0 100644 --- a/app/views/EditProject/UpdateProjectForm/StreetProjectSpecifics/index.tsx +++ b/app/views/EditProject/UpdateProjectForm/StreetProjectSpecifics/index.tsx @@ -21,11 +21,11 @@ import NonFieldError from '#components/NonFieldError'; import { ProjectAssetInputTypeEnum } from '#generated/types/graphql'; import { - defaultStreetMapilaryImageFiltersInputFormValue, - PartialStreetMapilaryImageFiltersInputFields, -} from './StreetMapilaryImageFiltersInput/schema'; + defaultStreetMapillaryImageFiltersInputFormValue, + PartialStreetMapillaryImageFiltersInputFields, +} from './StreetMapillaryImageFiltersInput/schema'; import { type PartialStreetSpecificFields } from './schema'; -import StreetMapilaryImageFiltersInput from './StreetMapilaryImageFiltersInput'; +import StreetMapillaryImageFiltersInput from './StreetMapillaryImageFiltersInput'; interface Props { projectId: string; @@ -68,10 +68,10 @@ function StreetProjectSpecifics(props: Props) { ); }, [setFieldValue]); - const setStreetMapilaryImageFiltersInputFieldValue = useFormObject<'mapillaryImageFilters', PartialStreetMapilaryImageFiltersInputFields>( + const setStreetMapillaryImageFiltersInputFieldValue = useFormObject<'mapillaryImageFilters', PartialStreetMapillaryImageFiltersInputFields>( 'mapillaryImageFilters' as const, setFieldValue, - defaultStreetMapilaryImageFiltersInputFormValue, + defaultStreetMapillaryImageFiltersInputFormValue, ); return ( @@ -124,12 +124,13 @@ function StreetProjectSpecifics(props: Props) { error={error?.aoiGeometry} inputType={ProjectAssetInputTypeEnum.AoiGeometry} disabled={disabled} + hint="Upload your project area as GeoJSON File (max. 1MB)" withoutPreview /> - diff --git a/app/views/EditProject/UpdateProjectForm/StreetProjectSpecifics/schema.ts b/app/views/EditProject/UpdateProjectForm/StreetProjectSpecifics/schema.ts index 75e38378..d7ad1b0e 100644 --- a/app/views/EditProject/UpdateProjectForm/StreetProjectSpecifics/schema.ts +++ b/app/views/EditProject/UpdateProjectForm/StreetProjectSpecifics/schema.ts @@ -11,7 +11,7 @@ import { type PartialProjectUpdateInput, type UpdateProjectContext, } from '../schema'; -import streetMapilaryimageFiltersFormSchema, { defaultStreetMapilaryImageFiltersInputFormValue } from './StreetMapilaryImageFiltersInput/schema'; +import streetMapillaryimageFiltersFormSchema, { defaultStreetMapillaryImageFiltersInputFormValue } from './StreetMapillaryImageFiltersInput/schema'; export type PartialStreetSpecificFields = PartialForm< DeepNonNullable, @@ -24,7 +24,7 @@ type StreetSpecificFormSchema = ObjectSchema< >; export const defaultStreetSpecificFormValue: PartialStreetSpecificFields = { - mapillaryImageFilters: defaultStreetMapilaryImageFiltersInputFormValue, + mapillaryImageFilters: defaultStreetMapillaryImageFiltersInputFormValue, }; const streetSpecificFormSchema: StreetSpecificFormSchema = { @@ -34,7 +34,7 @@ const streetSpecificFormSchema: StreetSpecificFormSchema = { member: () => customOptionSchema, }, aoiGeometry: {}, - mapillaryImageFilters: streetMapilaryimageFiltersFormSchema, + mapillaryImageFilters: streetMapillaryimageFiltersFormSchema, }), }; diff --git a/app/views/EditProject/UpdateProjectForm/ValidateProjectSpecifics/ObjectSourceInput/index.tsx b/app/views/EditProject/UpdateProjectForm/ValidateProjectSpecifics/ObjectSourceInput/index.tsx index e533dbe8..7cb89dba 100644 --- a/app/views/EditProject/UpdateProjectForm/ValidateProjectSpecifics/ObjectSourceInput/index.tsx +++ b/app/views/EditProject/UpdateProjectForm/ValidateProjectSpecifics/ObjectSourceInput/index.tsx @@ -79,8 +79,7 @@ function ObjectSourceInput(props: Props) { value={value.aoiGeometry} error={error?.aoiGeometry} inputType={ProjectAssetInputTypeEnum.AoiGeometry} - // TODO(frozenhelium): add proper hint - // hint="Upload your project area as GeoJSON File (max. 1MB) + hint="Upload your project area as GeoJSON File (max. 1MB)" disabled={disabled} withoutPreview /> @@ -92,6 +91,7 @@ function ObjectSourceInput(props: Props) { value={value.objectGeojsonUrl} error={error?.objectGeojsonUrl} onChange={setFieldValue} + hint="Provide a direct link to a GeoJSON file containing your building footprint geometries." /> )} {value?.sourceType === TaskingManager && ( @@ -101,6 +101,7 @@ function ObjectSourceInput(props: Props) { value={value.taskingManagerProjectId} error={error?.taskingManagerProjectId} onChange={setFieldValue} + hint="Provide the ID of a HOT Tasking Manager Project (only numbers, e.g. 6526)." /> )} {(value?.sourceType === AoiGeojsonFile || value?.sourceType === TaskingManager) && ( @@ -110,6 +111,7 @@ function ObjectSourceInput(props: Props) { value={value.ohsomeFilter} error={error?.ohsomeFilter} onChange={setFieldValue} + hint="Please specify which objects should be included in your project." /> )} diff --git a/app/views/EditTutorial/InformationPageInput/BlockInput/index.tsx b/app/views/EditTutorial/InformationPageInput/BlockInput/index.tsx index 41f312fb..05820603 100644 --- a/app/views/EditTutorial/InformationPageInput/BlockInput/index.tsx +++ b/app/views/EditTutorial/InformationPageInput/BlockInput/index.tsx @@ -87,6 +87,7 @@ function BlockInput(props: Props) { onChange={setFieldValue} error={error?.image} inputType={TutorialAssetInputTypeEnum.InformationBlockImage} + hint="Make sure you have the rights to use the image. It should end with .jpg or .png." /> )} diff --git a/app/views/EditTutorial/ScenarioPageInput/TaskInput/StreetPropertyInput/index.tsx b/app/views/EditTutorial/ScenarioPageInput/TaskInput/StreetPropertyInput/index.tsx new file mode 100644 index 00000000..f1626ed0 --- /dev/null +++ b/app/views/EditTutorial/ScenarioPageInput/TaskInput/StreetPropertyInput/index.tsx @@ -0,0 +1,59 @@ +import { _cs } from '@togglecorp/fujs'; +import { + EntriesAsList, + getErrorObject, + LeafError, + ObjectError, +} from '@togglecorp/toggle-form'; + +import TextArea from '#components/TextArea'; +import TextInput from '#components/TextInput'; + +import { PartialStreetPropertyInputFields } from './schema'; + +import styles from './styles.module.css'; + +interface Props { + className?: string; + value: PartialStreetPropertyInputFields | undefined; + setFieldValue: (...entries: EntriesAsList) => void; + error: LeafError | ObjectError; + disabled?: boolean; +} + +function StreetPropertyInput(props: Props) { + const { + className, + value, + setFieldValue, + error: formError, + disabled, + } = props; + + const error = getErrorObject(formError); + + return ( +
+ +