Skip to content

Commit

Permalink
a few small improvements to get things building
Browse files Browse the repository at this point in the history
  • Loading branch information
jthrilly committed Jan 22, 2025
1 parent c882f7b commit 6d69eaa
Show file tree
Hide file tree
Showing 14 changed files with 162 additions and 111 deletions.
46 changes: 26 additions & 20 deletions lib/interviewer/containers/Interfaces/Geospatial/Geospatial.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
import { entityPrimaryKeyProperty, type NcNode } from '@codaco/shared-consts';
import {
entityPrimaryKeyProperty,
type NcNode,
type Stage,
} from '@codaco/shared-consts';
import { type Action } from '@reduxjs/toolkit';
import { Locate } from 'lucide-react';
import 'mapbox-gl/dist/mapbox-gl.css';
import { AnimatePresence, motion } from 'motion/react';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { type ThunkDispatch } from 'redux-thunk';
import { usePrompts } from '~/lib/interviewer/behaviours/withPrompt';
import CollapsablePrompts from '~/lib/interviewer/components/CollapsablePrompts';
import Node from '~/lib/interviewer/components/Node';
import { actionCreators as sessionActions } from '~/lib/interviewer/ducks/modules/session';
import usePropSelector from '~/lib/interviewer/hooks/usePropSelector';
import useReadyForNextStage from '~/lib/interviewer/hooks/useReadyForNextStage';
import { getNetworkNodesForType } from '~/lib/interviewer/selectors/interface';
import { getAssetUrlFromId } from '~/lib/interviewer/selectors/protocol';
import { type RootState } from '~/lib/interviewer/store';
import type { Protocol } from '~/lib/protocol-validation/schemas/src/8.zod';
import { ActionButton } from '~/lib/ui/components';
import Button from '~/lib/ui/components/Button';
import { usePrompts } from '../../../behaviours/withPrompt';
import CollapsablePrompts from '../../../components/CollapsablePrompts';
import Node from '../../../components/Node';
import { actionCreators as sessionActions } from '../../../ducks/modules/session';
import usePropSelector from '../../../hooks/usePropSelector';
import useReadyForNextStage from '../../../hooks/useReadyForNextStage';
import { getNetworkNodesForType } from '../../../selectors/interface';
import { getAssetUrlFromId } from '../../../selectors/protocol';
import { type RootState } from '../../../store';
import { useMapbox } from './useMapbox';

type NavDirection = 'forwards' | 'backwards';
Expand Down Expand Up @@ -50,7 +54,7 @@ type GeospatialStage = Extract<
{ type: 'Geospatial' }
>;

