Skip to content
Open
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
48 changes: 48 additions & 0 deletions app/components/PanoramaxImagePreview/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import '@panoramax/web-viewer/build/index.css';
import '@panoramax/web-viewer';

import { useMemo } from 'react';
import {
_cs,
isDefined,
} from '@togglecorp/fujs';

import styles from './styles.module.css';

interface Props {
className?: string;
imageId: string | undefined;
url?: string | undefined | null;
}

const DEFAULT_ENDPOINT = 'https://api.panoramax.xyz/api';
Comment thread
ofr1tz marked this conversation as resolved.

function PanoramaxImagePreview({ className, imageId, url }: Props) {
const endpoint = useMemo(() => {
// NOTE: Workaround to use Metacatalog API for MapComplete Panoramax due to CORS issues.
if (!url) return DEFAULT_ENDPOINT;
return url.includes('mapcomplete') ? DEFAULT_ENDPOINT : `${url.replace(/\/+$/, '')}/api`;
}, [url]);

const PanoramaxPhotoViewer = 'pnx-photo-viewer' as React.ElementType;
return (
<div className={_cs(styles.panoramaxImagePreview, className)}>
{isDefined(imageId) && (
<PanoramaxPhotoViewer
class={styles.viewer}
endpoint={endpoint}
picture={imageId}
widgets={false}
url-parameters={false}
keyboard-shortcuts={false}
psv-options={JSON.stringify({
picturesNavigation: 'pic',
displayAnnotations: false,
})}
/>
)}
</div>
);
}

