Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions app/components/domain/CompareScenarioPreview/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<div className={_cs(styles.compareScenarioPreview, className)}>
<MobilePreview
Expand All @@ -82,7 +92,7 @@ function CompareScenarioPreview(props: Props) {
tileSize={280}
className={styles.mapContainer}
geoJson={generatedGeojson}
baseTileServer={removeNull(tileServerProperty)}
baseTileServer={tileServerPropertySafe}
geoJsonLayerOptions={layerOptions}
disablePan
/>
Expand All @@ -91,7 +101,7 @@ function CompareScenarioPreview(props: Props) {
tileSize={280}
className={styles.mapContainer}
geoJson={generatedGeojson}
baseTileServer={removeNull(tileServerBProperty)}
baseTileServer={tileServerBPropertySafe}
geoJsonLayerOptions={layerOptions}
disablePan
/>
Expand Down
7 changes: 6 additions & 1 deletion app/components/domain/FindScenarioPreview/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ function FindScenarioPreview(props: Props) {
return createGeoJsonFromTiles(tiles);
}, [scenario]);

const tileServerPropertySafe = useMemo(
() => removeNull(tileServerProperty),
[tileServerProperty],
);

return (
<ListLayout
className={_cs(styles.findScenarioPreview, className)}
Expand All @@ -84,7 +89,7 @@ function FindScenarioPreview(props: Props) {
tileSize={160}
className={styles.mapContainer}
geoJson={generatedGeojson}
baseTileServer={removeNull(tileServerProperty)}
baseTileServer={tileServerPropertySafe}
geoJsonLayerOptions={layerOptions}
disablePan
/>
Expand Down
139 changes: 139 additions & 0 deletions app/components/domain/LocateFeaturesScenarioPreview/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
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 styles from './styles.module.css';

// FIXME: add actual colors from custom options
const layerOptions: Omit<FillLayerSpecification, 'id' | 'source'> = {
type: 'fill',
paint: {
'fill-color': [
'match',
['get', 'reference'],
1,
'green',
2,
'yellow',
3,
'red',
'transparent',
],
'fill-outline-color': '#ffffff',
'fill-opacity': 0.5,
},
};

interface Props {
className?: string;
tileServerProperty: ProjectRasterTileServerConfig | undefined;
projectInstruction: string | undefined | null;
scenario: PartialForm<TutorialScenarioPageCreateInput> | undefined;
preview: PreviewItem | undefined;
subgridSize: SubGridSizeEnum | undefined;
}

function LocateFeaturesScenarioPreview(props: Props) {
const {
className,
scenario,
projectInstruction,
tileServerProperty,
preview,
subgridSize,
} = props;

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 (
<ListLayout
className={_cs(styles.locateFeaturesScenarioPreview, className)}
layout="block"
>
<MobilePreview
heading={projectInstruction}
popupIcons={<Icon value={preview?.icon} />}
popupTitle={preview?.title || '{title}'}
popupDescription={preview?.description || '{description}'}
popupVariant={preview?.popupVariant}
contentClassName={styles.content}
>
<GeoJsonPreview
// NOTE: this should match --size-tile-locateFeatures
tileSize={280}
className={styles.mapContainer}
geoJson={generatedGeojson}
baseTileServer={tileServerPropertySafe}
geoJsonLayerOptions={layerOptions}
disablePan
/>
</MobilePreview>
</ListLayout>
);
}

export default LocateFeaturesScenarioPreview;
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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<MapZoomViewType>('aoiBounds');

// eslint-disable-next-line no-underscore-dangle
if (isNotDefined(data) || data.__typename !== 'LocateProjectPropertyType') {
return null;
}

const tileZ = data.zoomLevel;

return (
<>
<ZoomLevelOutput value={data.zoomLevel} />
<ListLayout layout="grid">
<ListLayout layout="block">
<BaseMap
baseTileServer={removeNull(data?.tileServerProperty)}
>
<DefaultMapContainer />
<GeoJsonAssetMapSource
geoJsonAssetId={data?.aoiGeometry}
defaultBounds={defaultBounds}
zoomLevel={zoomView === 'zoomLevel' ? tileZ : undefined}
withPadding={zoomView === 'aoiBounds'}
/>
</BaseMap>
<InlineLayout withCenteredContent>
<MapZoomViewSelectInput
value={zoomView}
onChange={setZoomView}
/>
</InlineLayout>
</ListLayout>
<RasterTileServerOutput
value={data.tileServerProperty}
withHeaderBorder
withPadding
/>
</ListLayout>
<TextOutput
label="Sub grid size"
value={data.subGridSize}
/>
</>
);
}

export default LocateFeaturesDetails;
8 changes: 8 additions & 0 deletions app/components/domain/ProjectSpecificDetails/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -134,6 +135,13 @@ function ProjectSpecificDetails(props: Props) {
data={projectData.project.projectTypeSpecifics}
/>
)}
{/* eslint-disable-next-line no-underscore-dangle */}
{projectData?.project.projectTypeSpecifics?.__typename === 'LocateProjectPropertyType' && (
<LocateFeaturesDetails
data={projectData.project.projectTypeSpecifics}
defaultBounds={defaultBounds}
/>
)}
</Container>
);
}
Expand Down
5 changes: 5 additions & 0 deletions app/components/domain/ProjectTypeIcon/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { PiGridFour } from 'react-icons/pi';
import { _cs } from '@togglecorp/fujs';

import { ProjectTypeEnum } from '#generated/types/graphql';
Expand Down Expand Up @@ -119,6 +120,10 @@ function ProjectTypeIcon(props: Props) {
);
}

if (type === ProjectTypeEnum.Locate) {
return <PiGridFour className={className} />;
}

const remainingTypes = type;

remainingTypes satisfies never;
Expand Down
2 changes: 2 additions & 0 deletions app/contexts/EnumsContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
Expand Down Expand Up @@ -61,6 +62,7 @@ export const defaultAllEnumsValue: EnumsContextProps = {
overlayLayerTypeOptions: [],
tutorialStatusOptions: [],
firebasePushStatusOptions: [],
subGridSizeOptions: [],

validateObjectSourceTypeMapping: undefined,
validateImageSourceTypeMapping: undefined,
Expand Down
8 changes: 8 additions & 0 deletions app/utils/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
import {
ProjectTypeEnum,
ProjectTypeSpecificInput,
SubGridSizeEnum,
} from '#generated/types/graphql';

export const DEFAULT_ALERT_DISMISS_DURATION = 4500;
Expand Down Expand Up @@ -156,6 +157,7 @@ export const projectTypeToKeyMap: Record<ProjectTypeEnum, keyof(ProjectTypeSpeci
[ProjectTypeEnum.Validate]: 'validate',
[ProjectTypeEnum.ValidateImage]: 'validateImage',
[ProjectTypeEnum.Street]: 'street',
[ProjectTypeEnum.Locate]: 'locate',
};

interface NumericValueOption {
Expand Down Expand Up @@ -596,3 +598,9 @@ export function formatArea(area: number) {
},
);
}

export const subgridSizeToValueMap: Record<SubGridSizeEnum, 1 | 2 | 3> = {
[SubGridSizeEnum.Size_2X2]: 1,
[SubGridSizeEnum.Size_4X4]: 2,
[SubGridSizeEnum.Size_8X8]: 3,
};
Loading
Loading