type GeospatialInterfaceProps = {
type GeospatialInterfaceProps = Stage & {
stage: GeospatialStage;
registerBeforeNext: (
beforeNext: (direction: NavDirection) => boolean,
Expand All @@ -73,12 +77,9 @@ export default function GeospatialInterface({
direction: null as NavDirection | null,
});

const { prompts, mapOptions } = stage;
const { promptIndex } = usePrompts();
const currentPrompt = prompts[promptIndex];
if (!currentPrompt) {
throw new Error('Prompt not found');
}
const { mapOptions } = stage;
const { promptIndex, prompt: currentPrompt } = usePrompts();

const stageNodes = usePropSelector(getNetworkNodesForType, {
stage,
}) as NcNode[];
Expand Down Expand Up @@ -113,7 +114,7 @@ export default function GeospatialInterface({
stageNodes[navState.activeIndex]?.[entityPrimaryKeyProperty] ?? '',
{},
{
[currentPrompt.variable]: value,
[currentPrompt.variable!]: value,
},
);
}
Expand Down Expand Up @@ -206,11 +207,16 @@ export default function GeospatialInterface({
<div id="map-container" className="h-full w-full" ref={mapContainerRef} />

<div className="absolute bottom-10 left-14 z-10">
<ActionButton onClick={handleResetMapZoom} icon={<Locate />} title='Reset Map' showPlusButton={false}/>
<ActionButton
onClick={handleResetMapZoom}
icon={<Locate />}
title="Reset Map"
showPlusButton={false}
/>
</div>

<CollapsablePrompts
currentPromptIndex={currentPrompt ? prompts.indexOf(currentPrompt) : -1}
currentPromptIndex={promptIndex}
dragConstraints={dragSafeRef}
>
<div className="flex flex-col items-center gap-2 pb-4">
Expand Down
56 changes: 36 additions & 20 deletions lib/interviewer/containers/Interfaces/Geospatial/useMapbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { MapMouseEvent } from 'mapbox-gl';
import mapboxgl from 'mapbox-gl';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useSelector } from 'react-redux';
import { getApiKeyAssetValue } from '~/lib/interviewer/selectors/protocol';
import { makeGetApiKeyAssetValue } from '~/lib/interviewer/selectors/protocol';
import { type MapOptions } from '~/lib/protocol-validation/schemas/src/8.zod';
import { getCSSVariableAsString } from '~/lib/ui/utils/CSSVariables';

Expand All @@ -29,11 +29,20 @@ export const useMapbox = ({
const mapRef = useRef<mapboxgl.Map | null>(null);
const [isMapLoaded, setIsMapLoaded] = useState(false);

const { center, initialZoom, tokenAssetId, dataSourceAssetId, color, targetFeatureProperty, style } = mapOptions;

const {
center,
initialZoom,
tokenAssetId,
dataSourceAssetId,
color,
targetFeatureProperty,
style,
} = mapOptions;

// get token value from asset manifest, using id
const getAccessToken = useSelector(getApiKeyAssetValue);
const accessToken = getAccessToken(tokenAssetId);
const accessToken = useSelector(
makeGetApiKeyAssetValue(tokenAssetId),
) as string;

const handleResetMapZoom = useCallback(() => {
mapRef.current?.flyTo({
Expand Down Expand Up @@ -138,7 +147,16 @@ export const useMapbox = ({
return () => {
mapRef.current?.remove();
};
}, [accessToken, center, getAssetUrl, initialZoom, color, targetFeatureProperty, dataSourceAssetId, style]);
}, [
accessToken,
center,
getAssetUrl,
initialZoom,
color,
targetFeatureProperty,
dataSourceAssetId,
style,
]);

// handle selections
useEffect(() => {
Expand Down Expand Up @@ -187,9 +205,7 @@ export const useMapbox = ({
}
};



// handle hover events
// handle hover events
let hoveredFeatureId: string | null = null;
const handleMouseMove = (e: MapMouseEvent) => {
if (!e?.features?.length) return;
Expand All @@ -198,26 +214,26 @@ export const useMapbox = ({
if (hoveredFeatureId !== null) {
mapInstance.setFeatureState(
{ source: 'geojson-data', id: hoveredFeatureId },
{ hover: false }
{ hover: false },
);
}

hoveredFeatureId = feature?.id !== undefined ? String(feature.id) : null;
if (hoveredFeatureId === null) return;
mapInstance.setFeatureState(
{ source: 'geojson-data', id: hoveredFeatureId },
{ hover: true }
{ hover: true },
);
};
const handleMouseLeave = () => {
if (hoveredFeatureId !== null) {
mapInstance.setFeatureState(
{ source: 'geojson-data', id: hoveredFeatureId },
{ hover: false }
);
}
hoveredFeatureId = null;
};
const handleMouseLeave = () => {
if (hoveredFeatureId !== null) {
mapInstance.setFeatureState(
{ source: 'geojson-data', id: hoveredFeatureId },
{ hover: false },
);
}
hoveredFeatureId = null;
};

// add event listeners to map
mapInstance.on('click', 'layerToSelect', handleMapClick);
Expand Down
10 changes: 3 additions & 7 deletions lib/interviewer/selectors/protocol.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,11 @@ export const getAssetUrlFromId = createSelector(
getAssetManifest,
(manifest) => (id) => manifest[id]?.url,
);
export const getApiKeyAssetValue = createSelector(
getAssetManifest,
(manifest) => (key) => {
console.log(manifest);
export const makeGetApiKeyAssetValue = (key) =>
createSelector(getAssetManifest, (manifest) => {
const value = manifest[key]?.value;
console.log(`getApiKeyAssetValue: key=${key}, value=${value}`);
return value;
}
);
});

export const getProtocolCodebook = createSelector(
getCurrentSessionProtocol,
Expand Down
4 changes: 2 additions & 2 deletions lib/protocol-validation/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { type Protocol } from '@codaco/shared-consts';
import { ensureError } from '~/utils/ensureError';
import { Protocol } from './schemas/src/8.zod';
import { validateLogic } from './validation/validateLogic';
import { validateSchema } from './validation/validateSchema';

Expand Down Expand Up @@ -46,4 +46,4 @@ const validateProtocol = async (
}
};

export { validateProtocol, };
export { validateProtocol };
51 changes: 43 additions & 8 deletions lib/protocol-validation/schemas/src/8.zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -468,27 +468,41 @@ const familyTreeCensusStage = baseStageSchema.extend({

const mapboxStyleOptions = [
{ label: 'Standard', value: 'mapbox://styles/mapbox/standard' },
{ label: 'Standard Satellite', value: 'mapbox://styles/mapbox/standard-satellite' },
{
label: 'Standard Satellite',
value: 'mapbox://styles/mapbox/standard-satellite',
},
{ label: 'Streets', value: 'mapbox://styles/mapbox/streets-v12' },
{ label: 'Outdoors', value: 'mapbox://styles/mapbox/outdoors-v12' },
{ label: 'Light', value: 'mapbox://styles/mapbox/light-v11' },
{ label: 'Dark', value: 'mapbox://styles/mapbox/dark-v11' },
{ label: 'Satellite', value: 'mapbox://styles/mapbox/satellite-v9' },
{ label: 'Satellite Streets', value: 'mapbox://styles/mapbox/satellite-streets-v12' },
{ label: 'Navigation Day', value: 'mapbox://styles/mapbox/navigation-day-v1' },
{ label: 'Navigation Night', value: 'mapbox://styles/mapbox/navigation-night-v1' },
{
label: 'Satellite Streets',
value: 'mapbox://styles/mapbox/satellite-streets-v12',
},
{
label: 'Navigation Day',
value: 'mapbox://styles/mapbox/navigation-day-v1',
},
{
label: 'Navigation Night',
value: 'mapbox://styles/mapbox/navigation-night-v1',
},
];

const styleOptions = z.enum(mapboxStyleOptions.map(option => option.value) as [string, ...string[]]);
const styleOptions = z.enum(
mapboxStyleOptions.map((option) => option.value) as [string, ...string[]],
);

const mapOptions = z.object({
tokenAssetId: z.string(),
style: styleOptions,
center: z.tuple([z.number(), z.number()]),
initialZoom: z
.number()
.min(0, { message: "Zoom must be at least 0" })
.max(22, { message: "Zoom must be less than or equal to 22" }),
.min(0, { message: 'Zoom must be at least 0' })
.max(22, { message: 'Zoom must be less than or equal to 22' }),
dataSourceAssetId: z.string(),
color: z.string(),
targetFeatureProperty: z.string(), // property of geojson to select
Expand Down Expand Up @@ -532,6 +546,27 @@ const stageSchema = z.discriminatedUnion('type', [
geospatialStage,
]);

const baseAssetSchema = z.object({
id: z.string(),
type: z.enum(['image', 'video', 'network', 'geojson', 'apikey']),
name: z.string(),
});

const fileAssetSchema = baseAssetSchema.extend({
type: z.enum(['image', 'video', 'network', 'geojson']),
source: z.string(),
});

const apiKeyAssetSchema = baseAssetSchema.extend({
type: z.enum(['apikey']),
value: z.string(),
});

const assetSchema = z.discriminatedUnion('type', [
fileAssetSchema,
apiKeyAssetSchema,
]);

// Main Protocol Schema
export const Protocol = z
.object({
Expand All @@ -540,7 +575,7 @@ export const Protocol = z
lastModified: z.string().datetime().optional(),
schemaVersion: z.literal(8),
codebook: codebookSchema,
assetManifest: z.record(z.any()).optional(),
assetManifest: z.record(z.string(), assetSchema).optional(),
stages: z.array(stageSchema),
})
.strict();
Expand Down
2 changes: 1 addition & 1 deletion lib/protocol-validation/scripts/validateProtocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
*
* Errors & Validation failures are written to stderr.
*/
import { type Protocol } from '@codaco/shared-consts';
import chalk from 'chalk';
import JSZip from 'jszip';
import { readFile } from 'node:fs/promises';
import { basename } from 'node:path';
import { ensureError } from '~/utils/ensureError';
import { getProtocolJson } from '~/utils/protocolImport';
import { validateProtocol } from '..';
import { Protocol } from '../schemas/src/8.zod';
import { errToString } from '../validation/helpers';

// eslint-disable-next-line no-console
Expand Down
3 changes: 2 additions & 1 deletion lib/protocol-validation/validation/Validator.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { type Protocol, type StageSubject } from '@codaco/shared-consts';
import { type StageSubject } from '@codaco/shared-consts';
import { get } from 'es-toolkit/compat';
import { type ValidationError } from '..';
import { Protocol } from '../schemas/src/8.zod';

/**
* See addValidation().
Expand Down
2 changes: 1 addition & 1 deletion lib/protocol-validation/validation/validateLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import {
type FilterRule,
type FormField,
type NcNode,
type Protocol,
type StageSubject,
type VariableDefinition,
type VariableValidation,
} from '@codaco/shared-consts';
import { get, isObject } from 'es-toolkit/compat';
import { Protocol } from '../schemas/src/8.zod';
import Validator from './Validator';
import {
duplicateId,
Expand Down
2 changes: 1 addition & 1 deletion lib/protocol-validation/validation/validateSchema.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable no-console */
import { type Protocol } from '@codaco/shared-consts';
import type { ValidateFunction } from 'ajv';
import { Protocol } from '../schemas/src/8.zod';

export const validateSchema = async (
protocol: Protocol,
Expand Down
Loading

0 comments on commit 6d69eaa

Please sign in to comment.