export default PanoramaxImagePreview;
9 changes: 9 additions & 0 deletions app/components/PanoramaxImagePreview/styles.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.panoramax-image-preview {
isolation: isolate;
height: 20rem;

.viewer {
width: 100%;
height: 100%;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ 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';
import {
StreetImageProviderNameEnum,
StreetProjectPropertyType,
} from '#generated/types/graphql';

interface Props {
data: StreetProjectPropertyType | undefined;
Expand Down Expand Up @@ -36,7 +39,7 @@ function StreetDetails(props: Props) {
</Container>
</ListLayout>
<Container
heading="Mapillary Image Filters"
heading="Street-level Image Filters"
headingLevel={5}
>
<ListLayout
Expand All @@ -56,7 +59,7 @@ function StreetDetails(props: Props) {
value={data.mapillaryImageFilters.creatorId}
/>
<TextOutput
label="Mapillary Organization ID"
label="Organization ID"
value={data.mapillaryImageFilters.organizationId}
/>
<TextOutput
Expand All @@ -71,7 +74,7 @@ function StreetDetails(props: Props) {
>
<TextOutput
label="Only use 360 degree panaroma images"
value={data.mapillaryImageFilters.isPano}
value={data.mapillaryImageFilters.panoOnly}
valueType="boolean"
/>
<TextOutput
Expand All @@ -80,6 +83,26 @@ function StreetDetails(props: Props) {
valueType="boolean"
/>
</ListLayout>
<ListLayout
layout="block"
spacing="sm"
>
<TextOutput
label="Image provider name"
value={data.imageProvider?.name}
valueType="text"
/>
{(
data.imageProvider?.name === StreetImageProviderNameEnum.PanoramaxCustom
&& data.imageProvider?.url
) && (
<TextOutput
label="Panoramax API URL"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this label correct? How do we know the url is for Panoramax?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's better to check the imageProvider type so that it does not break in the future.

value={data.imageProvider.url}
valueType="text"
/>
)}
</ListLayout>
</Container>
</>
);
Expand Down
101 changes: 101 additions & 0 deletions app/components/domain/StreetImageProviderInput/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import {
useCallback,
useContext,
useMemo,
} from 'react';
import {
EntriesAsList,
getErrorObject,
LeafError,
ObjectError,
} from '@togglecorp/toggle-form';

import Container from '#components/Container';
import ListLayout from '#components/ListLayout';
import RadioInput from '#components/RadioInput';
import TextInput from '#components/TextInput';
import EnumsContext from '#contexts/EnumsContext';
import { StreetImageProviderNameEnum } from '#generated/types/graphql';

import { PartialStreetImageProviderInputFields } from './schema';

interface Props {
value: PartialStreetImageProviderInputFields | undefined;
error: LeafError | ObjectError<PartialStreetImageProviderInputFields>;
setFieldValue: (
...entries: EntriesAsList<PartialStreetImageProviderInputFields>
) => void;
disabled?: boolean;
}

interface StreetProviderOption {
key: StreetImageProviderNameEnum;
label: string;
}

function providerKeySelector(option: StreetProviderOption) {
return option.key;
}

function providerLabelSelector(option: StreetProviderOption) {
return option.label;
}

function StreetImageProviderInput(props: Props) {
const {
value, error: formError, setFieldValue, disabled,
} = props;

const { streetImageProviderNameOptions } = useContext(EnumsContext);
const error = getErrorObject(formError);

const providerOptions = useMemo(() => {
if (!streetImageProviderNameOptions || streetImageProviderNameOptions.length === 0) {
return [];
}

return streetImageProviderNameOptions.map((option) => ({
key: option.key as StreetImageProviderNameEnum,
label: option.label,
}));
}, [streetImageProviderNameOptions]);

const handleChange = useCallback((newValue: StreetImageProviderNameEnum) => {
setFieldValue(newValue, 'name');
if (newValue !== StreetImageProviderNameEnum.PanoramaxCustom) {
setFieldValue(undefined, 'url');
}
}, [setFieldValue]);

return (
<Container heading="Street-level imagery provider">
<ListLayout layout="block">
<RadioInput
name="provider"
label="Image provider"
options={providerOptions}
keySelector={providerKeySelector}
labelSelector={providerLabelSelector}
value={value?.name}
onChange={handleChange}
error={error?.name}
disabled={disabled}
radioListLayout="block"
/>

{value?.name === StreetImageProviderNameEnum.PanoramaxCustom && (
<TextInput
name="url"
label="Panoramax API URL"
value={value?.url}
error={error?.url}
onChange={setFieldValue}
disabled={disabled}
/>
)}
</ListLayout>
</Container>
);
}

export default StreetImageProviderInput;
61 changes: 61 additions & 0 deletions app/components/domain/StreetImageProviderInput/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import {
addCondition,
ObjectSchema,
PartialForm,
requiredStringCondition,
} from '@togglecorp/toggle-form';

import {
StreetImageProviderInput,
StreetImageProviderNameEnum,
} from '#generated/types/graphql';
import { DeepNonNullable } from '#utils/types';

export type PartialStreetImageProviderInputFields = PartialForm<
DeepNonNullable<StreetImageProviderInput>
>;

type StreetImageProviderSchema =
ObjectSchema<PartialStreetImageProviderInputFields>;

type StreetImageProviderFields =
ReturnType<StreetImageProviderSchema['fields']>;

export const defaultStreetImageProviderValue:
PartialStreetImageProviderInputFields = {
name: StreetImageProviderNameEnum.Mapillary,
};

const streetImageProviderSchema: StreetImageProviderSchema = {
fields: (value): StreetImageProviderFields => {
const baseSchema: StreetImageProviderFields = {
name: {
required: true,
},
url: {},
};

return addCondition(
baseSchema,
value,
['name'],
['url'],
(): StreetImageProviderFields => {
if (value?.name === StreetImageProviderNameEnum.PanoramaxCustom) {
return {
url: {
required: true,
requiredValidation: requiredStringCondition,
},
};
}

return {
url: { required: false },
};
},
);
},
};

export default streetImageProviderSchema;
30 changes: 25 additions & 5 deletions app/components/domain/StreetScenarioPreview/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ import { PreviewItem } from '#components/domain/TutorialPreviewScreenSelectInput
import ListLayout from '#components/ListLayout';
import MapillaryImagePreview from '#components/MapillaryImagePreview';
import MobilePreview from '#components/MobilePreview';
import { TutorialScenarioPageCreateInput } from '#generated/types/graphql';
import PanoramaxImagePreview from '#components/PanoramaxImagePreview';
import {
StreetImageProvider,
StreetImageProviderNameEnum,
TutorialScenarioPageCreateInput,
} from '#generated/types/graphql';

import { PartialCustomOptionInputFields } from '../CustomOptionInput/schema';
import CustomOptionPreview from '../CustomOptionsPreview';
Expand All @@ -19,6 +24,7 @@ interface Props {
scenario: PartialForm<TutorialScenarioPageCreateInput> | undefined;
customOptions: PartialCustomOptionInputFields[] | undefined;
preview: PreviewItem | undefined;
imageProvider: StreetImageProvider | null | undefined;
}

function StreetScenarioPreview(props: Props) {
Expand All @@ -28,9 +34,14 @@ function StreetScenarioPreview(props: Props) {
projectInstruction,
customOptions,
preview,
imageProvider,
} = props;

const imageId = scenario?.tasks?.[0].projectTypeSpecifics?.street?.mapillaryImageId;
const isPanoramaxProvider = [
StreetImageProviderNameEnum.Panoramax,
StreetImageProviderNameEnum.PanoramaxCustom,
].includes(imageProvider?.name as StreetImageProviderNameEnum);

return (
<ListLayout
Expand All @@ -45,10 +56,19 @@ function StreetScenarioPreview(props: Props) {
popupVariant={preview?.popupVariant}
contentClassName={styles.content}
>
<MapillaryImagePreview
imageId={imageId}
className={styles.streetPreview}
/>
{imageProvider?.name === StreetImageProviderNameEnum.Mapillary && (
<MapillaryImagePreview
imageId={imageId}
className={styles.streetPreview}
/>
)}
{isPanoramaxProvider && (
<PanoramaxImagePreview
imageId={imageId}
className={styles.streetPreview}
url={imageProvider?.url}
/>
)}
<CustomOptionPreview
value={customOptions}
/>
Expand Down
7 changes: 7 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'],
streetImageProviderNameOptions: AllEnumsQuery['enums']['StreetImageProviderNameEnum'],

validateObjectSourceTypeMapping: Record<
AllEnumsQuery['enums']['ValidateObjectSourceTypeEnum'][number]['key'],
Expand Down Expand Up @@ -49,6 +50,10 @@ export interface EnumsContextProps {
AllEnumsQuery['enums']['FirebasePushStatusEnum'][number]['key'],
AllEnumsQuery['enums']['FirebasePushStatusEnum'][number]
> | undefined;
streetImageProviderNameMapping: Record<
AllEnumsQuery['enums']['StreetImageProviderNameEnum'][number]['key'],
AllEnumsQuery['enums']['StreetImageProviderNameEnum'][number]
> | undefined;
}

export const defaultAllEnumsValue: EnumsContextProps = {
Expand All @@ -61,6 +66,7 @@ export const defaultAllEnumsValue: EnumsContextProps = {
overlayLayerTypeOptions: [],
tutorialStatusOptions: [],
firebasePushStatusOptions: [],
streetImageProviderNameOptions: [],

validateObjectSourceTypeMapping: undefined,
validateImageSourceTypeMapping: undefined,
Expand All @@ -71,6 +77,7 @@ export const defaultAllEnumsValue: EnumsContextProps = {
overlayLayerTypeMapping: undefined,
tutorialStatusMapping: undefined,
firebasePushStatusMapping: undefined,
streetImageProviderNameMapping: undefined,
};

const EnumsContext = createContext<EnumsContextProps>(defaultAllEnumsValue);
Expand Down
Loading
Loading