From 467b8c85f5d023788a33c89386346ac243b9e0d3 Mon Sep 17 00:00:00 2001 From: shreeyash07 Date: Tue, 23 May 2023 17:48:27 +0545 Subject: [PATCH 01/72] Add card component and maintain css --- .../app/components/Card/index.tsx | 43 +++++++ .../app/components/Card/styles.css | 25 ++++ .../app/components/InputSection/styles.css | 1 - .../app/views/NewTutorial/index.tsx | 111 ++++++++++++------ .../app/views/NewTutorial/styles.css | 12 +- 5 files changed, 151 insertions(+), 41 deletions(-) create mode 100644 manager-dashboard/app/components/Card/index.tsx create mode 100644 manager-dashboard/app/components/Card/styles.css diff --git a/manager-dashboard/app/components/Card/index.tsx b/manager-dashboard/app/components/Card/index.tsx new file mode 100644 index 000000000..3c13e3041 --- /dev/null +++ b/manager-dashboard/app/components/Card/index.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { _cs } from '@togglecorp/fujs'; + +import styles from './styles.css'; + +interface Props { + className?: string; + children?: React.ReactNode; + contentClassName?: string; + title?: React.ReactNode; + multiColumn?: boolean; +} + +function Card(props: Props) { + const { + className, + children, + title, + multiColumn, + contentClassName, + } = props; + + return ( +
+ {title && ( +
+ {title} +
+ )} +
+ {children} +
+
+ ); +} + +export default Card; diff --git a/manager-dashboard/app/components/Card/styles.css b/manager-dashboard/app/components/Card/styles.css new file mode 100644 index 000000000..56aaac368 --- /dev/null +++ b/manager-dashboard/app/components/Card/styles.css @@ -0,0 +1,25 @@ +.card { + border: 0.5px; + background-color: var(--color-foreground); + padding: var(--spacing); + + .title { + padding: var(--spacing-medium); + font-weight: var(--font-weight-bold); + } + + .content { + padding: var(--spacing-medium); + } + + &.multi-column { + .content { + display: flex; + + >* { + flex-basis: 0; + flex-grow: 1; + } + } + } +} \ No newline at end of file diff --git a/manager-dashboard/app/components/InputSection/styles.css b/manager-dashboard/app/components/InputSection/styles.css index ee278d18f..fde96fe6f 100644 --- a/manager-dashboard/app/components/InputSection/styles.css +++ b/manager-dashboard/app/components/InputSection/styles.css @@ -11,7 +11,6 @@ display: flex; flex-direction: column; border-radius: var(--radius-card); - background-color: var(--color-foreground); padding: var(--spacing-large); gap: var(--spacing-extra-large); } diff --git a/manager-dashboard/app/views/NewTutorial/index.tsx b/manager-dashboard/app/views/NewTutorial/index.tsx index 945de082a..8606baf23 100644 --- a/manager-dashboard/app/views/NewTutorial/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/index.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useCallback } from 'react'; import { _cs, isDefined, @@ -30,12 +30,19 @@ import { Link } from 'react-router-dom'; import UserContext from '#base/context/UserContext'; import useMountedRef from '#hooks/useMountedRef'; +import Card from '#components/Card'; import Modal from '#components/Modal'; import TextInput from '#components/TextInput'; import NumberInput from '#components/NumberInput'; import SegmentInput from '#components/SegmentInput'; import FileInput from '#components/FileInput'; import GeoJsonFileInput from '#components/GeoJsonFileInput'; +import { + Tabs, + Tab, + TabList, + TabPanel, +} from '#components/Tabs'; import JsonFileInput from '#components/JsonFileInput'; import TileServerInput, { TILE_SERVER_BING, @@ -99,6 +106,7 @@ function NewTutorial(props: Props) { setTutorialSubmissionStatus, ] = React.useState<'started' | 'imageUpload' | 'tutorialSubmit' | 'success' | 'failed' | undefined>(); + const [activeTab, setActiveTab] = React.useState(); const error = React.useMemo( () => getErrorObject(formError), [formError], @@ -210,46 +218,59 @@ function NewTutorial(props: Props) { const tileServerBVisible = value?.projectType === PROJECT_TYPE_CHANGE_DETECTION || value?.projectType === PROJECT_TYPE_COMPLETENESS; + // const handleGeoJsonFile = useCallback(( + // geoProps: PartialTutorialFormType['tutorialTasks'], + // ) => ( + // ), []); + return (
- -
- - + -
-
+
+ + +
+ + -
-
+ + -
+ + + + + + scenario 2 + + + + scenarios 2 + + +
{(value?.projectType === PROJECT_TYPE_BUILD_AREA || value?.projectType === PROJECT_TYPE_CHANGE_DETECTION diff --git a/manager-dashboard/app/views/NewTutorial/styles.css b/manager-dashboard/app/views/NewTutorial/styles.css index 6ac3932c9..11bfabd00 100644 --- a/manager-dashboard/app/views/NewTutorial/styles.css +++ b/manager-dashboard/app/views/NewTutorial/styles.css @@ -13,15 +13,21 @@ max-width: 70rem; gap: var(--spacing-large); + .card { + display: flex; + flex-direction: column; + gap: var(--spacing-extra-large); + } + .input-group { display: flex; + flex-direction: column; flex-wrap: wrap; - gap: var(--spacing-extra-large); + gap: var(--spacing-small); >* { flex-basis: 0; - flex-grow: 1; - min-width: 20rem; + width: 60% } } } From 3f771bcbd8731392e8efaa2997555a49ad097ec7 Mon Sep 17 00:00:00 2001 From: shreeyash07 Date: Tue, 23 May 2023 17:48:27 +0545 Subject: [PATCH 02/72] Add card component and maintain css --- manager-dashboard/app/views/NewTutorial/index.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/manager-dashboard/app/views/NewTutorial/index.tsx b/manager-dashboard/app/views/NewTutorial/index.tsx index 8606baf23..7792bda9a 100644 --- a/manager-dashboard/app/views/NewTutorial/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/index.tsx @@ -1,4 +1,4 @@ -import React, { useCallback } from 'react'; +import React from 'react'; import { _cs, isDefined, @@ -99,7 +99,10 @@ function NewTutorial(props: Props) { error: formError, validate, setError, - } = useForm(tutorialFormSchema, defaultTutorialFormValue); + } = useForm( + tutorialFormSchema, + { value: defaultTutorialFormValue }, + ); const [ tutorialSubmissionStatus, @@ -218,7 +221,7 @@ function NewTutorial(props: Props) { const tileServerBVisible = value?.projectType === PROJECT_TYPE_CHANGE_DETECTION || value?.projectType === PROJECT_TYPE_COMPLETENESS; - // const handleGeoJsonFile = useCallback(( + // const handleGeoJsonFile = React.useCallback(( // geoProps: PartialTutorialFormType['tutorialTasks'], // ) => ( // ), []); From 14ddbc6d765094e3b46d2d447f8ffbfcc6d029af Mon Sep 17 00:00:00 2001 From: shreeyash07 Date: Wed, 24 May 2023 18:14:44 +0545 Subject: [PATCH 03/72] Add describe scenario file --- .../app/views/NewTutorial/index.tsx | 126 ++++++++++++++++-- .../app/views/NewTutorial/styles.css | 6 + .../app/views/NewTutorial/utils.ts | 11 +- 3 files changed, 128 insertions(+), 15 deletions(-) diff --git a/manager-dashboard/app/views/NewTutorial/index.tsx b/manager-dashboard/app/views/NewTutorial/index.tsx index 7792bda9a..13c03b1e0 100644 --- a/manager-dashboard/app/views/NewTutorial/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/index.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { _cs, isDefined, + isNotDefined, } from '@togglecorp/fujs'; import { useForm, @@ -29,6 +30,7 @@ import { import { Link } from 'react-router-dom'; import UserContext from '#base/context/UserContext'; +import projectTypeOptions from '#base/configs/projectTypes'; import useMountedRef from '#hooks/useMountedRef'; import Card from '#components/Card'; import Modal from '#components/Modal'; @@ -65,8 +67,6 @@ import { } from './utils'; import styles from './styles.css'; -import projectTypeOptions from '#base/configs/projectTypes'; - const defaultTutorialFormValue: PartialTutorialFormType = { projectType: PROJECT_TYPE_BUILD_AREA, zoomLevel: 18, @@ -80,6 +80,24 @@ const defaultTutorialFormValue: PartialTutorialFormType = { }, }; +interface TutorialTask { + scenario: number; + hint: { + description: string; + icon: string; + title: string; + }, + intructions: { + description: string; + icon: string; + title: string; + }, + success: { + description: string; + icon: string; + title: string; + } +} interface Props { className?: string; } @@ -109,6 +127,7 @@ function NewTutorial(props: Props) { setTutorialSubmissionStatus, ] = React.useState<'started' | 'imageUpload' | 'tutorialSubmit' | 'success' | 'failed' | undefined>(); + const [tutorialTask, setTutorialTask] = React.useState(); const [activeTab, setActiveTab] = React.useState(); const error = React.useMemo( () => getErrorObject(formError), @@ -221,10 +240,41 @@ function NewTutorial(props: Props) { const tileServerBVisible = value?.projectType === PROJECT_TYPE_CHANGE_DETECTION || value?.projectType === PROJECT_TYPE_COMPLETENESS; - // const handleGeoJsonFile = React.useCallback(( - // geoProps: PartialTutorialFormType['tutorialTasks'], - // ) => ( - // ), []); + const handleGeoJsonFile = React.useCallback(( + geoProps: PartialTutorialFormType['tutorialTasks'], + ) => { + setFieldValue('tutorialTasks', geoProps); + const tutorialTaskArray = geoProps?.features.map((geo) => ( + { + scenario: geo.properties.screen, + hint: { + description: undefined, + icon: undefined, + title: undefined, + }, + intructions: { + description: undefined, + icon: undefined, + title: undefined, + }, + success: { + description: undefined, + icon: undefined, + title: undefined, + }, + } + )); + + const reduced = tutorialTaskArray?.reduce((acc, currItem) => { + const findValue = acc.find((item) => item.scenario === currItem.scenario); + if (isNotDefined(findValue)) { + acc.push(currItem); + } + return acc; + }, []).sort((a, b) => a.scenario - b.scenario); + + setTutorialTask(reduced); + }, [setFieldValue]); return (
@@ -286,7 +336,7 @@ function NewTutorial(props: Props) { - - scenario 2 - + {tutorialTask?.map((task) => ( + + {`Scenario ${task.scenario}`} + + ))} - - scenarios 2 - + {tutorialTask?.map((task) => ( + +
+
+ Instructions + + +
+
+ Hint + + +
+
+ Success + + +
+
+
+ ))} diff --git a/manager-dashboard/app/views/NewTutorial/styles.css b/manager-dashboard/app/views/NewTutorial/styles.css index 11bfabd00..9e203fd58 100644 --- a/manager-dashboard/app/views/NewTutorial/styles.css +++ b/manager-dashboard/app/views/NewTutorial/styles.css @@ -19,6 +19,12 @@ gap: var(--spacing-extra-large); } + .scenario { + display: flex; + flex-direction: column; + gap: var(--spacing-large); + } + .input-group { display: flex; flex-direction: column; diff --git a/manager-dashboard/app/views/NewTutorial/utils.ts b/manager-dashboard/app/views/NewTutorial/utils.ts index 4c94b89d2..dd9dd48c0 100644 --- a/manager-dashboard/app/views/NewTutorial/utils.ts +++ b/manager-dashboard/app/views/NewTutorial/utils.ts @@ -27,11 +27,18 @@ export interface TutorialFormType { name: string; tileServer: TileServer, screens?: GeoJSON.GeoJSON ; // FIXME: this is not FeatureCollection - tutorialTasks?: GeoJSON.GeoJSON; + tutorialTasks?: GeoJSON.FeatureCollection; exampleImage1: File; exampleImage2: File; projectType: ProjectType; - tileServerB?: TileServer, zoomLevel?: number; } From d96894da17d19c547b50b1e163b2c7e4cb682cb6 Mon Sep 17 00:00:00 2001 From: shreeyash07 Date: Thu, 25 May 2023 17:34:44 +0545 Subject: [PATCH 04/72] Add icons --- manager-dashboard/app/Base/styles.css | 8 + .../app/components/GeoJsonFileInput/index.tsx | 5 +- .../app/components/Heading/index.tsx | 35 ++ .../app/components/Heading/styles.module.css | 33 ++ .../app/components/Tabs/index.tsx | 24 +- .../app/views/NewTutorial/index.tsx | 297 +++++++--- .../app/views/NewTutorial/styles.css | 11 + .../app/views/NewTutorial/utils.ts | 84 ++- manager-dashboard/package.json | 2 +- manager-dashboard/yarn.lock | 509 ++---------------- node_modules/.yarn-integrity | 10 + yarn.lock | 4 + 12 files changed, 430 insertions(+), 592 deletions(-) create mode 100644 manager-dashboard/app/components/Heading/index.tsx create mode 100644 manager-dashboard/app/components/Heading/styles.module.css create mode 100644 node_modules/.yarn-integrity create mode 100644 yarn.lock diff --git a/manager-dashboard/app/Base/styles.css b/manager-dashboard/app/Base/styles.css index c599ea183..3ffb25fea 100644 --- a/manager-dashboard/app/Base/styles.css +++ b/manager-dashboard/app/Base/styles.css @@ -83,6 +83,7 @@ p { --font-size-ultra-large: 4rem; --font-weight-medium: 400; + --font-weight-semibold: 600; --font-weight-bold: 700; --opacity-disabled-element: 0.5; @@ -101,6 +102,13 @@ p { --radius-card-border: 0.25rem; --radius-badge-border: 1rem; + --line-height-none: 1; + --line-height-tight: 1.25; + --line-height-snug: 1.375; + --line-height-normal: 1.5; + --line-height-relaxed: 1.625; + --line-height-loose: 2; + --shadow-card: 0 2px 4px -2px var(--color-shadow); diff --git a/manager-dashboard/app/components/GeoJsonFileInput/index.tsx b/manager-dashboard/app/components/GeoJsonFileInput/index.tsx index acc981f40..60f8939fd 100644 --- a/manager-dashboard/app/components/GeoJsonFileInput/index.tsx +++ b/manager-dashboard/app/components/GeoJsonFileInput/index.tsx @@ -51,7 +51,10 @@ const DEFAULT_MAX_FILE_SIZE = ONE_MB; interface Props extends Omit, 'value' | 'onChange' | 'accept'> { maxFileSize?: number; - value: GeoJSON.GeoJSON | undefined | null; + value: GeoJSON.FeatureCollection< + GeoJSON.Geometry, Record + > | undefined | null; onChange: (newValue: GeoJSON.GeoJSON | undefined, name: N) => void; } diff --git a/manager-dashboard/app/components/Heading/index.tsx b/manager-dashboard/app/components/Heading/index.tsx new file mode 100644 index 000000000..2ed4b2c4e --- /dev/null +++ b/manager-dashboard/app/components/Heading/index.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import { _cs } from '@togglecorp/fujs'; + +import styles from './styles.module.css'; + +export interface Props { + className?: string; + level?: 1 | 2 | 3 | 4 | 5; + children: React.ReactNode; +} + +function Heading(props: Props) { + const { + className, + level = 3, + children, + } = props; + + const levelStyle = styles[`level${level}`]; + const HeadingTag = `h${level}` as React.ElementType; + + return ( + + {children} + + ); +} + +export default Heading; diff --git a/manager-dashboard/app/components/Heading/styles.module.css b/manager-dashboard/app/components/Heading/styles.module.css new file mode 100644 index 000000000..81a4bf68b --- /dev/null +++ b/manager-dashboard/app/components/Heading/styles.module.css @@ -0,0 +1,33 @@ +.heading { + --font-size: var(--font-size-extra-large); + --line-height: var(--line-height-snug); + display: flex; + margin: 0; + font-size: var(--font-size); + font-weight: var(--font-weight-semibold); + + &.level1 { + --font-size: var(--font-size-super-large); + --line-height: var(--line-height-none); + } + + &.level2 { + --font-size: var(--font-size-extra-large); + --line-height: var(--line-height-tight); + } + + &.level3 { + --font-size: var(--font-size-large); + --line-height: var(--line-height-snug); + } + + &.level4 { + --font-size: var(--font-size-medium); + --line-height: var(--line-height-normal); + } + + &.level5 { + --font-size: var(--font-size-small); + --line-height: var(--line-height-normal); + } +} diff --git a/manager-dashboard/app/components/Tabs/index.tsx b/manager-dashboard/app/components/Tabs/index.tsx index 7d31b31e1..e7bcadf4f 100644 --- a/manager-dashboard/app/components/Tabs/index.tsx +++ b/manager-dashboard/app/components/Tabs/index.tsx @@ -6,13 +6,13 @@ import styles from './styles.css'; type TabKey = string; -export interface TabContextProps { - activeTab: T; - setActiveTab: (key: T) => void; +export interface TabContextProps { + activeTab: TabKey | undefined; + setActiveTab: (key: TabKey | undefined) => void; } -const TabContext = React.createContext>({ - activeTab: undefined, +const TabContext = React.createContext({ + activeTab: '', setActiveTab: () => { // eslint-disable-next-line no-console console.warn('setActiveTab called before it was initialized'); @@ -23,7 +23,7 @@ export interface TabProps extends Omit, 'onClick'>{ name: T; } -export function Tab(props: TabProps) { +export function Tab(props: TabProps) { const { activeTab, setActiveTab, @@ -102,13 +102,13 @@ export function TabPanel(props: TabPanelProps) { ); } -export interface Props { +export interface Props { children: React.ReactNode; - value: T; - onChange: (key: T) => void; + value: TabKey; + onChange: (key: TabKey) => void; } -export function Tabs(props: Props) { +export function Tabs(props: Props) { const { children, value, @@ -118,8 +118,8 @@ export function Tabs(props: Props) { const contextValue = React.useMemo(() => ({ // Note: following cast is required since we do not have any other method // to provide template in the context type - activeTab: value as unknown as T, - setActiveTab: onChange as unknown as (key: T) => void, + activeTab: value, + setActiveTab: onChange as (key: TabKey | undefined) => void, }), [value, onChange]); return ( diff --git a/manager-dashboard/app/views/NewTutorial/index.tsx b/manager-dashboard/app/views/NewTutorial/index.tsx index 13c03b1e0..146d9770c 100644 --- a/manager-dashboard/app/views/NewTutorial/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/index.tsx @@ -2,13 +2,17 @@ import React from 'react'; import { _cs, isDefined, - isNotDefined, + unique, } from '@togglecorp/fujs'; import { useForm, getErrorObject, createSubmitHandler, analyzeErrors, + useFormArray, + useFormObject, + PartialForm, + SetValueArg, } from '@togglecorp/toggle-form'; import { getStorage, @@ -39,6 +43,7 @@ import NumberInput from '#components/NumberInput'; import SegmentInput from '#components/SegmentInput'; import FileInput from '#components/FileInput'; import GeoJsonFileInput from '#components/GeoJsonFileInput'; +import Heading from '#components/Heading'; import { Tabs, Tab, @@ -66,6 +71,7 @@ import { PartialTutorialFormType, } from './utils'; import styles from './styles.css'; +import SelectInput from '#components/SelectInput'; const defaultTutorialFormValue: PartialTutorialFormType = { projectType: PROJECT_TYPE_BUILD_AREA, @@ -83,25 +89,184 @@ const defaultTutorialFormValue: PartialTutorialFormType = { interface TutorialTask { scenario: number; hint: { - description: string; - icon: string; - title: string; + description?: string; + icon?: string; + title?: string; }, intructions: { - description: string; - icon: string; - title: string; + description?: string; + icon?: string; + title?: string; }, success: { - description: string; - icon: string; - title: string; + description?: string; + icon?: string; + title?: string; } } + +interface IconOptions { + key: string; + label: string; +} interface Props { className?: string; } +type ScenarioType = NonNullable + +const iconOptions: IconOptions[] = [ + { + key: 'swipe-left', + label: 'Swipe Left', + }, + { + key: 'tap-1', + label: 'Tap 1', + }, + { + key: 'tap-2', + label: 'Tap 2', + }, + { + key: 'tap-3', + label: 'Tap 3', + }, + { + key: 'check', + label: 'Check', + }, +]; + +const defaultScenarioTabsValue: PartialForm = { + scenario: '', + hint: { + description: '', + icon: '', + title: '', + }, + instruction: { + description: '', + icon: '', + title: '', + }, + success: { + description: '', + icon: '', + title: '', + }, + +}; +interface ScenarioTabsProps { + value: PartialForm[number], + onChange: (value: SetValueArg>, index: number) => void; + index: number, + } + +function ScenarioTabs(scenarioProps: ScenarioTabsProps) { + const { + value, + onChange, + index, + } = scenarioProps; + + const onFieldChange = useFormObject(index, onChange, defaultScenarioTabsValue); + + return ( + +
+ + Instructions + +
+
+ + +
+ d.key} + labelSelector={(d: IconOptions) => d.label} + onChange={onFieldChange} + /> +
+ + Hint + +
+
+ + +
+ d.key} + labelSelector={(d: IconOptions) => d.label} + onChange={onFieldChange} + /> +
+ + Success + +
+
+ + +
+ d.key} + labelSelector={(d: IconOptions) => d.label} + onChange={onFieldChange} + /> +
+
+
+ ); +} + function NewTutorial(props: Props) { const { className, @@ -117,18 +282,16 @@ function NewTutorial(props: Props) { error: formError, validate, setError, - } = useForm( - tutorialFormSchema, - { value: defaultTutorialFormValue }, - ); + } = useForm(tutorialFormSchema, { + value: defaultTutorialFormValue, + }); const [ tutorialSubmissionStatus, setTutorialSubmissionStatus, ] = React.useState<'started' | 'imageUpload' | 'tutorialSubmit' | 'success' | 'failed' | undefined>(); - const [tutorialTask, setTutorialTask] = React.useState(); - const [activeTab, setActiveTab] = React.useState(); + const [activeTab, setActiveTab] = React.useState(''); const error = React.useMemo( () => getErrorObject(formError), [formError], @@ -226,6 +389,10 @@ function NewTutorial(props: Props) { [validate, setError, handleFormSubmission], ); + const { + setValue: onScenarioFormChange, + } = useFormArray('screens', setFieldValue); + const hasErrors = React.useMemo( () => analyzeErrors(error), [error], @@ -244,7 +411,9 @@ function NewTutorial(props: Props) { geoProps: PartialTutorialFormType['tutorialTasks'], ) => { setFieldValue('tutorialTasks', geoProps); - const tutorialTaskArray = geoProps?.features.map((geo) => ( + const uniqueArray = unique(geoProps.features, ((geo) => geo?.properties.screen)); + const sorted = uniqueArray.sort((a, b) => a.properties?.screen - b.properties.screen); + const tutorialTaskArray = sorted.map((geo) => ( { scenario: geo.properties.screen, hint: { @@ -264,18 +433,11 @@ function NewTutorial(props: Props) { }, } )); - - const reduced = tutorialTaskArray?.reduce((acc, currItem) => { - const findValue = acc.find((item) => item.scenario === currItem.scenario); - if (isNotDefined(findValue)) { - acc.push(currItem); - } - return acc; - }, []).sort((a, b) => a.scenario - b.scenario); - - setTutorialTask(reduced); + setFieldValue('screens', tutorialTaskArray); }, [setFieldValue]); + console.log(value.tutorialTasks); + return (
@@ -373,64 +535,29 @@ function NewTutorial(props: Props) { value={activeTab} onChange={setActiveTab} > - - {tutorialTask?.map((task) => ( - - {`Scenario ${task.scenario}`} - - ))} - - {tutorialTask?.map((task) => ( - -
-
- Instructions - - -
-
- Hint - - -
-
- Success - - -
-
-
- ))} + {value.screens?.length ? ( + <> + + {value.screens?.map((task) => ( + + {`Scenario ${task.scenario}`} + + ))} + + {value.screens?.map((task, index) => ( + + ))} + + ) : ( +
No Scenarios
+ )} diff --git a/manager-dashboard/app/views/NewTutorial/styles.css b/manager-dashboard/app/views/NewTutorial/styles.css index 9e203fd58..367c3c177 100644 --- a/manager-dashboard/app/views/NewTutorial/styles.css +++ b/manager-dashboard/app/views/NewTutorial/styles.css @@ -23,6 +23,17 @@ display: flex; flex-direction: column; gap: var(--spacing-large); + + .scenario-form { + display: flex; + gap: var(--spacing-medium); + + .scenario-form-content { + display: flex; + flex-basis: 80%; + flex-direction: column; + } + } } .input-group { diff --git a/manager-dashboard/app/views/NewTutorial/utils.ts b/manager-dashboard/app/views/NewTutorial/utils.ts index dd9dd48c0..ff64c629f 100644 --- a/manager-dashboard/app/views/NewTutorial/utils.ts +++ b/manager-dashboard/app/views/NewTutorial/utils.ts @@ -1,12 +1,11 @@ import { ObjectSchema, PartialForm, - requiredCondition, requiredStringCondition, - forceNullType, greaterThanOrEqualToCondition, lessThanOrEqualToCondition, integerCondition, + nullValue, } from '@togglecorp/toggle-form'; import { TileServer, @@ -25,15 +24,35 @@ import { export interface TutorialFormType { lookFor: string; name: string; - tileServer: TileServer, - screens?: GeoJSON.GeoJSON ; // FIXME: this is not FeatureCollection + tileServer: TileServer; + screens?: { + scenario: string; + hint: { + description: string; + icon: string; + title: string; + }; + instruction: { + description: string; + icon: string; + title: string; + }; + success: { + description: string; + icon: string; + title: string; + }; + }[]; tutorialTasks?: GeoJSON.FeatureCollection; exampleImage1: File; @@ -58,31 +77,44 @@ type TutorialFormSchemaFields = ReturnType; export const tutorialFormSchema: TutorialFormSchema = { fields: (value): TutorialFormSchemaFields => { const baseSchema: TutorialFormSchemaFields = { - projectType: [requiredCondition], - lookFor: [requiredStringCondition, getNoMoreThanNCharacterCondition(25)], - name: [requiredStringCondition, getNoMoreThanNCharacterCondition(1000)], + projectType: { + required: true, + }, + lookFor: { + required: true, + requiredValidation: requiredStringCondition, + validations: [getNoMoreThanNCharacterCondition(25)], + }, + name: { + + required: true, + requiredValidation: requiredStringCondition, + validations: [getNoMoreThanNCharacterCondition(1000)], + }, tileServer: { fields: tileServerFieldsSchema, }, - screens: [requiredCondition], + screens: { required: true }, // FIXME: add validation for tutorialTasks (geojson) - tutorialTasks: [requiredCondition], - exampleImage1: [requiredCondition], - exampleImage2: [requiredCondition], + tutorialTasks: { required: true }, + exampleImage1: { required: true }, + exampleImage2: { required: true }, - tileServerB: [forceNullType], - zoomLevel: [forceNullType], + tileServerB: { forceValue: nullValue }, + zoomLevel: { forceValue: nullValue }, }; if (value?.projectType === PROJECT_TYPE_BUILD_AREA) { return { ...baseSchema, - zoomLevel: [ - requiredCondition, - greaterThanOrEqualToCondition(14), - lessThanOrEqualToCondition(22), - integerCondition, - ], + zoomLevel: { + required: true, + validations: [ + greaterThanOrEqualToCondition(14), + lessThanOrEqualToCondition(22), + integerCondition, + ], + }, }; } @@ -90,12 +122,14 @@ export const tutorialFormSchema: TutorialFormSchema = { || value?.projectType === PROJECT_TYPE_COMPLETENESS) { return { ...baseSchema, - zoomLevel: [ - requiredCondition, - greaterThanOrEqualToCondition(14), - lessThanOrEqualToCondition(22), - integerCondition, - ], + zoomLevel: { + required: true, + validations: [ + greaterThanOrEqualToCondition(14), + lessThanOrEqualToCondition(22), + integerCondition, + ], + }, tileServerB: { fields: tileServerFieldsSchema, }, diff --git a/manager-dashboard/package.json b/manager-dashboard/package.json index dd433e347..f4f758cfc 100644 --- a/manager-dashboard/package.json +++ b/manager-dashboard/package.json @@ -39,7 +39,7 @@ "@sentry/react": "^6.9.0", "@sentry/tracing": "^6.9.0", "@togglecorp/fujs": "^1.9.2", - "@togglecorp/toggle-form": "^1.2.1", + "@togglecorp/toggle-form": "^2.0.2", "@turf/area": "^6.5.0", "@turf/invariant": "^6.5.0", "apollo-link": "^1.2.14", diff --git a/manager-dashboard/yarn.lock b/manager-dashboard/yarn.lock index f55aae387..11617e234 100644 --- a/manager-dashboard/yarn.lock +++ b/manager-dashboard/yarn.lock @@ -292,7 +292,7 @@ dependencies: "@babel/types" "^7.16.0" -"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.16.0": +"@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.16.0": version "7.16.0" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.16.0.tgz#90538e60b672ecf1b448f5f4f5433d37e79a3ec3" integrity sha512-kkH7sWzKPq0xt3H1n+ghb4xEMP8k0U7XV3kkB+ZGy69kDk2ySFW1qPi06sjKzFY3t1j6XbJSqr4mF9L7CYVyhg== @@ -1224,7 +1224,7 @@ "@babel/helper-validator-option" "^7.14.5" "@babel/plugin-transform-typescript" "^7.16.1" -"@babel/runtime-corejs3@^7.10.2", "@babel/runtime-corejs3@^7.11.2": +"@babel/runtime-corejs3@^7.10.2": version "7.16.5" resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.16.5.tgz#9057d879720c136193f0440bc400088212a74894" integrity sha512-F1pMwvTiUNSAM8mc45kccMQxj31x3y3P+tA/X8hKNWp3/hUsxdGxZ3D3H8JIkxtfA8qGkaBTKvcmvStaYseAFw== @@ -1232,13 +1232,20 @@ core-js-pure "^3.19.0" regenerator-runtime "^0.13.4" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.0", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.14.0", "@babel/runtime@^7.16.3", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.7.7", "@babel/runtime@^7.8.4": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.0", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.16.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.7", "@babel/runtime@^7.8.4": version "7.16.5" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.5.tgz#7f3e34bf8bdbbadf03fbb7b1ea0d929569c9487a" integrity sha512-TXWihFIS3Pyv5hzR7j6ihmeLkZfrXGxAr5UfSl8CHf+6q/wpiYDkUau0czckpYG8QmnCIuPpdLtuA9VmuGGyMA== dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.19.0": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.5.tgz#8492dddda9644ae3bda3b45eabe87382caee7200" + integrity sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q== + dependencies: + regenerator-runtime "^0.13.11" + "@babel/template@^7.16.0": version "7.16.0" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.0.tgz#d16a35ebf4cd74e202083356fab21dd89363ddd6" @@ -1344,108 +1351,6 @@ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.6.tgz#d5e0706cf8c6acd8c6032f8d54070af261bbbb2f" integrity sha512-ws57AidsDvREKrZKYffXddNkyaF14iHNHm8VQnZH6t99E8gczjNN0GpvcGny0imC80yQ0tHz1xVUKk/KFQSUyA== -"@emotion/cache@^10.0.27": - version "10.0.29" - resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-10.0.29.tgz#87e7e64f412c060102d589fe7c6dc042e6f9d1e0" - integrity sha512-fU2VtSVlHiF27empSbxi1O2JFdNWZO+2NFHfwO0pxgTep6Xa3uGb+3pVKfLww2l/IBGLNEZl5Xf/++A4wAYDYQ== - dependencies: - "@emotion/sheet" "0.9.4" - "@emotion/stylis" "0.8.5" - "@emotion/utils" "0.11.3" - "@emotion/weak-memoize" "0.2.5" - -"@emotion/core@^10.1.1": - version "10.3.1" - resolved "https://registry.yarnpkg.com/@emotion/core/-/core-10.3.1.tgz#4021b6d8b33b3304d48b0bb478485e7d7421c69d" - integrity sha512-447aUEjPIm0MnE6QYIaFz9VQOHSXf4Iu6EWOIqq11EAPqinkSZmfymPTmlOE3QjLv846lH4JVZBUOtwGbuQoww== - dependencies: - "@babel/runtime" "^7.5.5" - "@emotion/cache" "^10.0.27" - "@emotion/css" "^10.0.27" - "@emotion/serialize" "^0.11.15" - "@emotion/sheet" "0.9.4" - "@emotion/utils" "0.11.3" - -"@emotion/css@^10.0.27": - version "10.0.27" - resolved "https://registry.yarnpkg.com/@emotion/css/-/css-10.0.27.tgz#3a7458198fbbebb53b01b2b87f64e5e21241e14c" - integrity sha512-6wZjsvYeBhyZQYNrGoR5yPMYbMBNEnanDrqmsqS1mzDm1cOTu12shvl2j4QHNS36UaTE0USIJawCH9C8oW34Zw== - dependencies: - "@emotion/serialize" "^0.11.15" - "@emotion/utils" "0.11.3" - babel-plugin-emotion "^10.0.27" - -"@emotion/hash@0.8.0": - version "0.8.0" - resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413" - integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow== - -"@emotion/is-prop-valid@0.8.8", "@emotion/is-prop-valid@^0.8.6": - version "0.8.8" - resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz#db28b1c4368a259b60a97311d6a952d4fd01ac1a" - integrity sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA== - dependencies: - "@emotion/memoize" "0.7.4" - -"@emotion/memoize@0.7.4": - version "0.7.4" - resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.4.tgz#19bf0f5af19149111c40d98bb0cf82119f5d9eeb" - integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw== - -"@emotion/serialize@^0.11.15", "@emotion/serialize@^0.11.16": - version "0.11.16" - resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-0.11.16.tgz#dee05f9e96ad2fb25a5206b6d759b2d1ed3379ad" - integrity sha512-G3J4o8by0VRrO+PFeSc3js2myYNOXVJ3Ya+RGVxnshRYgsvErfAOglKAiy1Eo1vhzxqtUvjCyS5gtewzkmvSSg== - dependencies: - "@emotion/hash" "0.8.0" - "@emotion/memoize" "0.7.4" - "@emotion/unitless" "0.7.5" - "@emotion/utils" "0.11.3" - csstype "^2.5.7" - -"@emotion/sheet@0.9.4": - version "0.9.4" - resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-0.9.4.tgz#894374bea39ec30f489bbfc3438192b9774d32e5" - integrity sha512-zM9PFmgVSqBw4zL101Q0HrBVTGmpAxFZH/pYx/cjJT5advXguvcgjHFTCaIO3enL/xr89vK2bh0Mfyj9aa0ANA== - -"@emotion/styled-base@^10.3.0": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@emotion/styled-base/-/styled-base-10.3.0.tgz#9aa2c946100f78b47316e4bc6048321afa6d4e36" - integrity sha512-PBRqsVKR7QRNkmfH78hTSSwHWcwDpecH9W6heujWAcyp2wdz/64PP73s7fWS1dIPm8/Exc8JAzYS8dEWXjv60w== - dependencies: - "@babel/runtime" "^7.5.5" - "@emotion/is-prop-valid" "0.8.8" - "@emotion/serialize" "^0.11.15" - "@emotion/utils" "0.11.3" - -"@emotion/styled@^10.0.27": - version "10.3.0" - resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-10.3.0.tgz#8ee959bf75730789abb5f67f7c3ded0c30aec876" - integrity sha512-GgcUpXBBEU5ido+/p/mCT2/Xx+Oqmp9JzQRuC+a4lYM4i4LBBn/dWvc0rQ19N9ObA8/T4NWMrPNe79kMBDJqoQ== - dependencies: - "@emotion/styled-base" "^10.3.0" - babel-plugin-emotion "^10.0.27" - -"@emotion/stylis@0.8.5": - version "0.8.5" - resolved "https://registry.yarnpkg.com/@emotion/stylis/-/stylis-0.8.5.tgz#deacb389bd6ee77d1e7fcaccce9e16c5c7e78e04" - integrity sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ== - -"@emotion/unitless@0.7.5": - version "0.7.5" - resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed" - integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg== - -"@emotion/utils@0.11.3": - version "0.11.3" - resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-0.11.3.tgz#a759863867befa7e583400d322652a3f44820924" - integrity sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw== - -"@emotion/weak-memoize@0.2.5": - version "0.2.5" - resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46" - integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA== - "@endemolshinegroup/cosmiconfig-typescript-loader@3.0.2": version "3.0.2" resolved "https://registry.yarnpkg.com/@endemolshinegroup/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-3.0.2.tgz#eea4635828dde372838b0909693ebd9aafeec22d" @@ -3006,180 +2911,6 @@ dependencies: "@sinonjs/commons" "^1.7.0" -"@storybook/addons@6.4.9": - version "6.4.9" - resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-6.4.9.tgz#43b5dabf6781d863fcec0a0b293c236b4d5d4433" - integrity sha512-y+oiN2zd+pbRWwkf6aQj4tPDFn+rQkrv7fiVoMxsYub+kKyZ3CNOuTSJH+A1A+eBL6DmzocChUyO6jvZFuh6Dg== - dependencies: - "@storybook/api" "6.4.9" - "@storybook/channels" "6.4.9" - "@storybook/client-logger" "6.4.9" - "@storybook/core-events" "6.4.9" - "@storybook/csf" "0.0.2--canary.87bc651.0" - "@storybook/router" "6.4.9" - "@storybook/theming" "6.4.9" - "@types/webpack-env" "^1.16.0" - core-js "^3.8.2" - global "^4.4.0" - regenerator-runtime "^0.13.7" - -"@storybook/api@6.4.9": - version "6.4.9" - resolved "https://registry.yarnpkg.com/@storybook/api/-/api-6.4.9.tgz#6187d08658629580f0a583f2069d55b34964b34a" - integrity sha512-U+YKcDQg8xal9sE5eSMXB9vcqk8fD1pSyewyAjjbsW5hV0B3L3i4u7z/EAD9Ujbnor+Cvxq+XGvp+Qnc5Gd40A== - dependencies: - "@storybook/channels" "6.4.9" - "@storybook/client-logger" "6.4.9" - "@storybook/core-events" "6.4.9" - "@storybook/csf" "0.0.2--canary.87bc651.0" - "@storybook/router" "6.4.9" - "@storybook/semver" "^7.3.2" - "@storybook/theming" "6.4.9" - core-js "^3.8.2" - fast-deep-equal "^3.1.3" - global "^4.4.0" - lodash "^4.17.21" - memoizerific "^1.11.3" - regenerator-runtime "^0.13.7" - store2 "^2.12.0" - telejson "^5.3.2" - ts-dedent "^2.0.0" - util-deprecate "^1.0.2" - -"@storybook/channel-postmessage@6.4.9": - version "6.4.9" - resolved "https://registry.yarnpkg.com/@storybook/channel-postmessage/-/channel-postmessage-6.4.9.tgz#b20b7d66f0f2a8ba39fe9002f3a3dc16d9e1f681" - integrity sha512-0Oif4e6/oORv4oc2tHhIRts9faE/ID9BETn4uqIUWSl2CX1wYpKYDm04rEg3M6WvSzsi+6fzoSFvkr9xC5Ns2w== - dependencies: - "@storybook/channels" "6.4.9" - "@storybook/client-logger" "6.4.9" - "@storybook/core-events" "6.4.9" - core-js "^3.8.2" - global "^4.4.0" - qs "^6.10.0" - telejson "^5.3.2" - -"@storybook/channels@6.4.9": - version "6.4.9" - resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-6.4.9.tgz#132c574d3fb2e6aaa9c52312c592794699b9d8ec" - integrity sha512-DNW1qDg+1WFS2aMdGh658WJXh8xBXliO5KAn0786DKcWCsKjfsPPQg/QCHczHK0+s5SZyzQT5aOBb4kTRHELQA== - dependencies: - core-js "^3.8.2" - ts-dedent "^2.0.0" - util-deprecate "^1.0.2" - -"@storybook/client-api@^6.0.21": - version "6.4.9" - resolved "https://registry.yarnpkg.com/@storybook/client-api/-/client-api-6.4.9.tgz#e3d90c66356d6f53f8ceb4f31753f670f704fde0" - integrity sha512-1IljlTr+ea2pIr6oiPhygORtccOdEb7SqaVzWDfLCHOhUnJ2Ka5UY9ADqDa35jvSSdRdynfk9Yl5/msY0yY1yg== - dependencies: - "@storybook/addons" "6.4.9" - "@storybook/channel-postmessage" "6.4.9" - "@storybook/channels" "6.4.9" - "@storybook/client-logger" "6.4.9" - "@storybook/core-events" "6.4.9" - "@storybook/csf" "0.0.2--canary.87bc651.0" - "@storybook/store" "6.4.9" - "@types/qs" "^6.9.5" - "@types/webpack-env" "^1.16.0" - core-js "^3.8.2" - fast-deep-equal "^3.1.3" - global "^4.4.0" - lodash "^4.17.21" - memoizerific "^1.11.3" - qs "^6.10.0" - regenerator-runtime "^0.13.7" - store2 "^2.12.0" - synchronous-promise "^2.0.15" - ts-dedent "^2.0.0" - util-deprecate "^1.0.2" - -"@storybook/client-logger@6.4.9": - version "6.4.9" - resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-6.4.9.tgz#ef6af30fac861fea69c8917120ed06b4c2f0b54e" - integrity sha512-BVagmmHcuKDZ/XROADfN3tiolaDW2qG0iLmDhyV1gONnbGE6X5Qm19Jt2VYu3LvjKF1zMPSWm4mz7HtgdwKbuQ== - dependencies: - core-js "^3.8.2" - global "^4.4.0" - -"@storybook/core-events@6.4.9": - version "6.4.9" - resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-6.4.9.tgz#7febedb8d263fbd6e4a69badbfcdce0101e6f782" - integrity sha512-YhU2zJr6wzvh5naYYuy/0UKNJ/SaXu73sIr0Tx60ur3bL08XkRg7eZ9vBhNBTlAa35oZqI0iiGCh0ljiX7yEVQ== - dependencies: - core-js "^3.8.2" - -"@storybook/csf@0.0.2--canary.87bc651.0": - version "0.0.2--canary.87bc651.0" - resolved "https://registry.yarnpkg.com/@storybook/csf/-/csf-0.0.2--canary.87bc651.0.tgz#c7b99b3a344117ef67b10137b6477a3d2750cf44" - integrity sha512-ajk1Uxa+rBpFQHKrCcTmJyQBXZ5slfwHVEaKlkuFaW77it8RgbPJp/ccna3sgoi8oZ7FkkOyvv1Ve4SmwFqRqw== - dependencies: - lodash "^4.17.15" - -"@storybook/router@6.4.9": - version "6.4.9" - resolved "https://registry.yarnpkg.com/@storybook/router/-/router-6.4.9.tgz#7cc3f85494f4e14d38925e2802145df69a071201" - integrity sha512-GT2KtVHo/mBjxDBFB5ZtVJVf8vC+3p5kRlQC4jao68caVp7H24ikPOkcY54VnQwwe4A1aXpGbJXUyTisEPFlhQ== - dependencies: - "@storybook/client-logger" "6.4.9" - core-js "^3.8.2" - fast-deep-equal "^3.1.3" - global "^4.4.0" - history "5.0.0" - lodash "^4.17.21" - memoizerific "^1.11.3" - qs "^6.10.0" - react-router "^6.0.0" - react-router-dom "^6.0.0" - ts-dedent "^2.0.0" - -"@storybook/semver@^7.3.2": - version "7.3.2" - resolved "https://registry.yarnpkg.com/@storybook/semver/-/semver-7.3.2.tgz#f3b9c44a1c9a0b933c04e66d0048fcf2fa10dac0" - integrity sha512-SWeszlsiPsMI0Ps0jVNtH64cI5c0UF3f7KgjVKJoNP30crQ6wUSddY2hsdeczZXEKVJGEn50Q60flcGsQGIcrg== - dependencies: - core-js "^3.6.5" - find-up "^4.1.0" - -"@storybook/store@6.4.9": - version "6.4.9" - resolved "https://registry.yarnpkg.com/@storybook/store/-/store-6.4.9.tgz#613c6f13271276837c6a603a16199d2abf90153e" - integrity sha512-H30KfiM2XyGMJcLaOepCEUsU7S3C/f7t46s6Nhw0lc5w/6HTQc2jGV3GgG3lUAUAzEQoxmmu61w3N2a6eyRzmg== - dependencies: - "@storybook/addons" "6.4.9" - "@storybook/client-logger" "6.4.9" - "@storybook/core-events" "6.4.9" - "@storybook/csf" "0.0.2--canary.87bc651.0" - core-js "^3.8.2" - fast-deep-equal "^3.1.3" - global "^4.4.0" - lodash "^4.17.21" - memoizerific "^1.11.3" - regenerator-runtime "^0.13.7" - slash "^3.0.0" - stable "^0.1.8" - synchronous-promise "^2.0.15" - ts-dedent "^2.0.0" - util-deprecate "^1.0.2" - -"@storybook/theming@6.4.9": - version "6.4.9" - resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-6.4.9.tgz#8ece44007500b9a592e71eca693fbeac90803b0d" - integrity sha512-Do6GH6nKjxfnBg6djcIYAjss5FW9SRKASKxLYxX2RyWJBpz0m/8GfcGcRyORy0yFTk6jByA3Hs+WFH3GnEbWkw== - dependencies: - "@emotion/core" "^10.1.1" - "@emotion/is-prop-valid" "^0.8.6" - "@emotion/styled" "^10.0.27" - "@storybook/client-logger" "6.4.9" - core-js "^3.8.2" - deep-object-diff "^1.1.0" - emotion-theming "^10.0.27" - global "^4.4.0" - memoizerific "^1.11.3" - polished "^4.0.5" - resolve-from "^5.0.0" - ts-dedent "^2.0.0" - "@stylelint/postcss-css-in-js@^0.37.2": version "0.37.2" resolved "https://registry.yarnpkg.com/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.37.2.tgz#7e5a84ad181f4234a2480803422a47b8749af3d2" @@ -3212,21 +2943,27 @@ dependencies: defer-to-connect "^1.0.1" -"@togglecorp/fujs@^1.9.2", "@togglecorp/fujs@^1.9.2-beta.0": +"@togglecorp/fujs@^1.9.2": version "1.9.2" resolved "https://registry.yarnpkg.com/@togglecorp/fujs/-/fujs-1.9.2.tgz#72fad13bd21eb04ecd078b50baaa68e2badbb248" integrity sha512-dH0mmqtty32bmQ18MEFHf+f2NwYsIDjn6A4N0NO5q9LyYnNnpnc+7tgeDan/fGXUXD7pXIBc400baGffaVUBaw== dependencies: "@babel/runtime" "^7.7.7" -"@togglecorp/toggle-form@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@togglecorp/toggle-form/-/toggle-form-1.2.1.tgz#43145c3e61bf910916edb73fd735eb2dd5cdbef6" - integrity sha512-YzjbiC2+E6aZOb2T57bkhcTG7Iq03dn53Yw/Ve2k/rBY6BNLLrFJaePbfAXe5on7rO+57/RVkqpO46FTFAjfRw== +"@togglecorp/fujs@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@togglecorp/fujs/-/fujs-2.0.0.tgz#b1834326a173e568a226d956271b1b532b5f1f30" + integrity sha512-Fx5SF9kusbf35uT2WYlI6gdqqSL/e3JN4urAyB2O+jPz1VkG2I85VRqFxGjrg4m/ZZH+HRyGgLhSDn2V8LNdjA== dependencies: - "@babel/runtime-corejs3" "^7.11.2" - "@storybook/client-api" "^6.0.21" - "@togglecorp/fujs" "^1.9.2-beta.0" + "@babel/runtime" "^7.19.0" + +"@togglecorp/toggle-form@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@togglecorp/toggle-form/-/toggle-form-2.0.2.tgz#c347b0e756479bcf0b478a4dea763b0c35644a48" + integrity sha512-Q14PsF0KL0i0NBrPPCJ38zWFtBih2FQvMmSJqVDbkYCdwIIE/cmNKwGh/bayTsEHPlCbIGkoEvx1Ak9hXEL2Cg== + dependencies: + "@babel/runtime" "^7.19.0" + "@togglecorp/fujs" "^2.0.0" "@tootallnate/once@1": version "1.1.2" @@ -3383,11 +3120,6 @@ dependencies: "@types/node" "*" -"@types/is-function@^1.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@types/is-function/-/is-function-1.0.1.tgz#2d024eace950c836d9e3335a66b97960ae41d022" - integrity sha512-A79HEEiwXTFtfY+Bcbo58M2GRYzCr9itHWzbzHVFNEYCcoU/MMGwYYf721gBrnhpj1s6RGVVha/IgNFnR0Iw/Q== - "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": version "2.0.4" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44" @@ -3501,11 +3233,6 @@ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11" integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ== -"@types/qs@^6.9.5": - version "6.9.7" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" - integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== - "@types/react-dom@^17.0.9": version "17.0.11" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.11.tgz#e1eadc3c5e86bdb5f7684e00274ae228e7bcc466" @@ -3574,11 +3301,6 @@ resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d" integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ== -"@types/webpack-env@^1.16.0": - version "1.16.3" - resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.16.3.tgz#b776327a73e561b71e7881d0cd6d34a1424db86a" - integrity sha512-9gtOPPkfyNoEqCQgx4qJKkuNm/x0R2hKR7fdl7zvTJyHnIisuE/LfvXOsYWL0o3qq6uiBnKZNNNzi3l0y/X+xw== - "@types/websocket@1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@types/websocket/-/websocket-1.0.2.tgz#d2855c6a312b7da73ed16ba6781815bf30c6187a" @@ -4376,22 +4098,6 @@ babel-plugin-dynamic-import-node@^2.3.3: dependencies: object.assign "^4.1.0" -babel-plugin-emotion@^10.0.27: - version "10.2.2" - resolved "https://registry.yarnpkg.com/babel-plugin-emotion/-/babel-plugin-emotion-10.2.2.tgz#a1fe3503cff80abfd0bdda14abd2e8e57a79d17d" - integrity sha512-SMSkGoqTbTyUTDeuVuPIWifPdUGkTk1Kf9BWRiXIOIcuyMfsdp2EjeiiFvOzX8NOBvEh/ypKYvUh2rkgAJMCLA== - dependencies: - "@babel/helper-module-imports" "^7.0.0" - "@emotion/hash" "0.8.0" - "@emotion/memoize" "0.7.4" - "@emotion/serialize" "^0.11.16" - babel-plugin-macros "^2.0.0" - babel-plugin-syntax-jsx "^6.18.0" - convert-source-map "^1.5.0" - escape-string-regexp "^1.0.5" - find-root "^1.1.0" - source-map "^0.5.7" - babel-plugin-graphql-tag@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/babel-plugin-graphql-tag/-/babel-plugin-graphql-tag-3.3.0.tgz#77d8ce04fb8f2ca56bce9af0ca7d5ad2b91f53bc" @@ -4423,15 +4129,6 @@ babel-plugin-jest-hoist@^27.5.1: "@types/babel__core" "^7.0.0" "@types/babel__traverse" "^7.0.6" -babel-plugin-macros@^2.0.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz#0f958a7cc6556b1e65344465d99111a1e5e10138" - integrity sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg== - dependencies: - "@babel/runtime" "^7.7.2" - cosmiconfig "^6.0.0" - resolve "^1.12.0" - babel-plugin-module-resolver@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/babel-plugin-module-resolver/-/babel-plugin-module-resolver-4.1.0.tgz#22a4f32f7441727ec1fbf4967b863e1e3e9f33e2" @@ -4482,11 +4179,6 @@ babel-plugin-polyfill-regenerator@^0.3.0: dependencies: "@babel/helper-define-polyfill-provider" "^0.3.0" -babel-plugin-syntax-jsx@^6.18.0: - version "6.18.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946" - integrity sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY= - babel-plugin-syntax-trailing-function-commas@^7.0.0-beta.0: version "7.0.0-beta.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-7.0.0-beta.0.tgz#aa213c1435e2bffeb6fca842287ef534ad05d5cf" @@ -5270,7 +4962,7 @@ content-type@~1.0.4: resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== -convert-source-map@^1.4.0, convert-source-map@^1.5.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: +convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: version "1.8.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== @@ -5305,7 +4997,7 @@ core-js-pure@^3.19.0: resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.20.1.tgz#f7a2c62f98de83e4da8fca7b78846d3a2f542145" integrity sha512-yeNNr3L9cEBwNy6vhhIJ0nko7fE7uFO6PgawcacGt2VWep4WqQx0RiqlkgSP7kqUMC1IKdfO9qPeWXcUheHLVQ== -core-js@3, core-js@^3.6.5, core-js@^3.8.2: +core-js@3: version "3.20.1" resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.20.1.tgz#eb1598047b7813572f1dc24b7c6a95528c99eef3" integrity sha512-btdpStYFQScnNVQ5slVcr858KP0YWYjV16eGJQw8Gg7CWtu/2qNvIM3qVRIR3n1pK2R9NNOrTevbvAYxajwEjg== @@ -5338,17 +5030,6 @@ cosmiconfig@7.0.0: path-type "^4.0.0" yaml "^1.10.0" -cosmiconfig@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-6.0.0.tgz#da4fee853c52f6b1e6935f41c1a2fc50bd4a9982" - integrity sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg== - dependencies: - "@types/parse-json" "^4.0.0" - import-fresh "^3.1.0" - parse-json "^5.0.0" - path-type "^4.0.0" - yaml "^1.7.2" - cosmiconfig@^7.0.0: version "7.0.1" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.1.tgz#714d756522cace867867ccb4474c5d01bbae5d6d" @@ -5568,11 +5249,6 @@ cssstyle@^2.3.0: dependencies: cssom "~0.3.6" -csstype@^2.5.7: - version "2.6.19" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.19.tgz#feeb5aae89020bb389e1f63669a5ed490e391caa" - integrity sha512-ZVxXaNy28/k3kJg0Fou5MiYpp88j7H9hLZp8PDC3jV0WFjfH5E9xHb56L0W59cPbKbcHXeP4qyT8PrHp8t6LcQ== - csstype@^3.0.2: version "3.0.10" resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.10.tgz#2ad3a7bed70f35b965707c092e5f30b327c290e5" @@ -5700,11 +5376,6 @@ deep-is@^0.1.3, deep-is@~0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== -deep-object-diff@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/deep-object-diff/-/deep-object-diff-1.1.0.tgz#d6fabf476c2ed1751fc94d5ca693d2ed8c18bc5a" - integrity sha512-b+QLs5vHgS+IoSNcUE4n9HP2NwcHj7aqnJWsjPtuG75Rh5TOaGt0OjAYInh77d5T16V5cRDC+Pw/6ZZZiETBGw== - deepmerge@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" @@ -6021,15 +5692,6 @@ emojis-list@^3.0.0: resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== -emotion-theming@^10.0.27: - version "10.3.0" - resolved "https://registry.yarnpkg.com/emotion-theming/-/emotion-theming-10.3.0.tgz#7f84d7099581d7ffe808aab5cd870e30843db72a" - integrity sha512-mXiD2Oj7N9b6+h/dC6oLf9hwxbtKHQjoIqtodEyL8CpkN4F3V4IK/BT4D0C7zSs4BBFOu4UlPJbvvBLa88SGEA== - dependencies: - "@babel/runtime" "^7.5.5" - "@emotion/weak-memoize" "0.2.5" - hoist-non-react-statics "^3.3.0" - encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" @@ -6807,11 +6469,6 @@ find-cache-dir@^3.3.1: make-dir "^3.0.2" pkg-dir "^4.1.0" -find-root@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" - integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== - find-up@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" @@ -7104,7 +6761,7 @@ global-prefix@^3.0.0: kind-of "^6.0.2" which "^1.3.1" -global@^4.4.0, global@~4.4.0: +global@~4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406" integrity sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w== @@ -7345,13 +7002,6 @@ header-case@^2.0.4: capital-case "^1.0.4" tslib "^2.0.3" -history@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/history/-/history-5.0.0.tgz#0cabbb6c4bbf835addb874f8259f6d25101efd08" - integrity sha512-3NyRMKIiFSJmIPdq7FxkNMJkQ7ZEtVblOQ38VtKaA0zZMW1Eo6Q6W8oDKEflr1kNNTItSnk4JMCO1deeSgbLLg== - dependencies: - "@babel/runtime" "^7.7.6" - history@^4.9.0: version "4.10.1" resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3" @@ -7364,14 +7014,7 @@ history@^4.9.0: tiny-warning "^1.0.0" value-equal "^1.0.1" -history@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/history/-/history-5.2.0.tgz#7cdd31cf9bac3c5d31f09c231c9928fad0007b7c" - integrity sha512-uPSF6lAJb3nSePJ43hN3eKj1dTWpN9gMod0ZssbFTIsen+WehTmEadgL+kg78xLJFdRfrrC//SavDzmRVdE+Ig== - dependencies: - "@babel/runtime" "^7.7.6" - -hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: +hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -7604,7 +7247,7 @@ import-cwd@^3.0.0: dependencies: import-from "^3.0.0" -import-fresh@^3.0.0, import-fresh@^3.1.0, import-fresh@^3.2.1: +import-fresh@^3.0.0, import-fresh@^3.2.1: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== @@ -7928,7 +7571,7 @@ is-fullwidth-code-point@^3.0.0: resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== -is-function@^1.0.1, is-function@^1.0.2: +is-function@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/is-function/-/is-function-1.0.2.tgz#4f097f30abf6efadac9833b17ca5dc03f8144e08" integrity sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ== @@ -8068,7 +7711,7 @@ is-promise@^2.1.0: resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== -is-regex@^1.0.4, is-regex@^1.1.2, is-regex@^1.1.4: +is-regex@^1.0.4, is-regex@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== @@ -8190,11 +7833,6 @@ isobject@^3.0.0, isobject@^3.0.1: resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= -isobject@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-4.0.0.tgz#3f1c9155e73b192022a80819bacd0343711697b0" - integrity sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA== - isomorphic-fetch@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz#0267b005049046d2421207215d45d6a262b8b8b4" @@ -9218,7 +8856,7 @@ lodash.without@^4.4.0: resolved "https://registry.yarnpkg.com/lodash.without/-/lodash.without-4.4.0.tgz#3cd4574a00b67bae373a94b748772640507b7aac" integrity sha1-PNRXSgC2e643OpS3SHcmQFB7eqw= -lodash@4.17.21, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.7.0, lodash@~4.17.0: +lodash@4.17.21, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.7.0, lodash@~4.17.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -9341,11 +8979,6 @@ map-obj@^4.0.0: resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.3.0.tgz#9304f906e93faae70880da102a9f1df0ea8bb05a" integrity sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ== -map-or-similar@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/map-or-similar/-/map-or-similar-1.5.0.tgz#6de2653174adfb5d9edc33c69d3e92a1b76faf08" - integrity sha1-beJlMXSt+12e3DPGnT6Sobdvrwg= - map-visit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" @@ -9396,13 +9029,6 @@ media-typer@0.3.0: resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= -memoizerific@^1.11.3: - version "1.11.3" - resolved "https://registry.yarnpkg.com/memoizerific/-/memoizerific-1.11.3.tgz#7c87a4646444c32d75438570905f2dbd1b1a805a" - integrity sha1-fIekZGREwy11Q4VwkF8tvRsagFo= - dependencies: - map-or-similar "^1.5.0" - memory-fs@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" @@ -10388,13 +10014,6 @@ pngjs@^3.0.0, pngjs@^3.3.3: resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.4.0.tgz#99ca7d725965fb655814eaf65f38f12bbdbf555f" integrity sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w== -polished@^4.0.5: - version "4.1.3" - resolved "https://registry.yarnpkg.com/polished/-/polished-4.1.3.tgz#7a3abf2972364e7d97770b827eec9a9e64002cfc" - integrity sha512-ocPAcVBUOryJEKe0z2KLd1l9EBa1r5mSwlKpExmrLzsnIzJo4axsoU9O2BjOTkDGDT4mZ0WFE5XKTlR3nLnZOA== - dependencies: - "@babel/runtime" "^7.14.0" - portfinder@^1.0.26: version "1.0.28" resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.28.tgz#67c4622852bd5374dd1dd900f779f53462fac778" @@ -11112,13 +10731,6 @@ qs@6.9.6: resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.6.tgz#26ed3c8243a431b2924aca84cc90471f35d5a0ee" integrity sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ== -qs@^6.10.0: - version "6.10.2" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.2.tgz#c1431bea37fc5b24c5bdbafa20f16bdf2a4b9ffe" - integrity sha512-mSIdjzqznWgfd4pMii7sHtaYF8rx8861hBO80SraY5GT0XQibWZWJSid0avzHGkDIZLImux2S5mXO0Hfct2QCw== - dependencies: - side-channel "^1.0.4" - querystring@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" @@ -11218,14 +10830,6 @@ react-router-dom@^5.2.0: tiny-invariant "^1.0.2" tiny-warning "^1.0.0" -react-router-dom@^6.0.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.2.1.tgz#32ec81829152fbb8a7b045bf593a22eadf019bec" - integrity sha512-I6Zax+/TH/cZMDpj3/4Fl2eaNdcvoxxHoH1tYOREsQ22OKDYofGebrNm6CTPUcvLvZm63NL/vzCYdjf9CUhqmA== - dependencies: - history "^5.2.0" - react-router "6.2.1" - react-router@5.2.1: version "5.2.1" resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.2.1.tgz#4d2e4e9d5ae9425091845b8dbc6d9d276239774d" @@ -11242,13 +10846,6 @@ react-router@5.2.1: tiny-invariant "^1.0.2" tiny-warning "^1.0.0" -react-router@6.2.1, react-router@^6.0.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.2.1.tgz#be2a97a6006ce1d9123c28934e604faef51448a3" - integrity sha512-2fG0udBtxou9lXtK97eJeET2ki5//UWfQSl1rlJ7quwe6jrktK9FCCc8dQb5QY6jAv3jua8bBQRhhDOM/kVRsg== - dependencies: - history "^5.2.0" - react@^17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" @@ -11341,7 +10938,12 @@ regenerate@^1.4.2: resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== -regenerator-runtime@^0.13.3, regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.7: +regenerator-runtime@^0.13.11: + version "0.13.11" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" + integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== + +regenerator-runtime@^0.13.3, regenerator-runtime@^0.13.4: version "0.13.9" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== @@ -11582,7 +11184,7 @@ resolve.exports@^1.1.0: resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.0.tgz#5ce842b94b05146c0e03076985d1d0e7e48c90c9" integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ== -resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.14.2, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.9.0: +resolve@^1.10.0, resolve@^1.13.1, resolve@^1.14.2, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.9.0: version "1.20.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== @@ -12104,7 +11706,7 @@ source-map-url@^0.4.0: resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56" integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw== -source-map@^0.5.0, source-map@^0.5.6, source-map@^0.5.7: +source-map@^0.5.0, source-map@^0.5.6: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= @@ -12234,11 +11836,6 @@ static-extend@^0.1.1: resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= -store2@^2.12.0: - version "2.13.1" - resolved "https://registry.yarnpkg.com/store2/-/store2-2.13.1.tgz#fae7b5bb9d35fc53dc61cd262df3abb2f6e59022" - integrity sha512-iJtHSGmNgAUx0b/MCS6ASGxb//hGrHHRgzvN+K5bvkBTN7A9RTpPSf1WSp+nPGvWCJ1jRnvY7MKnuqfoi3OEqg== - string-env-interpolation@1.0.1, string-env-interpolation@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/string-env-interpolation/-/string-env-interpolation-1.0.1.tgz#ad4397ae4ac53fe6c91d1402ad6f6a52862c7152" @@ -12623,11 +12220,6 @@ sync-fetch@0.3.0: buffer "^5.7.0" node-fetch "^2.6.1" -synchronous-promise@^2.0.15: - version "2.0.15" - resolved "https://registry.yarnpkg.com/synchronous-promise/-/synchronous-promise-2.0.15.tgz#07ca1822b9de0001f5ff73595f3d08c4f720eb8e" - integrity sha512-k8uzYIkIVwmT+TcglpdN50pS2y1BDcUnBPK9iJeGu0Pl1lOI8pD6wtzgw91Pjpe+RxtTncw32tLxs/R0yNL2Mg== - table@^6.0.9, table@^6.6.0: version "6.7.5" resolved "https://registry.yarnpkg.com/table/-/table-6.7.5.tgz#f04478c351ef3d8c7904f0e8be90a1b62417d238" @@ -12644,20 +12236,6 @@ tapable@^2.0.0, tapable@^2.1.1, tapable@^2.2.0: resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== -telejson@^5.3.2: - version "5.3.3" - resolved "https://registry.yarnpkg.com/telejson/-/telejson-5.3.3.tgz#fa8ca84543e336576d8734123876a9f02bf41d2e" - integrity sha512-PjqkJZpzEggA9TBpVtJi1LVptP7tYtXB6rEubwlHap76AMjzvOdKX41CxyaW7ahhzDU1aftXnMCx5kAPDZTQBA== - dependencies: - "@types/is-function" "^1.0.0" - global "^4.4.0" - is-function "^1.0.2" - is-regex "^1.1.2" - is-symbol "^1.0.3" - isobject "^4.0.0" - lodash "^4.17.21" - memoizerific "^1.11.3" - temp-dir@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-2.0.0.tgz#bde92b05bdfeb1516e804c9c00ad45177f31321e" @@ -12871,11 +12449,6 @@ trough@^1.0.0: resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406" integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA== -ts-dedent@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/ts-dedent/-/ts-dedent-2.2.0.tgz#39e4bd297cd036292ae2394eb3412be63f563bb5" - integrity sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ== - ts-invariant@^0.3.2: version "0.3.3" resolved "https://registry.yarnpkg.com/ts-invariant/-/ts-invariant-0.3.3.tgz#b5742b1885ecf9e29c31a750307480f045ec0b16" @@ -13941,7 +13514,7 @@ yaml-ast-parser@^0.0.43: resolved "https://registry.yarnpkg.com/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz#e8a23e6fb4c38076ab92995c5dca33f3d3d7c9bb" integrity sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A== -yaml@^1.10.0, yaml@^1.10.2, yaml@^1.7.2: +yaml@^1.10.0, yaml@^1.10.2: version "1.10.2" resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== diff --git a/node_modules/.yarn-integrity b/node_modules/.yarn-integrity new file mode 100644 index 000000000..60ee24b8d --- /dev/null +++ b/node_modules/.yarn-integrity @@ -0,0 +1,10 @@ +{ + "systemParams": "linux-x64-111", + "modulesFolders": [], + "flags": [], + "linkedModules": [], + "topLevelPatterns": [], + "lockfileEntries": {}, + "files": [], + "artifacts": {} +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 000000000..fb57ccd13 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,4 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + From f93fe771117328e874abfb46a830a1483c94aec9 Mon Sep 17 00:00:00 2001 From: shreeyash07 Date: Mon, 29 May 2023 17:40:10 +0545 Subject: [PATCH 05/72] Separate files --- .../views/NewTutorial/ScenarioInput/index.tsx | 179 ++++++++++++++ .../NewTutorial/ScenarioInput/styles.css | 16 ++ .../app/views/NewTutorial/index.tsx | 227 ++---------------- .../app/views/NewTutorial/styles.css | 17 -- .../app/views/NewTutorial/utils.ts | 109 +++++++-- 5 files changed, 306 insertions(+), 242 deletions(-) create mode 100644 manager-dashboard/app/views/NewTutorial/ScenarioInput/index.tsx create mode 100644 manager-dashboard/app/views/NewTutorial/ScenarioInput/styles.css diff --git a/manager-dashboard/app/views/NewTutorial/ScenarioInput/index.tsx b/manager-dashboard/app/views/NewTutorial/ScenarioInput/index.tsx new file mode 100644 index 000000000..dfdebb3a6 --- /dev/null +++ b/manager-dashboard/app/views/NewTutorial/ScenarioInput/index.tsx @@ -0,0 +1,179 @@ +import React from 'react'; +import { + useFormObject, + PartialForm, + SetValueArg, +} from '@togglecorp/toggle-form'; +import TextInput from '#components/TextInput'; +import Heading from '#components/Heading'; +import SelectInput from '#components/SelectInput'; +import { + TabPanel, +} from '#components/Tabs'; +import { + PartialTutorialFormType, +} from '../utils'; + +import styles from './styles.css'; + +type ScenarioType = NonNullable[number]; + +interface IconOptions { + key: string; + label: string; +} + +const iconOptions: IconOptions[] = [ + { + key: 'swipe-left', + label: 'Swipe Left', + }, + { + key: 'tap-1', + label: 'Tap 1', + }, + { + key: 'tap-2', + label: 'Tap 2', + }, + { + key: 'tap-3', + label: 'Tap 3', + }, + { + key: 'check', + label: 'Check', + }, +]; + +const defaultScenarioTabsValue: ScenarioType = { + scenario: '', + hint: { + description: '', + icon: '', + title: '', + }, + instructions: { + description: '', + icon: '', + title: '', + }, + success: { + description: '', + icon: '', + title: '', + }, +}; + +interface ScenarioTabsProps { + value: PartialForm, + onChange: (value: SetValueArg>, index: number) => void; + index: number, +} + +export default function ScenarioInput(scenarioProps: ScenarioTabsProps) { + const { + value, + onChange, + index, + } = scenarioProps; + + const onFieldChange = useFormObject(index, onChange, defaultScenarioTabsValue); + const onInstructionFieldChange = useFormObject('instructions', onFieldChange, defaultScenarioTabsValue.instructions); + const onHintFieldChange = useFormObject('hint', onFieldChange, defaultScenarioTabsValue.hint); + const onSuccessFieldChange = useFormObject('success', onFieldChange, defaultScenarioTabsValue.success); + + return ( + +
+ + Instructions + +
+
+ + +
+ d.key} + labelSelector={(d: IconOptions) => d.label} + onChange={onInstructionFieldChange} + /> +
+ + Hint + +
+
+ + +
+ d.key} + labelSelector={(d: IconOptions) => d.label} + onChange={onHintFieldChange} + /> +
+ + Success + +
+
+ + +
+ d.key} + labelSelector={(d: IconOptions) => d.label} + onChange={onSuccessFieldChange} + /> +
+
+
+ ); +} diff --git a/manager-dashboard/app/views/NewTutorial/ScenarioInput/styles.css b/manager-dashboard/app/views/NewTutorial/ScenarioInput/styles.css new file mode 100644 index 000000000..505605b80 --- /dev/null +++ b/manager-dashboard/app/views/NewTutorial/ScenarioInput/styles.css @@ -0,0 +1,16 @@ +.scenario { + display: flex; + flex-direction: column; + gap: var(--spacing-large); + + .scenario-form { + display: flex; + gap: var(--spacing-medium); + + .scenario-form-content { + display: flex; + flex-basis: 80%; + flex-direction: column; + } + } +} \ No newline at end of file diff --git a/manager-dashboard/app/views/NewTutorial/index.tsx b/manager-dashboard/app/views/NewTutorial/index.tsx index 146d9770c..8d47fc4fa 100644 --- a/manager-dashboard/app/views/NewTutorial/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/index.tsx @@ -10,9 +10,6 @@ import { createSubmitHandler, analyzeErrors, useFormArray, - useFormObject, - PartialForm, - SetValueArg, } from '@togglecorp/toggle-form'; import { getStorage, @@ -43,12 +40,10 @@ import NumberInput from '#components/NumberInput'; import SegmentInput from '#components/SegmentInput'; import FileInput from '#components/FileInput'; import GeoJsonFileInput from '#components/GeoJsonFileInput'; -import Heading from '#components/Heading'; import { Tabs, Tab, TabList, - TabPanel, } from '#components/Tabs'; import JsonFileInput from '#components/JsonFileInput'; import TileServerInput, { @@ -70,8 +65,8 @@ import { TutorialFormType, PartialTutorialFormType, } from './utils'; +import ScenarioInput from './ScenarioInput'; import styles from './styles.css'; -import SelectInput from '#components/SelectInput'; const defaultTutorialFormValue: PartialTutorialFormType = { projectType: PROJECT_TYPE_BUILD_AREA, @@ -86,187 +81,10 @@ const defaultTutorialFormValue: PartialTutorialFormType = { }, }; -interface TutorialTask { - scenario: number; - hint: { - description?: string; - icon?: string; - title?: string; - }, - intructions: { - description?: string; - icon?: string; - title?: string; - }, - success: { - description?: string; - icon?: string; - title?: string; - } -} - -interface IconOptions { - key: string; - label: string; -} interface Props { className?: string; } -type ScenarioType = NonNullable - -const iconOptions: IconOptions[] = [ - { - key: 'swipe-left', - label: 'Swipe Left', - }, - { - key: 'tap-1', - label: 'Tap 1', - }, - { - key: 'tap-2', - label: 'Tap 2', - }, - { - key: 'tap-3', - label: 'Tap 3', - }, - { - key: 'check', - label: 'Check', - }, -]; - -const defaultScenarioTabsValue: PartialForm = { - scenario: '', - hint: { - description: '', - icon: '', - title: '', - }, - instruction: { - description: '', - icon: '', - title: '', - }, - success: { - description: '', - icon: '', - title: '', - }, - -}; -interface ScenarioTabsProps { - value: PartialForm[number], - onChange: (value: SetValueArg>, index: number) => void; - index: number, - } - -function ScenarioTabs(scenarioProps: ScenarioTabsProps) { - const { - value, - onChange, - index, - } = scenarioProps; - - const onFieldChange = useFormObject(index, onChange, defaultScenarioTabsValue); - - return ( - -
- - Instructions - -
-
- - -
- d.key} - labelSelector={(d: IconOptions) => d.label} - onChange={onFieldChange} - /> -
- - Hint - -
-
- - -
- d.key} - labelSelector={(d: IconOptions) => d.label} - onChange={onFieldChange} - /> -
- - Success - -
-
- - -
- d.key} - labelSelector={(d: IconOptions) => d.label} - onChange={onFieldChange} - /> -
-
-
- ); -} - function NewTutorial(props: Props) { const { className, @@ -408,36 +226,38 @@ function NewTutorial(props: Props) { || value?.projectType === PROJECT_TYPE_COMPLETENESS; const handleGeoJsonFile = React.useCallback(( - geoProps: PartialTutorialFormType['tutorialTasks'], + geoProps: GeoJSON.GeoJSON | undefined, ) => { - setFieldValue('tutorialTasks', geoProps); - const uniqueArray = unique(geoProps.features, ((geo) => geo?.properties.screen)); - const sorted = uniqueArray.sort((a, b) => a.properties?.screen - b.properties.screen); - const tutorialTaskArray = sorted.map((geo) => ( + const tutorialTasks = geoProps as PartialTutorialFormType['tutorialTasks']; + + setFieldValue(tutorialTasks, 'tutorialTasks'); + const uniqueArray = tutorialTasks && unique( + tutorialTasks.features, ((geo) => geo?.properties.screen), + ); + const sorted = uniqueArray?.sort((a, b) => a.properties?.screen - b.properties.screen); + const tutorialTaskArray = sorted?.map((geo) => ( { - scenario: geo.properties.screen, + scenario: String(geo.properties.screen) ?? '', hint: { - description: undefined, - icon: undefined, - title: undefined, + title: '', + description: '', + icon: '', }, - intructions: { - description: undefined, - icon: undefined, - title: undefined, + instructions: { + title: '', + description: '', + icon: '', }, success: { - description: undefined, - icon: undefined, - title: undefined, + title: '', + description: '', + icon: '', }, } )); - setFieldValue('screens', tutorialTaskArray); + setFieldValue(tutorialTaskArray, 'screens'); }, [setFieldValue]); - console.log(value.tutorialTasks); - return (
@@ -548,7 +368,8 @@ function NewTutorial(props: Props) { ))} {value.screens?.map((task, index) => ( - ; + export interface TutorialFormType { lookFor: string; name: string; @@ -28,33 +43,22 @@ export interface TutorialFormType { screens?: { scenario: string; hint: { - description: string; - icon: string; - title: string; + description?: string; + icon?: string; + title?: string; }; - instruction: { - description: string; - icon: string; + instructions: { + description?: string; + icon?: string; title: string; }; success: { - description: string; - icon: string; - title: string; + description?: string; + icon?: string; + title?: string; }; }[]; - tutorialTasks?: GeoJSON.FeatureCollection; + tutorialTasks?: TutorialTasks, exampleImage1: File; exampleImage2: File; projectType: ProjectType; @@ -74,6 +78,13 @@ export type PartialTutorialFormType = PartialForm< type TutorialFormSchema = ObjectSchema; type TutorialFormSchemaFields = ReturnType; +type ScreenType = NonNullable[number]; +type ScreenSchema = ObjectSchema, PartialTutorialFormType>; +type ScreenFormSchemaFields = ReturnType; + +type ScreenFormSchema = ArraySchema, PartialTutorialFormType>; +type ScreenFormSchemaMember = ReturnType; + export const tutorialFormSchema: TutorialFormSchema = { fields: (value): TutorialFormSchemaFields => { const baseSchema: TutorialFormSchemaFields = { @@ -94,7 +105,61 @@ export const tutorialFormSchema: TutorialFormSchema = { tileServer: { fields: tileServerFieldsSchema, }, - screens: { required: true }, + screens: { + keySelector: (key) => key.scenario, + member: () => ({ + fields: () => ({ + scenario: { + required: true, + }, + hint: { + fields: () => ({ + title: { + required: true, + requiredValidation: requiredStringCondition, + validations: [getNoMoreThanNCharacterCondition(500)], + }, + description: { + required: true, + requiredValidation: requiredStringCondition, + validations: [getNoMoreThanNCharacterCondition(500)], + }, + icon: { required: true }, + }), + }, + instructions: { + fields: () => ({ + title: { + required: true, + requiredValidation: requiredStringCondition, + validations: [getNoMoreThanNCharacterCondition(500)], + }, + description: { + required: true, + requiredValidation: requiredStringCondition, + validations: [getNoMoreThanNCharacterCondition(500)], + }, + icon: { required: true }, + }), + }, + success: { + fields: () => ({ + title: { + required: true, + requiredValidation: requiredStringCondition, + validations: [getNoMoreThanNCharacterCondition(500)], + }, + hintDescription: { + required: true, + requiredValidation: requiredStringCondition, + validations: [getNoMoreThanNCharacterCondition(500)], + }, + hintIcon: { required: true }, + }), + }, + }), + }), + }, // FIXME: add validation for tutorialTasks (geojson) tutorialTasks: { required: true }, exampleImage1: { required: true }, From 4aa5a6dd58e687496fb691758143ad8aa8ab53fa Mon Sep 17 00:00:00 2001 From: shreeyash07 Date: Tue, 30 May 2023 17:28:41 +0545 Subject: [PATCH 06/72] fix type issue scenario form --- .../app/components/Tabs/styles.css | 14 +++- .../app/components/TileServerInput/index.tsx | 32 +++++-- .../NewTutorial/DescribeOptions/index.tsx | 83 +++++++++++++++++++ .../NewTutorial/DescribeOptions/styles.css | 11 +++ .../views/NewTutorial/ScenarioInput/index.tsx | 54 ++++++------ .../app/views/NewTutorial/index.tsx | 38 +++------ .../app/views/NewTutorial/styles.css | 6 ++ .../app/views/NewTutorial/utils.ts | 28 +++---- 8 files changed, 185 insertions(+), 81 deletions(-) create mode 100644 manager-dashboard/app/views/NewTutorial/DescribeOptions/index.tsx create mode 100644 manager-dashboard/app/views/NewTutorial/DescribeOptions/styles.css diff --git a/manager-dashboard/app/components/Tabs/styles.css b/manager-dashboard/app/components/Tabs/styles.css index 947908cdd..28e354241 100644 --- a/manager-dashboard/app/components/Tabs/styles.css +++ b/manager-dashboard/app/components/Tabs/styles.css @@ -1,13 +1,19 @@ .tab { + border: 1px solid rgba(0,0,0,.1); + border-bottom: none; + border-radius: 5px 5px 0 0; + background-color: #FFFFFF; + padding: var(--spacing-small); text-align: left; - + &.active { - color: var(--color-accent); + bottom: -1px; + background-color: #FBFBFF; } } .tab-list { display: flex; - gap: var(--spacing-large); - padding: var(--spacing-medium); + gap: var(--spacing-small); + border-bottom: 1px solid rgba(0,0,0,.1); } diff --git a/manager-dashboard/app/components/TileServerInput/index.tsx b/manager-dashboard/app/components/TileServerInput/index.tsx index 5038a3c8f..c74b78f9e 100644 --- a/manager-dashboard/app/components/TileServerInput/index.tsx +++ b/manager-dashboard/app/components/TileServerInput/index.tsx @@ -7,7 +7,6 @@ import { SetValueArg, ObjectSchema, requiredStringCondition, - requiredCondition, } from '@togglecorp/toggle-form'; import TextInput from '#components/TextInput'; @@ -82,19 +81,34 @@ type TileServerSchema = ObjectSchema, unknown>; type TileServerFields = ReturnType; export function tileServerFieldsSchema(value: TileServerInputType | undefined): TileServerFields { const basicFields: TileServerFields = { - name: [requiredStringCondition, getNoMoreThanNCharacterCondition(1000)], - credits: [requiredStringCondition, getNoMoreThanNCharacterCondition(1000)], + name: { + required: true, + requiredValidation: requiredStringCondition, + validations: [getNoMoreThanNCharacterCondition(1000)], + }, + credits: { + required: true, + requiredValidation: requiredStringCondition, + validations: [getNoMoreThanNCharacterCondition(1000)], + }, }; if (value?.name === TILE_SERVER_CUSTOM) { return { ...basicFields, - url: [ - requiredStringCondition, - imageryUrlCondition, - getNoMoreThanNCharacterCondition(1000), - ], - wmtsLayerName: [requiredCondition, getNoMoreThanNCharacterCondition(1000)], + url: { + required: true, + requiredValidation: requiredStringCondition, + validations: [ + getNoMoreThanNCharacterCondition(1000), + imageryUrlCondition, + ], + }, + wmtsLayerName: { + required: true, + requiredValidation: requiredStringCondition, + validations: [getNoMoreThanNCharacterCondition(1000)], + }, }; } return basicFields; diff --git a/manager-dashboard/app/views/NewTutorial/DescribeOptions/index.tsx b/manager-dashboard/app/views/NewTutorial/DescribeOptions/index.tsx new file mode 100644 index 000000000..909fb2401 --- /dev/null +++ b/manager-dashboard/app/views/NewTutorial/DescribeOptions/index.tsx @@ -0,0 +1,83 @@ +import React from 'react'; +import { MdAdd } from 'react-icons/md'; +import Button from '#components/Button'; +import Card from '#components/Card'; +import Heading from '#components/Heading'; +import Tabs, { Tab, TabList, TabPanel } from '#components/Tabs'; +import TextInput from '#components/TextInput'; + +import styles from './styles.css'; + +export default function DescribeOptions() { + const [activeOptionsTab, setActiveOptionsTab] = React.useState('option1'); + + return ( + + + Option Instructions + + + + + + Option 1 + + + Option 2 + + + +
+ + + + Reasons + + + + +
+
+
+ +
+ ); +} diff --git a/manager-dashboard/app/views/NewTutorial/DescribeOptions/styles.css b/manager-dashboard/app/views/NewTutorial/DescribeOptions/styles.css new file mode 100644 index 000000000..8a7cfd02c --- /dev/null +++ b/manager-dashboard/app/views/NewTutorial/DescribeOptions/styles.css @@ -0,0 +1,11 @@ +.options-container{ + display: flex; + flex-direction: column; + gap: var(--spacing-large); + + .option-form { + display: flex; + flex-direction: column; + gap: var(--spacing-medium); + } +} \ No newline at end of file diff --git a/manager-dashboard/app/views/NewTutorial/ScenarioInput/index.tsx b/manager-dashboard/app/views/NewTutorial/ScenarioInput/index.tsx index dfdebb3a6..8c5cd738d 100644 --- a/manager-dashboard/app/views/NewTutorial/ScenarioInput/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/ScenarioInput/index.tsx @@ -10,13 +10,27 @@ import SelectInput from '#components/SelectInput'; import { TabPanel, } from '#components/Tabs'; -import { - PartialTutorialFormType, -} from '../utils'; import styles from './styles.css'; -type ScenarioType = NonNullable[number]; +type ScenarioType = { + scenario: string; + hint: { + description: string; + icon: string; + title: string; + }; + instructions: { + description: string; + icon: string; + title: string; + }; + success: { + description: string; + icon: string; + title: string; + }; +}; interface IconOptions { key: string; @@ -46,28 +60,14 @@ const iconOptions: IconOptions[] = [ }, ]; -const defaultScenarioTabsValue: ScenarioType = { - scenario: '', - hint: { - description: '', - icon: '', - title: '', - }, - instructions: { - description: '', - icon: '', - title: '', - }, - success: { - description: '', - icon: '', - title: '', - }, +type PartialScenarioType = PartialForm; +const defaultScenarioTabsValue: PartialScenarioType = { + scenario: 'xxx', }; interface ScenarioTabsProps { - value: PartialForm, - onChange: (value: SetValueArg>, index: number) => void; + value: PartialScenarioType, + onChange: (value: SetValueArg, index: number) => void; index: number, } @@ -79,13 +79,13 @@ export default function ScenarioInput(scenarioProps: ScenarioTabsProps) { } = scenarioProps; const onFieldChange = useFormObject(index, onChange, defaultScenarioTabsValue); - const onInstructionFieldChange = useFormObject('instructions', onFieldChange, defaultScenarioTabsValue.instructions); - const onHintFieldChange = useFormObject('hint', onFieldChange, defaultScenarioTabsValue.hint); - const onSuccessFieldChange = useFormObject('success', onFieldChange, defaultScenarioTabsValue.success); + + const onInstructionFieldChange = useFormObject<'instructions', PartialScenarioType['instructions']>('instructions', onFieldChange, {}); + const onHintFieldChange = useFormObject<'hint', PartialScenarioType['hint']>('hint', onFieldChange, {}); + const onSuccessFieldChange = useFormObject<'success', PartialScenarioType['success']>('success', onFieldChange, {}); return (
diff --git a/manager-dashboard/app/views/NewTutorial/index.tsx b/manager-dashboard/app/views/NewTutorial/index.tsx index 8d47fc4fa..8f0857812 100644 --- a/manager-dashboard/app/views/NewTutorial/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/index.tsx @@ -45,7 +45,6 @@ import { Tab, TabList, } from '#components/Tabs'; -import JsonFileInput from '#components/JsonFileInput'; import TileServerInput, { TILE_SERVER_BING, tileServerDefaultCredits, @@ -66,6 +65,7 @@ import { PartialTutorialFormType, } from './utils'; import ScenarioInput from './ScenarioInput'; +import DescribeOptions from './DescribeOptions'; import styles from './styles.css'; const defaultTutorialFormValue: PartialTutorialFormType = { @@ -109,7 +109,7 @@ function NewTutorial(props: Props) { setTutorialSubmissionStatus, ] = React.useState<'started' | 'imageUpload' | 'tutorialSubmit' | 'success' | 'failed' | undefined>(); - const [activeTab, setActiveTab] = React.useState(''); + const [activeTab, setActiveTab] = React.useState('Scenario 1'); const error = React.useMemo( () => getErrorObject(formError), [formError], @@ -237,24 +237,13 @@ function NewTutorial(props: Props) { const sorted = uniqueArray?.sort((a, b) => a.properties?.screen - b.properties.screen); const tutorialTaskArray = sorted?.map((geo) => ( { - scenario: String(geo.properties.screen) ?? '', - hint: { - title: '', - description: '', - icon: '', - }, - instructions: { - title: '', - description: '', - icon: '', - }, - success: { - title: '', - description: '', - icon: '', - }, + scenario: String(geo.properties.screen), + hint: {}, + instructions: {}, + success: {}, } )); + setFieldValue(tutorialTaskArray, 'screens'); }, [setFieldValue]); @@ -306,15 +295,6 @@ function NewTutorial(props: Props) { title="Upload GeoJSON file" contentClassName={styles.inputGroup} > - + {value.projectType === 2 && ( + + )} ; type TutorialFormSchema = ObjectSchema; type TutorialFormSchemaFields = ReturnType; type ScreenType = NonNullable[number]; -type ScreenSchema = ObjectSchema, PartialTutorialFormType>; +type ScreenSchema = ObjectSchema; type ScreenFormSchemaFields = ReturnType; -type ScreenFormSchema = ArraySchema, PartialTutorialFormType>; +type ScreenFormSchema = ArraySchema; type ScreenFormSchemaMember = ReturnType; export const tutorialFormSchema: TutorialFormSchema = { @@ -107,8 +107,8 @@ export const tutorialFormSchema: TutorialFormSchema = { }, screens: { keySelector: (key) => key.scenario, - member: () => ({ - fields: () => ({ + member: (): ScreenFormSchemaMember => ({ + fields: (): ScreenFormSchemaFields => ({ scenario: { required: true, }, From 1b71a73d02098876edd5ea4c41bc153cab4ee226 Mon Sep 17 00:00:00 2001 From: shreeyash07 Date: Wed, 31 May 2023 17:25:03 +0545 Subject: [PATCH 07/72] Add schema for define options form --- .../app/components/GeoJsonFileInput/index.tsx | 5 +- .../OrganisationFormModal/index.tsx | 16 +- .../SelectInput/SearchSelectInput.tsx | 2 - .../app/components/SelectInput/index.tsx | 2 - .../components/SelectInputContainer/index.tsx | 2 +- manager-dashboard/app/views/Login/index.tsx | 21 +- .../app/views/NewProject/index.tsx | 12 +- .../app/views/NewProject/utils.ts | 309 ++++++++++++------ .../NewTutorial/DescribeOptions/index.tsx | 83 ----- .../NewTutorial/DescribeOptions/styles.css | 11 - .../views/NewTutorial/ScenarioInput/index.tsx | 18 + .../app/views/NewTutorial/index.tsx | 9 +- .../app/views/NewTutorial/utils.ts | 69 ++-- .../UserGroups/UserGroupFormModal/index.tsx | 16 +- manager-dashboard/package.json | 15 +- manager-dashboard/yarn.lock | 201 +++++++++--- 16 files changed, 481 insertions(+), 310 deletions(-) delete mode 100644 manager-dashboard/app/views/NewTutorial/DescribeOptions/index.tsx delete mode 100644 manager-dashboard/app/views/NewTutorial/DescribeOptions/styles.css diff --git a/manager-dashboard/app/components/GeoJsonFileInput/index.tsx b/manager-dashboard/app/components/GeoJsonFileInput/index.tsx index 60f8939fd..acc981f40 100644 --- a/manager-dashboard/app/components/GeoJsonFileInput/index.tsx +++ b/manager-dashboard/app/components/GeoJsonFileInput/index.tsx @@ -51,10 +51,7 @@ const DEFAULT_MAX_FILE_SIZE = ONE_MB; interface Props extends Omit, 'value' | 'onChange' | 'accept'> { maxFileSize?: number; - value: GeoJSON.FeatureCollection< - GeoJSON.Geometry, Record - > | undefined | null; + value: GeoJSON.GeoJSON | undefined | null; onChange: (newValue: GeoJSON.GeoJSON | undefined, name: N) => void; } diff --git a/manager-dashboard/app/components/OrganisationFormModal/index.tsx b/manager-dashboard/app/components/OrganisationFormModal/index.tsx index b79bdbe03..5c0b0e7f8 100644 --- a/manager-dashboard/app/components/OrganisationFormModal/index.tsx +++ b/manager-dashboard/app/components/OrganisationFormModal/index.tsx @@ -18,8 +18,8 @@ import { useForm, getErrorObject, createSubmitHandler, - requiredCondition, analyzeErrors, + requiredStringCondition, } from '@togglecorp/toggle-form'; import { MdOutlinePublishedWithChanges, @@ -51,8 +51,16 @@ const MAX_CHARS_DESCRIPTION = 100; const organisationFormSchema: OrganisationFormSchema = { fields: (): OrganisationFormSchemaFields => ({ - name: [requiredCondition, getNoMoreThanNCharacterCondition(MAX_CHARS_NAME)], - description: [getNoMoreThanNCharacterCondition(MAX_CHARS_DESCRIPTION)], + name: { + required: true, + requiredValidation: requiredStringCondition, + validations: [getNoMoreThanNCharacterCondition(MAX_CHARS_NAME)], + }, + description: { + required: true, + requiredValidation: requiredStringCondition, + validations: [getNoMoreThanNCharacterCondition(MAX_CHARS_DESCRIPTION)], + }, }), }; @@ -74,7 +82,7 @@ function OrganisationFormModal(props: Props) { value, validate, setError, - } = useForm(organisationFormSchema, defaultOrganisationFormValue); + } = useForm(organisationFormSchema, { value: defaultOrganisationFormValue }); const mountedRef = useMountedRef(); const { user } = React.useContext(UserContext); diff --git a/manager-dashboard/app/components/SelectInput/SearchSelectInput.tsx b/manager-dashboard/app/components/SelectInput/SearchSelectInput.tsx index 70347658f..edb93e1f5 100644 --- a/manager-dashboard/app/components/SelectInput/SearchSelectInput.tsx +++ b/manager-dashboard/app/components/SelectInput/SearchSelectInput.tsx @@ -37,7 +37,6 @@ type OptionKey = string | number; export type SearchSelectInputProps< T extends OptionKey, K, - // eslint-disable-next-line @typescript-eslint/ban-types O extends object, P extends Def, OMISSION extends string, @@ -87,7 +86,6 @@ const emptyList: unknown[] = []; function SearchSelectInput< T extends OptionKey, K extends string, - // eslint-disable-next-line @typescript-eslint/ban-types O extends object, P extends Def, >( diff --git a/manager-dashboard/app/components/SelectInput/index.tsx b/manager-dashboard/app/components/SelectInput/index.tsx index 0290ec96d..d5998c228 100644 --- a/manager-dashboard/app/components/SelectInput/index.tsx +++ b/manager-dashboard/app/components/SelectInput/index.tsx @@ -11,12 +11,10 @@ type Def = { containerClassName?: string }; export type SelectInputProps< T extends OptionKey, K extends string, - // eslint-disable-next-line @typescript-eslint/ban-types O extends object, P extends Def, > = SearchSelectInputProps; -// eslint-disable-next-line @typescript-eslint/ban-types function SelectInput( props: SelectInputProps, ) { diff --git a/manager-dashboard/app/components/SelectInputContainer/index.tsx b/manager-dashboard/app/components/SelectInputContainer/index.tsx index 9dd565627..794aab80b 100644 --- a/manager-dashboard/app/components/SelectInputContainer/index.tsx +++ b/manager-dashboard/app/components/SelectInputContainer/index.tsx @@ -92,7 +92,7 @@ export type SelectInputContainerProps< const emptyList: unknown[] = []; -// eslint-disable-next-line @typescript-eslint/ban-types, max-len +// eslint-disable-next-line max-len function SelectInputContainer( props: SelectInputContainerProps, ) { diff --git a/manager-dashboard/app/views/Login/index.tsx b/manager-dashboard/app/views/Login/index.tsx index 8dc6c5c13..f0d45f6b1 100644 --- a/manager-dashboard/app/views/Login/index.tsx +++ b/manager-dashboard/app/views/Login/index.tsx @@ -12,7 +12,7 @@ import { useForm, getErrorObject, createSubmitHandler, - internal, + nonFieldError, } from '@togglecorp/toggle-form'; import TextInput from '#components/TextInput'; @@ -33,8 +33,14 @@ type LoginFormSchema = ObjectSchema; type LoginFormSchemaFields = ReturnType const loginFormSchema: LoginFormSchema = { fields: (): LoginFormSchemaFields => ({ - email: [requiredStringCondition], - password: [requiredStringCondition], + email: { + required: true, + requiredValidation: requiredStringCondition, + }, + password: { + required: true, + requiredValidation: requiredStringCondition, + }, }), }; @@ -57,7 +63,7 @@ function Login(props: Props) { value, validate, setError, - } = useForm(loginFormSchema, defaultLoginFormValue); + } = useForm(loginFormSchema, { value: defaultLoginFormValue }); const error = getErrorObject(formError); const [pending, setPending] = React.useState(false); @@ -83,7 +89,6 @@ function Login(props: Props) { if (!mountedRef.current) { return; } - setErrorMessage(undefined); setPending(false); } catch (submissionError) { // eslint-disable-next-line no-console @@ -118,7 +123,7 @@ function Login(props: Props) { setError((prevError) => ({ ...getErrorObject(prevError), - [internal]: 'Failed to authenticate', + [nonFieldError]: 'Failed to authenticate', })); setPending(false); @@ -168,9 +173,9 @@ function Login(props: Props) { type="password" disabled={pending} /> - {error?.[internal] && ( + {error?.[nonFieldError] && (
- {error?.[internal]} + {error?.[nonFieldError]}
)}
diff --git a/manager-dashboard/app/views/NewProject/index.tsx b/manager-dashboard/app/views/NewProject/index.tsx index a8a808da5..6d1c35ae7 100644 --- a/manager-dashboard/app/views/NewProject/index.tsx +++ b/manager-dashboard/app/views/NewProject/index.tsx @@ -8,7 +8,7 @@ import { getErrorObject, createSubmitHandler, analyzeErrors, - internal, + nonFieldError, } from '@togglecorp/toggle-form'; import { getStorage, @@ -115,7 +115,9 @@ function NewProject(props: Props) { validate, setError, setValue, - } = useForm(projectFormSchema, defaultProjectFormValue); + } = useForm(projectFormSchema, { + value: defaultProjectFormValue, + }); const [testPending, setTestPending] = React.useState(false); const [geometryDescription, setGeometryDescription] = React.useState(); @@ -245,7 +247,7 @@ function NewProject(props: Props) { if (!userId) { setError((err) => ({ ...getErrorObject(err), - [internal]: 'Cannot submit form because user is not defined', + [nonFieldError]: 'Cannot submit form because user is not defined', })); setProjectSubmissionStatus('failed'); return; @@ -351,7 +353,7 @@ function NewProject(props: Props) { console.error(submissionError); setError((err) => ({ ...getErrorObject(err), - [internal]: 'Some error occurred', + [nonFieldError]: 'Some error occurred', })); setProjectSubmissionStatus('failed'); } @@ -468,7 +470,7 @@ function NewProject(props: Props) { {value?.inputType === PROJECT_INPUT_TYPE_UPLOAD && ( { - const baseSchema: ProjectFormSchemaFields = { - projectTopic: [requiredStringCondition, getNoMoreThanNCharacterCondition(50)], - projectType: [requiredCondition], - projectRegion: [requiredStringCondition, getNoMoreThanNCharacterCondition(50)], - projectNumber: [requiredCondition, integerCondition, greaterThanCondition(0)], - requestingOrganisation: [requiredStringCondition], - name: [requiredStringCondition], - visibility: [requiredCondition], - lookFor: [getNoMoreThanNCharacterCondition(25)], - projectDetails: [requiredStringCondition, getNoMoreThanNCharacterCondition(10000)], - tutorialId: [requiredCondition], - projectImage: [requiredCondition], - verificationNumber: [ - requiredCondition, - greaterThanOrEqualToCondition(3), - lessThanOrEqualToCondition(10000), - integerCondition, - ], - groupSize: [ - requiredCondition, - greaterThanOrEqualToCondition(10), - lessThanOrEqualToCondition(250), - integerCondition, - ], + let baseSchema: ProjectFormSchemaFields = { + projectTopic: { + required: true, + requiredValidation: requiredStringCondition, + validations: [getNoMoreThanNCharacterCondition(50)], + }, + projectType: { + required: true, + }, + projectRegion: { + required: true, + requiredValidation: requiredStringCondition, + validations: [getNoMoreThanNCharacterCondition(50)], + }, + projectNumber: { + required: true, + requiredValidation: integerCondition, + validations: [greaterThanCondition(0)], + }, + requestingOrganisation: { + required: true, + requiredValidation: requiredStringCondition, + }, + name: { + required: true, + requiredValidation: requiredStringCondition, + }, + visibility: { + required: true, + }, + lookFor: { + validations: [getNoMoreThanNCharacterCondition(25)], + }, + projectDetails: { + requiredValidation: requiredStringCondition, + validations: [getNoMoreThanNCharacterCondition(10000)], + }, + tutorialId: { + required: true, + }, + projectImage: { + required: true, + }, + verificationNumber: { + required: true, + requiredValidation: integerCondition, + validations: [ + greaterThanOrEqualToCondition(3), + lessThanOrEqualToCondition(10000), + ], + }, + groupSize: { + required: true, + requiredValidation: integerCondition, + validations: [ + greaterThanOrEqualToCondition(10), + lessThanOrEqualToCondition(250), + ], + }, tileServer: { fields: tileServerFieldsSchema, }, - maxTasksPerUser: [ - greaterThanCondition(0), - integerCondition, - ], - - zoomLevel: [forceNullType], - geometry: [forceNullType], - filter: [forceNullType], - filterText: [forceNullType], - TMId: [forceNullType], - tileServerB: [forceNullType], + maxTasksPerUser: { + requiredValidation: integerCondition, + validations: [greaterThanCondition(0)], + }, }; - if (value?.projectType === PROJECT_TYPE_BUILD_AREA) { - return { - ...baseSchema, - zoomLevel: [ - requiredCondition, - greaterThanOrEqualToCondition(14), - lessThanOrEqualToCondition(22), - integerCondition, - ], - geometry: [ - requiredCondition, - validGeometryCondition, - ], - }; - } - - if (value?.projectType === PROJECT_TYPE_FOOTPRINT) { - return { - ...baseSchema, - inputType: [requiredCondition], - filter: (value?.inputType === PROJECT_INPUT_TYPE_TASKING_MANAGER_ID - || value?.inputType === PROJECT_INPUT_TYPE_UPLOAD) - ? [requiredCondition] - : [forceNullType], - filterText: ( - value?.inputType === PROJECT_INPUT_TYPE_TASKING_MANAGER_ID - || value?.inputType === PROJECT_INPUT_TYPE_UPLOAD - ) && value?.filter === FILTER_OTHERS - ? [requiredStringCondition, getNoMoreThanNCharacterCondition(1000)] - : [forceNullType], - // FIXME: geometry type is either string or object - // update validation - geometry: (value?.inputType === PROJECT_INPUT_TYPE_LINK - || value?.inputType === PROJECT_INPUT_TYPE_UPLOAD) - ? [requiredCondition, validGeometryCondition] - : [forceNullType], - // FIXME: number string condition - TMId: value?.inputType === PROJECT_INPUT_TYPE_TASKING_MANAGER_ID - ? [requiredStringCondition, getNoMoreThanNCharacterCondition(1000)] - : [forceNullType], - }; - } - - if (value?.projectType === PROJECT_TYPE_CHANGE_DETECTION - || value?.projectType === PROJECT_TYPE_COMPLETENESS) { - return { - ...baseSchema, - zoomLevel: [ - requiredCondition, - greaterThanOrEqualToCondition(14), - lessThanOrEqualToCondition(22), - integerCondition, - ], - geometry: [ - requiredCondition, - validGeometryCondition, - ], - tileServerB: { - fields: tileServerFieldsSchema, - }, - }; - } + baseSchema = addCondition( + baseSchema, + value, + ['projectType'], + ['zoomLevel', 'geometry'], + (v) => { + const projectType = v?.projectType; + if ( + projectType === PROJECT_TYPE_BUILD_AREA + || projectType === PROJECT_TYPE_CHANGE_DETECTION + || projectType === PROJECT_TYPE_COMPLETENESS + ) { + return { + zoomLevel: { + required: true, + validations: [ + greaterThanOrEqualToCondition(14), + lessThanOrEqualToCondition(22), + integerCondition, + ], + }, + geometry: { + required: true, + validations: [validGeometryCondition], + }, + }; + } + return { + zoomLevel: { forceValue: nullValue }, + geometry: { forceValue: nullValue }, + }; + }, + ); + + baseSchema = addCondition( + baseSchema, + value, + ['projectType'], + ['tileServerB'], + (v) => { + const projectType = v?.projectType; + if ( + projectType === PROJECT_TYPE_CHANGE_DETECTION + || projectType === PROJECT_TYPE_COMPLETENESS + ) { + return { + tileServerB: { + fields: tileServerFieldsSchema, + }, + }; + } + return { + tileServerB: { forceValue: nullValue }, + }; + }, + ); + + baseSchema = addCondition( + baseSchema, + value, + ['projectType'], + ['inputType'], + (v) => { + const projectType = v?.projectType; + if (projectType === PROJECT_TYPE_FOOTPRINT) { + return { + inputType: { required: true }, + }; + } + return { + inputType: { forceValue: nullValue }, + }; + }, + ); + + baseSchema = addCondition( + baseSchema, + value, + ['projectType', 'inputType'], + ['filter', 'geometry', 'TMId'], + (v) => { + const projectType = v?.projectType; + const inputType = v?.inputType; + return { + filter: ( + inputType === PROJECT_INPUT_TYPE_TASKING_MANAGER_ID + || inputType === PROJECT_INPUT_TYPE_UPLOAD + ) && projectType === PROJECT_TYPE_FOOTPRINT + ? { required: true } + : { forceValue: nullValue }, + // FIXME: geometry type is either string or object update validation + geometry: ( + inputType === PROJECT_INPUT_TYPE_LINK + || inputType === PROJECT_INPUT_TYPE_UPLOAD + ) && projectType === PROJECT_TYPE_FOOTPRINT + ? { required: true, validations: [validGeometryCondition] } + : { forceValue: nullValue }, + // FIXME: number string condition + TMId: ( + inputType === PROJECT_INPUT_TYPE_TASKING_MANAGER_ID + ) && projectType === PROJECT_TYPE_FOOTPRINT + ? { + required: true, + requiredValidation: requiredStringCondition, + validations: [getNoMoreThanNCharacterCondition(1000)], + } + : { forceValue: nullValue }, + }; + }, + ); + + baseSchema = addCondition( + baseSchema, + value, + ['projectType', 'inputType', 'filter'], + ['filterText'], + (v) => { + const projectType = v?.projectType; + const inputType = v?.inputType; + const filter = v?.filter; + + if ( + filter === FILTER_OTHERS + && projectType === PROJECT_TYPE_FOOTPRINT + && ( + inputType === PROJECT_INPUT_TYPE_TASKING_MANAGER_ID + || inputType === PROJECT_INPUT_TYPE_UPLOAD + ) + ) { + return { + filterText: { + required: true, + requiredValidation: requiredStringCondition, + validations: [getNoMoreThanNCharacterCondition(1000)], + }, + }; + } + return { + filterText: { forceValue: nullValue }, + }; + }, + ); return baseSchema; }, - fieldDependencies: () => ({ - geometry: ['zoomLevel'], - }), }; export function generateProjectName( diff --git a/manager-dashboard/app/views/NewTutorial/DescribeOptions/index.tsx b/manager-dashboard/app/views/NewTutorial/DescribeOptions/index.tsx deleted file mode 100644 index 909fb2401..000000000 --- a/manager-dashboard/app/views/NewTutorial/DescribeOptions/index.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import React from 'react'; -import { MdAdd } from 'react-icons/md'; -import Button from '#components/Button'; -import Card from '#components/Card'; -import Heading from '#components/Heading'; -import Tabs, { Tab, TabList, TabPanel } from '#components/Tabs'; -import TextInput from '#components/TextInput'; - -import styles from './styles.css'; - -export default function DescribeOptions() { - const [activeOptionsTab, setActiveOptionsTab] = React.useState('option1'); - - return ( - - - Option Instructions - - - - - - Option 1 - - - Option 2 - - - -
- - - - Reasons - - - - -
-
-
- -
- ); -} diff --git a/manager-dashboard/app/views/NewTutorial/DescribeOptions/styles.css b/manager-dashboard/app/views/NewTutorial/DescribeOptions/styles.css deleted file mode 100644 index 8a7cfd02c..000000000 --- a/manager-dashboard/app/views/NewTutorial/DescribeOptions/styles.css +++ /dev/null @@ -1,11 +0,0 @@ -.options-container{ - display: flex; - flex-direction: column; - gap: var(--spacing-large); - - .option-form { - display: flex; - flex-direction: column; - gap: var(--spacing-medium); - } -} \ No newline at end of file diff --git a/manager-dashboard/app/views/NewTutorial/ScenarioInput/index.tsx b/manager-dashboard/app/views/NewTutorial/ScenarioInput/index.tsx index 8c5cd738d..c96197242 100644 --- a/manager-dashboard/app/views/NewTutorial/ScenarioInput/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/ScenarioInput/index.tsx @@ -3,6 +3,8 @@ import { useFormObject, PartialForm, SetValueArg, + Error, + getErrorObject, } from '@togglecorp/toggle-form'; import TextInput from '#components/TextInput'; import Heading from '#components/Heading'; @@ -69,6 +71,7 @@ interface ScenarioTabsProps { value: PartialScenarioType, onChange: (value: SetValueArg, index: number) => void; index: number, + error: Error | undefined; } export default function ScenarioInput(scenarioProps: ScenarioTabsProps) { @@ -76,6 +79,7 @@ export default function ScenarioInput(scenarioProps: ScenarioTabsProps) { value, onChange, index, + error: riskyError, } = scenarioProps; const onFieldChange = useFormObject(index, onChange, defaultScenarioTabsValue); @@ -83,6 +87,11 @@ export default function ScenarioInput(scenarioProps: ScenarioTabsProps) { const onInstructionFieldChange = useFormObject<'instructions', PartialScenarioType['instructions']>('instructions', onFieldChange, {}); const onHintFieldChange = useFormObject<'hint', PartialScenarioType['hint']>('hint', onFieldChange, {}); const onSuccessFieldChange = useFormObject<'success', PartialScenarioType['success']>('success', onFieldChange, {}); + const error = getErrorObject(riskyError); + + const instructionsError = getErrorObject(error?.instructions); + const hintError = getErrorObject(error?.hint); + const successError = getErrorObject(error?.success); return (
d.key} labelSelector={(d: IconOptions) => d.label} onChange={onInstructionFieldChange} + error={instructionsError?.icon} />
@@ -127,12 +139,14 @@ export default function ScenarioInput(scenarioProps: ScenarioTabsProps) { value={value.hint?.title} label="Title" onChange={onHintFieldChange} + error={hintError?.title} />
d.key} labelSelector={(d: IconOptions) => d.label} onChange={onHintFieldChange} + error={hintError?.icon} />
@@ -155,12 +170,14 @@ export default function ScenarioInput(scenarioProps: ScenarioTabsProps) { value={value.success?.title} label="Title" onChange={onSuccessFieldChange} + error={successError?.title} />
d.key} labelSelector={(d: IconOptions) => d.label} onChange={onSuccessFieldChange} + error={successError?.icon} />
diff --git a/manager-dashboard/app/views/NewTutorial/index.tsx b/manager-dashboard/app/views/NewTutorial/index.tsx index 8f0857812..a30ddaf75 100644 --- a/manager-dashboard/app/views/NewTutorial/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/index.tsx @@ -65,7 +65,6 @@ import { PartialTutorialFormType, } from './utils'; import ScenarioInput from './ScenarioInput'; -import DescribeOptions from './DescribeOptions'; import styles from './styles.css'; const defaultTutorialFormValue: PartialTutorialFormType = { @@ -114,6 +113,10 @@ function NewTutorial(props: Props) { () => getErrorObject(formError), [formError], ); + const scenarioError = React.useMemo( + () => getErrorObject(error?.screens), + [error?.screens], + ); const handleFormSubmission = React.useCallback(( finalValuesFromProps: PartialTutorialFormType, @@ -328,9 +331,6 @@ function NewTutorial(props: Props) { disabled={submissionPending} /> - {value.projectType === 2 && ( - - )} ))} diff --git a/manager-dashboard/app/views/NewTutorial/utils.ts b/manager-dashboard/app/views/NewTutorial/utils.ts index 0781370c5..00de09073 100644 --- a/manager-dashboard/app/views/NewTutorial/utils.ts +++ b/manager-dashboard/app/views/NewTutorial/utils.ts @@ -7,6 +7,7 @@ import { integerCondition, nullValue, ArraySchema, + addCondition, } from '@togglecorp/toggle-form'; import { TileServer, @@ -87,7 +88,7 @@ type ScreenFormSchemaMember = ReturnType; export const tutorialFormSchema: TutorialFormSchema = { fields: (value): TutorialFormSchemaFields => { - const baseSchema: TutorialFormSchemaFields = { + let baseSchema: TutorialFormSchemaFields = { projectType: { required: true, }, @@ -149,12 +150,12 @@ export const tutorialFormSchema: TutorialFormSchema = { requiredValidation: requiredStringCondition, validations: [getNoMoreThanNCharacterCondition(500)], }, - hintDescription: { + description: { required: true, requiredValidation: requiredStringCondition, validations: [getNoMoreThanNCharacterCondition(500)], }, - hintIcon: { required: true }, + icon: { required: true }, }), }, }), @@ -164,29 +165,13 @@ export const tutorialFormSchema: TutorialFormSchema = { tutorialTasks: { required: true }, exampleImage1: { required: true }, exampleImage2: { required: true }, - - tileServerB: { forceValue: nullValue }, - zoomLevel: { forceValue: nullValue }, }; - - if (value?.projectType === PROJECT_TYPE_BUILD_AREA) { - return { - ...baseSchema, - zoomLevel: { - required: true, - validations: [ - greaterThanOrEqualToCondition(14), - lessThanOrEqualToCondition(22), - integerCondition, - ], - }, - }; - } - - if (value?.projectType === PROJECT_TYPE_CHANGE_DETECTION - || value?.projectType === PROJECT_TYPE_COMPLETENESS) { - return { - ...baseSchema, + baseSchema = addCondition( + baseSchema, + value, + ['projectType'], + ['zoomLevel'], + (v) => (v?.projectType === PROJECT_TYPE_BUILD_AREA ? { zoomLevel: { required: true, validations: [ @@ -195,12 +180,34 @@ export const tutorialFormSchema: TutorialFormSchema = { integerCondition, ], }, - tileServerB: { - fields: tileServerFieldsSchema, - }, - }; - } - + } : { + zoomLevel: { forceValue: nullValue }, + }), + ); + baseSchema = addCondition( + baseSchema, + value, + ['projectType'], + ['zoomLevel', 'tileServerB'], + (v) => (v?.projectType === PROJECT_TYPE_CHANGE_DETECTION + || v?.projectType === PROJECT_TYPE_COMPLETENESS + ? { + zoomLevel: { + required: true, + validations: [ + greaterThanOrEqualToCondition(14), + lessThanOrEqualToCondition(22), + integerCondition, + ], + }, + tileServerB: { + fields: tileServerFieldsSchema, + }, + } : { + zoomLevel: { forceValue: nullValue }, + tileServerB: { forceValue: nullValue }, + }), + ); return baseSchema; }, }; diff --git a/manager-dashboard/app/views/UserGroups/UserGroupFormModal/index.tsx b/manager-dashboard/app/views/UserGroups/UserGroupFormModal/index.tsx index c71ee44af..016da91bf 100644 --- a/manager-dashboard/app/views/UserGroups/UserGroupFormModal/index.tsx +++ b/manager-dashboard/app/views/UserGroups/UserGroupFormModal/index.tsx @@ -18,8 +18,8 @@ import { useForm, getErrorObject, createSubmitHandler, - requiredCondition, analyzeErrors, + requiredStringCondition, } from '@togglecorp/toggle-form'; import { MdOutlinePublishedWithChanges, @@ -51,8 +51,16 @@ const MAX_CHARS_DESCRIPTION = 100; const userGroupFormSchema: UserGroupFormSchema = { fields: (): UserGroupFormSchemaFields => ({ - name: [requiredCondition, getNoMoreThanNCharacterCondition(MAX_CHARS_NAME)], - description: [getNoMoreThanNCharacterCondition(MAX_CHARS_DESCRIPTION)], + name: { + required: true, + requiredValidation: requiredStringCondition, + validations: [getNoMoreThanNCharacterCondition(MAX_CHARS_NAME)], + }, + description: { + required: true, + requiredValidation: requiredStringCondition, + validations: [getNoMoreThanNCharacterCondition(MAX_CHARS_DESCRIPTION)], + }, }), }; @@ -75,7 +83,7 @@ function UserGroupFormModal(props: Props) { value, validate, setError, - } = useForm(userGroupFormSchema, defaultUserGroupFormValue); + } = useForm(userGroupFormSchema, { value: defaultUserGroupFormValue }); const mountedRef = useMountedRef(); const { user } = React.useContext(UserContext); diff --git a/manager-dashboard/package.json b/manager-dashboard/package.json index f4f758cfc..41efd07b4 100644 --- a/manager-dashboard/package.json +++ b/manager-dashboard/package.json @@ -14,12 +14,7 @@ "build:prod": "webpack --mode=production --node-env=production", "watch": "webpack --watch", "start": "webpack serve --mode=development", - "initialize": "mkdir -p generated && yes n | cp -i type.template.tsx generated/types.tsx 2>/dev/null", - "check-unused": "unimported", - "prelint": "yarn initialize", - "pretypecheck": "yarn initialize", - "prebuild": "yarn initialize", - "prestart": "yarn initialize" + "check-unused": "unimported" }, "repository": { "type": "git", @@ -39,7 +34,7 @@ "@sentry/react": "^6.9.0", "@sentry/tracing": "^6.9.0", "@togglecorp/fujs": "^1.9.2", - "@togglecorp/toggle-form": "^2.0.2", + "@togglecorp/toggle-form": "^2.0.3", "@turf/area": "^6.5.0", "@turf/invariant": "^6.5.0", "apollo-link": "^1.2.14", @@ -77,8 +72,8 @@ "@types/react": "^17.0.14", "@types/react-dom": "^17.0.9", "@types/react-router-dom": "^5.1.8", - "@typescript-eslint/eslint-plugin": "^4.28.3", - "@typescript-eslint/parser": "^4.28.3", + "@typescript-eslint/eslint-plugin": "^5.59.8", + "@typescript-eslint/parser": "^5.59.8", "babel-jest": "^27.4.6", "babel-loader": "^8.2.2", "babel-plugin-graphql-tag": "^3.3.0", @@ -120,7 +115,7 @@ "stylelint-config-recommended": "^5.0.0", "stylelint-webpack-plugin": "^2.2.2", "terser-webpack-plugin": "^5.1.4", - "typescript": "^4.3.5", + "typescript": "^5.0.4", "unimported": "^1.19.1", "webpack": "^5.27.0", "webpack-cli": "^4.5.0", diff --git a/manager-dashboard/yarn.lock b/manager-dashboard/yarn.lock index 11617e234..d3d6cf3e4 100644 --- a/manager-dashboard/yarn.lock +++ b/manager-dashboard/yarn.lock @@ -1361,6 +1361,18 @@ ts-node "^9" tslib "^2" +"@eslint-community/eslint-utils@^4.2.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" + integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== + dependencies: + eslint-visitor-keys "^3.3.0" + +"@eslint-community/regexpp@^4.4.0": + version "4.5.1" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.5.1.tgz#cdd35dce4fa1a89a4fd42b1599eb35b3af408884" + integrity sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ== + "@eslint/eslintrc@^0.4.3": version "0.4.3" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c" @@ -2957,10 +2969,10 @@ dependencies: "@babel/runtime" "^7.19.0" -"@togglecorp/toggle-form@^2.0.2": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@togglecorp/toggle-form/-/toggle-form-2.0.2.tgz#c347b0e756479bcf0b478a4dea763b0c35644a48" - integrity sha512-Q14PsF0KL0i0NBrPPCJ38zWFtBih2FQvMmSJqVDbkYCdwIIE/cmNKwGh/bayTsEHPlCbIGkoEvx1Ak9hXEL2Cg== +"@togglecorp/toggle-form@^2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@togglecorp/toggle-form/-/toggle-form-2.0.3.tgz#4867900b11aef8a9a53833535530fa911264b762" + integrity sha512-43JytRKdqWu9LrRDAbVxyaDjmFWsjEwWfG3UdFl8lbdE/M/BCU/yZIYTq8B8Xavf6ryUhcFIjWbSEAEBdc0K2A== dependencies: "@babel/runtime" "^7.19.0" "@togglecorp/fujs" "^2.0.0" @@ -3152,7 +3164,7 @@ resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.5.tgz#738dd390a6ecc5442f35e7f03fa1431353f7e138" integrity sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA== -"@types/json-schema@*", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.7", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": +"@types/json-schema@*", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": version "7.0.9" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ== @@ -3278,6 +3290,11 @@ resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== +"@types/semver@^7.3.12": + version "7.5.0" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.0.tgz#591c1ce3a702c45ee15f47a42ade72c2fd78978a" + integrity sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw== + "@types/stack-utils@^2.0.0": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" @@ -3325,33 +3342,23 @@ resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d" integrity sha512-te5lMAWii1uEJ4FwLjzdlbw3+n0FZNOvFXHxQDKeT0dilh7HOzdMzV2TrJVUzq8ep7J4Na8OUYPRLSQkJHAlrg== -"@typescript-eslint/eslint-plugin@^4.28.3": - version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.33.0.tgz#c24dc7c8069c7706bc40d99f6fa87edcb2005276" - integrity sha512-aINiAxGVdOl1eJyVjaWn/YcVAq4Gi/Yo35qHGCnqbWVz61g39D0h23veY/MA0rFFGfxK7TySg2uwDeNv+JgVpg== - dependencies: - "@typescript-eslint/experimental-utils" "4.33.0" - "@typescript-eslint/scope-manager" "4.33.0" - debug "^4.3.1" - functional-red-black-tree "^1.0.1" - ignore "^5.1.8" - regexpp "^3.1.0" - semver "^7.3.5" +"@typescript-eslint/eslint-plugin@^5.59.8": + version "5.59.8" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.8.tgz#1e7a3e5318ece22251dfbc5c9c6feeb4793cc509" + integrity sha512-JDMOmhXteJ4WVKOiHXGCoB96ADWg9q7efPWHRViT/f09bA8XOMLAVHHju3l0MkZnG1izaWXYmgvQcUjTRcpShQ== + dependencies: + "@eslint-community/regexpp" "^4.4.0" + "@typescript-eslint/scope-manager" "5.59.8" + "@typescript-eslint/type-utils" "5.59.8" + "@typescript-eslint/utils" "5.59.8" + debug "^4.3.4" + grapheme-splitter "^1.0.4" + ignore "^5.2.0" + natural-compare-lite "^1.4.0" + semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/experimental-utils@4.33.0": - version "4.33.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.33.0.tgz#6f2a786a4209fa2222989e9380b5331b2810f7fd" - integrity sha512-zeQjOoES5JFjTnAhI5QY7ZviczMzDptls15GFsI6jyUOq0kOf9+WonkhtlIhh0RgHRnqj5gdNxW5j1EvAyYg6Q== - dependencies: - "@types/json-schema" "^7.0.7" - "@typescript-eslint/scope-manager" "4.33.0" - "@typescript-eslint/types" "4.33.0" - "@typescript-eslint/typescript-estree" "4.33.0" - eslint-scope "^5.1.1" - eslint-utils "^3.0.0" - -"@typescript-eslint/parser@^4.28.1", "@typescript-eslint/parser@^4.28.3": +"@typescript-eslint/parser@^4.28.1": version "4.33.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.33.0.tgz#dfe797570d9694e560528d18eecad86c8c744899" integrity sha512-ZohdsbXadjGBSK0/r+d87X0SBmKzOq4/S5nzK6SBgJspFo9/CUDJ7hjayuze+JK7CZQLDMroqytp7pOcFKTxZA== @@ -3361,6 +3368,16 @@ "@typescript-eslint/typescript-estree" "4.33.0" debug "^4.3.1" +"@typescript-eslint/parser@^5.59.8": + version "5.59.8" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.59.8.tgz#60cbb00671d86cf746044ab797900b1448188567" + integrity sha512-AnR19RjJcpjoeGojmwZtCwBX/RidqDZtzcbG3xHrmz0aHHoOcbWnpDllenRDmDvsV0RQ6+tbb09/kyc+UT9Orw== + dependencies: + "@typescript-eslint/scope-manager" "5.59.8" + "@typescript-eslint/types" "5.59.8" + "@typescript-eslint/typescript-estree" "5.59.8" + debug "^4.3.4" + "@typescript-eslint/scope-manager@4.33.0": version "4.33.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.33.0.tgz#d38e49280d983e8772e29121cf8c6e9221f280a3" @@ -3369,11 +3386,34 @@ "@typescript-eslint/types" "4.33.0" "@typescript-eslint/visitor-keys" "4.33.0" +"@typescript-eslint/scope-manager@5.59.8": + version "5.59.8" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.59.8.tgz#ff4ad4fec6433647b817c4a7d4b4165d18ea2fa8" + integrity sha512-/w08ndCYI8gxGf+9zKf1vtx/16y8MHrZs5/tnjHhMLNSixuNcJavSX4wAiPf4aS5x41Es9YPCn44MIe4cxIlig== + dependencies: + "@typescript-eslint/types" "5.59.8" + "@typescript-eslint/visitor-keys" "5.59.8" + +"@typescript-eslint/type-utils@5.59.8": + version "5.59.8" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.59.8.tgz#aa6c029a9d7706d26bbd25eb4666398781df6ea2" + integrity sha512-+5M518uEIHFBy3FnyqZUF3BMP+AXnYn4oyH8RF012+e7/msMY98FhGL5SrN29NQ9xDgvqCgYnsOiKp1VjZ/fpA== + dependencies: + "@typescript-eslint/typescript-estree" "5.59.8" + "@typescript-eslint/utils" "5.59.8" + debug "^4.3.4" + tsutils "^3.21.0" + "@typescript-eslint/types@4.33.0": version "4.33.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.33.0.tgz#a1e59036a3b53ae8430ceebf2a919dc7f9af6d72" integrity sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ== +"@typescript-eslint/types@5.59.8": + version "5.59.8" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.8.tgz#212e54414733618f5d0fd50b2da2717f630aebf8" + integrity sha512-+uWuOhBTj/L6awoWIg0BlWy0u9TyFpCHrAuQ5bNfxDaZ1Ppb3mx6tUigc74LHcbHpOHuOTOJrBoAnhdHdaea1w== + "@typescript-eslint/typescript-estree@4.33.0", "@typescript-eslint/typescript-estree@^4.28.1": version "4.33.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.33.0.tgz#0dfb51c2908f68c5c08d82aefeaf166a17c24609" @@ -3387,6 +3427,33 @@ semver "^7.3.5" tsutils "^3.21.0" +"@typescript-eslint/typescript-estree@5.59.8": + version "5.59.8" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.8.tgz#801a7b1766481629481b3b0878148bd7a1f345d7" + integrity sha512-Jy/lPSDJGNow14vYu6IrW790p7HIf/SOV1Bb6lZ7NUkLc2iB2Z9elESmsaUtLw8kVqogSbtLH9tut5GCX1RLDg== + dependencies: + "@typescript-eslint/types" "5.59.8" + "@typescript-eslint/visitor-keys" "5.59.8" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + semver "^7.3.7" + tsutils "^3.21.0" + +"@typescript-eslint/utils@5.59.8": + version "5.59.8" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.59.8.tgz#34d129f35a2134c67fdaf024941e8f96050dca2b" + integrity sha512-Tr65630KysnNn9f9G7ROF3w1b5/7f6QVCJ+WK9nhIocWmx9F+TmCAcglF26Vm7z8KCTwoKcNEBZrhlklla3CKg== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@types/json-schema" "^7.0.9" + "@types/semver" "^7.3.12" + "@typescript-eslint/scope-manager" "5.59.8" + "@typescript-eslint/types" "5.59.8" + "@typescript-eslint/typescript-estree" "5.59.8" + eslint-scope "^5.1.1" + semver "^7.3.7" + "@typescript-eslint/visitor-keys@4.33.0": version "4.33.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.33.0.tgz#2a22f77a41604289b7a186586e9ec48ca92ef1dd" @@ -3395,6 +3462,14 @@ "@typescript-eslint/types" "4.33.0" eslint-visitor-keys "^2.0.0" +"@typescript-eslint/visitor-keys@5.59.8": + version "5.59.8" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.8.tgz#aa6a7ef862add919401470c09e1609392ef3cc40" + integrity sha512-pJhi2ms0x0xgloT7xYabil3SGGlojNNKjK/q6dB3Ey0uJLMjK2UDGJvHieiyJVW/7C3KI+Z4Q3pEHkm4ejA+xQ== + dependencies: + "@typescript-eslint/types" "5.59.8" + eslint-visitor-keys "^3.3.0" + "@webassemblyjs/ast@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7" @@ -5312,7 +5387,7 @@ debug@^3.1.1, debug@^3.2.6, debug@^3.2.7: dependencies: ms "^2.1.1" -debug@^4.3.2: +debug@^4.3.2, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -5986,13 +6061,6 @@ eslint-utils@^2.1.0: dependencies: eslint-visitor-keys "^1.1.0" -eslint-utils@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" - integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== - dependencies: - eslint-visitor-keys "^2.0.0" - eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" @@ -6003,6 +6071,11 @@ eslint-visitor-keys@^2.0.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== +eslint-visitor-keys@^3.3.0: + version "3.4.1" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz#c22c48f48942d08ca824cc526211ae400478a994" + integrity sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA== + eslint-webpack-plugin@^2.5.4: version "2.6.0" resolved "https://registry.yarnpkg.com/eslint-webpack-plugin/-/eslint-webpack-plugin-2.6.0.tgz#3bd4ada4e539cb1f6687d2f619073dbb509361cd" @@ -6314,6 +6387,17 @@ fast-glob@^3.1.1, fast-glob@^3.2.5: merge2 "^1.3.0" micromatch "^4.0.4" +fast-glob@^3.2.9: + version "3.2.12" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80" + integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" @@ -6805,6 +6889,18 @@ globby@11.x.x, globby@^11.0.3, globby@^11.0.4: merge2 "^1.3.0" slash "^3.0.0" +globby@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + globby@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" @@ -6855,6 +6951,11 @@ graceful-fs@^4.2.9: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== +grapheme-splitter@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" + integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== + graphql-anywhere@^4.2.7: version "4.2.7" resolved "https://registry.yarnpkg.com/graphql-anywhere/-/graphql-anywhere-4.2.7.tgz#c06fb40b1d62b39470c80e3731478dbbef060067" @@ -7225,6 +7326,11 @@ ignore@^5.1.4, ignore@^5.1.8: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== +ignore@^5.2.0: + version "5.2.4" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" + integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== + image-q@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/image-q/-/image-q-1.1.1.tgz#fc84099664460b90ca862d9300b6bfbbbfbf8056" @@ -9065,7 +9171,7 @@ merge-stream@^2.0.0: resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== -merge2@^1.3.0: +merge2@^1.3.0, merge2@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== @@ -9297,6 +9403,11 @@ native-url@^0.2.6: dependencies: querystring "^0.2.0" +natural-compare-lite@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" + integrity sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g== + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -11435,6 +11546,13 @@ semver@^7.3.2: dependencies: lru-cache "^6.0.0" +semver@^7.3.7: + version "7.5.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.1.tgz#c90c4d631cf74720e46b21c1d37ea07edfab91ec" + integrity sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw== + dependencies: + lru-cache "^6.0.0" + send@0.17.2: version "0.17.2" resolved "https://registry.yarnpkg.com/send/-/send-0.17.2.tgz#926622f76601c41808012c8bf1688fe3906f7820" @@ -12598,6 +12716,11 @@ typescript@^4.3.5: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.4.tgz#a17d3a0263bf5c8723b9c52f43c5084edf13c2e8" integrity sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg== +typescript@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.4.tgz#b217fd20119bd61a94d4011274e0ab369058da3b" + integrity sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw== + ua-parser-js@^0.7.30: version "0.7.31" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.31.tgz#649a656b191dffab4f21d5e053e27ca17cbff5c6" From c6d57da5d2cc060b89de087a2c473a17930b1083 Mon Sep 17 00:00:00 2001 From: shreeyash07 Date: Wed, 31 May 2023 17:25:03 +0545 Subject: [PATCH 08/72] Add schema for define options form --- .../NewTutorial/DescribeOptions/index.tsx | 84 ++++++++++++++++++ .../app/views/NewTutorial/utils.ts | 85 ++++++++++++++++++- 2 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 manager-dashboard/app/views/NewTutorial/DescribeOptions/index.tsx diff --git a/manager-dashboard/app/views/NewTutorial/DescribeOptions/index.tsx b/manager-dashboard/app/views/NewTutorial/DescribeOptions/index.tsx new file mode 100644 index 000000000..050097519 --- /dev/null +++ b/manager-dashboard/app/views/NewTutorial/DescribeOptions/index.tsx @@ -0,0 +1,84 @@ +import React from 'react'; +import { MdAdd } from 'react-icons/md'; + +import Button from '#components/Button'; +import Card from '#components/Card'; +import Heading from '#components/Heading'; +import Tabs, { Tab, TabList, TabPanel } from '#components/Tabs'; +import TextInput from '#components/TextInput'; + +import styles from './styles.css'; + +export default function DescribeOptions() { + const [activeOptionsTab, setActiveOptionsTab] = React.useState('option1'); + + return ( + + + Option Instructions + + + + + + Option 1 + + + Option 2 + + + +
+ + + + Reasons + + + + +
+
+
+ +
+ ); +} diff --git a/manager-dashboard/app/views/NewTutorial/utils.ts b/manager-dashboard/app/views/NewTutorial/utils.ts index 00de09073..7d2687af1 100644 --- a/manager-dashboard/app/views/NewTutorial/utils.ts +++ b/manager-dashboard/app/views/NewTutorial/utils.ts @@ -20,6 +20,7 @@ import { PROJECT_TYPE_BUILD_AREA, PROJECT_TYPE_CHANGE_DETECTION, PROJECT_TYPE_COMPLETENESS, + PROJECT_TYPE_FOOTPRINT, } from '#utils/common'; // FIXME: include here @@ -37,6 +38,19 @@ export type TutorialTasks = GeoJSON.FeatureCollection; +export type DefineOptions = { + option: number; + title: string; + description: string; + icon: string; + iconColor: string; + reasons: { + reason: number; + description: string; + icon: string; + }[]; +}[]; + export interface TutorialFormType { lookFor: string; name: string; @@ -65,6 +79,7 @@ export interface TutorialFormType { projectType: ProjectType; tileServerB?: TileServer, zoomLevel?: number; + defineOptions?: DefineOptions; } export type PartialTutorialFormType = PartialForm< @@ -73,7 +88,7 @@ export type PartialTutorialFormType = PartialForm< exampleImage2?: File; }, // NOTE: we do not want to change File and FeatureCollection to partials - 'scenario' | 'tutorialTasks' | 'exampleImage1' | 'exampleImage2' + 'scenario' | 'tutorialTasks' | 'exampleImage1' | 'exampleImage2' | 'option' | 'reason' >; type TutorialFormSchema = ObjectSchema; @@ -86,6 +101,13 @@ type ScreenFormSchemaFields = ReturnType; type ScreenFormSchema = ArraySchema; type ScreenFormSchemaMember = ReturnType; +type DefineOptionsType = NonNullable[number]; +type DefineOptionsSchema = ObjectSchema; +type DefineOptionsSchemaFields = ReturnType + +type DefineOptionsFormSchema = ArraySchema; +type DefineOptionsFormSchemaMember = ReturnType; + export const tutorialFormSchema: TutorialFormSchema = { fields: (value): TutorialFormSchemaFields => { let baseSchema: TutorialFormSchemaFields = { @@ -166,6 +188,67 @@ export const tutorialFormSchema: TutorialFormSchema = { exampleImage1: { required: true }, exampleImage2: { required: true }, }; + baseSchema = addCondition( + baseSchema, + value, + ['projectType'], + ['defineOptions'], + (v) => { + const defineOptionsField: DefineOptionsFormSchema = { + keySelector: (key) => key.option, + member: (): DefineOptionsFormSchemaMember => ({ + fields: (): DefineOptionsSchemaFields => ({ + option: { + required: true, + }, + title: { + required: true, + requiredValidation: requiredStringCondition, + validations: [getNoMoreThanNCharacterCondition(500)], + }, + description: { + required: true, + requiredValidation: requiredStringCondition, + validations: [getNoMoreThanNCharacterCondition(500)], + }, + icon: { + required: true, + }, + iconColor: { + required: true, + }, + reasons: { + keySelector: (key) => key.reason, + member: () => ({ + fields: () => ({ + reason: { + required: true, + }, + description: { + required: true, + requiredValidation: requiredStringCondition, + validations: [getNoMoreThanNCharacterCondition(500)], + }, + icon: { + required: true, + }, + }), + }), + }, + }), + }), + }; + + if (v?.projectType === PROJECT_TYPE_FOOTPRINT) { + return { + defineOptions: defineOptionsField, + }; + } + return { + defineOptions: { forceValue: nullValue }, + }; + }, + ); baseSchema = addCondition( baseSchema, value, From 1093f37e75f933b237697c36d64c5dfeefd80964 Mon Sep 17 00:00:00 2001 From: shreeyash07 Date: Fri, 2 Jun 2023 18:09:23 +0545 Subject: [PATCH 09/72] Add define option form --- .../views/NewTutorial/DefineOptions/index.tsx | 75 ++++++++++++ .../NewTutorial/DefineOptions/styles.css | 11 ++ .../NewTutorial/DescribeOptions/index.tsx | 84 ------------- .../views/NewTutorial/ScenarioInput/index.tsx | 1 + .../app/views/NewTutorial/index.tsx | 111 ++++++++++++++++++ 5 files changed, 198 insertions(+), 84 deletions(-) create mode 100644 manager-dashboard/app/views/NewTutorial/DefineOptions/index.tsx create mode 100644 manager-dashboard/app/views/NewTutorial/DefineOptions/styles.css delete mode 100644 manager-dashboard/app/views/NewTutorial/DescribeOptions/index.tsx diff --git a/manager-dashboard/app/views/NewTutorial/DefineOptions/index.tsx b/manager-dashboard/app/views/NewTutorial/DefineOptions/index.tsx new file mode 100644 index 000000000..b728524cf --- /dev/null +++ b/manager-dashboard/app/views/NewTutorial/DefineOptions/index.tsx @@ -0,0 +1,75 @@ +import React from 'react'; +import { MdAdd } from 'react-icons/md'; + +import { SetValueArg, Error, useFormObject, getErrorObject } from '@togglecorp/toggle-form'; +import Button from '#components/Button'; +import Heading from '#components/Heading'; +import { TabPanel } from '#components/Tabs'; +import TextInput from '#components/TextInput'; + +import styles from './styles.css'; +import { PartialTutorialFormType } from '../utils'; + +type PartialDefineOptionType = NonNullable[number] +const defaultDefineOptionValue: PartialDefineOptionType = { + option: 1, +}; +interface Props { + value: PartialDefineOptionType, + onChange: (value: SetValueArg, index: number) => void; + index: number; + error: Error | undefined +} + +export default function DefineOptions(props: Props) { + const { + value, + onChange, + index, + error: riskyError, + } = props; + + const onOptionChange = useFormObject(index, onChange, defaultDefineOptionValue); + const error = getErrorObject(riskyError); + return ( + +
+ + + + Reasons + + + + +
+
+ ); +} diff --git a/manager-dashboard/app/views/NewTutorial/DefineOptions/styles.css b/manager-dashboard/app/views/NewTutorial/DefineOptions/styles.css new file mode 100644 index 000000000..8a7cfd02c --- /dev/null +++ b/manager-dashboard/app/views/NewTutorial/DefineOptions/styles.css @@ -0,0 +1,11 @@ +.options-container{ + display: flex; + flex-direction: column; + gap: var(--spacing-large); + + .option-form { + display: flex; + flex-direction: column; + gap: var(--spacing-medium); + } +} \ No newline at end of file diff --git a/manager-dashboard/app/views/NewTutorial/DescribeOptions/index.tsx b/manager-dashboard/app/views/NewTutorial/DescribeOptions/index.tsx deleted file mode 100644 index 050097519..000000000 --- a/manager-dashboard/app/views/NewTutorial/DescribeOptions/index.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import React from 'react'; -import { MdAdd } from 'react-icons/md'; - -import Button from '#components/Button'; -import Card from '#components/Card'; -import Heading from '#components/Heading'; -import Tabs, { Tab, TabList, TabPanel } from '#components/Tabs'; -import TextInput from '#components/TextInput'; - -import styles from './styles.css'; - -export default function DescribeOptions() { - const [activeOptionsTab, setActiveOptionsTab] = React.useState('option1'); - - return ( - - - Option Instructions - - - - - - Option 1 - - - Option 2 - - - -
- - - - Reasons - - - - -
-
-
- -
- ); -} diff --git a/manager-dashboard/app/views/NewTutorial/ScenarioInput/index.tsx b/manager-dashboard/app/views/NewTutorial/ScenarioInput/index.tsx index c96197242..ffb86928f 100644 --- a/manager-dashboard/app/views/NewTutorial/ScenarioInput/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/ScenarioInput/index.tsx @@ -87,6 +87,7 @@ export default function ScenarioInput(scenarioProps: ScenarioTabsProps) { const onInstructionFieldChange = useFormObject<'instructions', PartialScenarioType['instructions']>('instructions', onFieldChange, {}); const onHintFieldChange = useFormObject<'hint', PartialScenarioType['hint']>('hint', onFieldChange, {}); const onSuccessFieldChange = useFormObject<'success', PartialScenarioType['success']>('success', onFieldChange, {}); + const error = getErrorObject(riskyError); const instructionsError = getErrorObject(error?.instructions); diff --git a/manager-dashboard/app/views/NewTutorial/index.tsx b/manager-dashboard/app/views/NewTutorial/index.tsx index a30ddaf75..16ee11036 100644 --- a/manager-dashboard/app/views/NewTutorial/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/index.tsx @@ -27,6 +27,7 @@ import { MdSwipe, MdOutlinePublishedWithChanges, MdOutlineUnpublished, + MdAdd, } from 'react-icons/md'; import { Link } from 'react-router-dom'; @@ -57,6 +58,7 @@ import { PROJECT_TYPE_BUILD_AREA, PROJECT_TYPE_COMPLETENESS, PROJECT_TYPE_CHANGE_DETECTION, + PROJECT_TYPE_FOOTPRINT, } from '#utils/common'; import { @@ -64,9 +66,55 @@ import { TutorialFormType, PartialTutorialFormType, } from './utils'; +import DefineOptions from './DefineOptions'; import ScenarioInput from './ScenarioInput'; import styles from './styles.css'; +import Heading from '#components/Heading'; +const defaultDefineOptions: PartialTutorialFormType['defineOptions'] = [ + { + option: 1, + title: 'Yes', + description: '', + icon: '', + iconColor: '', + reasons: [ + { + reason: 1, + description: '', + icon: '', + }, + ], + }, + { + option: 2, + title: 'No', + description: '', + icon: '', + iconColor: '', + reasons: [ + { + reason: 1, + description: '', + icon: '', + }, + ], + }, + { + option: 3, + title: 'Not sure', + description: '', + icon: '', + iconColor: '', + reasons: [ + { + reason: 1, + description: '', + icon: '', + }, + ], + }, +]; const defaultTutorialFormValue: PartialTutorialFormType = { projectType: PROJECT_TYPE_BUILD_AREA, zoomLevel: 18, @@ -78,6 +126,7 @@ const defaultTutorialFormValue: PartialTutorialFormType = { name: TILE_SERVER_BING, credits: tileServerDefaultCredits[TILE_SERVER_BING], }, + defineOptions: defaultDefineOptions, }; interface Props { @@ -109,6 +158,8 @@ function NewTutorial(props: Props) { ] = React.useState<'started' | 'imageUpload' | 'tutorialSubmit' | 'success' | 'failed' | undefined>(); const [activeTab, setActiveTab] = React.useState('Scenario 1'); + const [activeOptionsTab, setActiveOptionsTab] = React.useState('option1'); + const error = React.useMemo( () => getErrorObject(formError), [formError], @@ -118,6 +169,11 @@ function NewTutorial(props: Props) { [error?.screens], ); + const optionsError = React.useMemo( + () => getErrorObject(error?.defineOptions), + [error?.defineOptions], + ); + const handleFormSubmission = React.useCallback(( finalValuesFromProps: PartialTutorialFormType, ) => { @@ -219,6 +275,17 @@ function NewTutorial(props: Props) { [error], ); + const { + setValue: onOptionsAdd, + } = useFormArray('defineOptions', setFieldValue); + + // const handleAddDefineOptions = useCallback( + // () => { + // setFieldValue(defaultDefineOptions, 'defineOptions'); + // }, + // [setFieldValue], + // ); + const submissionPending = ( tutorialSubmissionStatus === 'started' || tutorialSubmissionStatus === 'imageUpload' @@ -331,6 +398,50 @@ function NewTutorial(props: Props) { disabled={submissionPending} />
+ {value.projectType === PROJECT_TYPE_FOOTPRINT && ( + + + Option Instrucitons + + + {value.defineOptions?.length ? ( + + + {value.defineOptions.map((opt) => ( + + {`Option ${opt.option}`} + + ))} + + {value.defineOptions.map((options, index) => ( + + ))} + + ) : ( +
Add options
+ )} +
+ )} Date: Mon, 5 Jun 2023 17:39:19 +0545 Subject: [PATCH 10/72] Add reasons form --- .../app/components/Tabs/index.tsx | 3 + .../NewTutorial/DefineOption/Reason/index.tsx | 58 ++++++ .../DefineOption/Reason/styles.css | 12 ++ .../views/NewTutorial/DefineOption/index.tsx | 182 ++++++++++++++++++ .../views/NewTutorial/DefineOption/styles.css | 27 +++ .../views/NewTutorial/DefineOptions/index.tsx | 75 -------- .../NewTutorial/DefineOptions/styles.css | 11 -- .../views/NewTutorial/ScenarioInput/index.tsx | 40 +--- .../app/views/NewTutorial/index.tsx | 128 ++++++------ .../app/views/NewTutorial/styles.css | 6 +- .../app/views/NewTutorial/utils.ts | 99 +++++++--- 11 files changed, 425 insertions(+), 216 deletions(-) create mode 100644 manager-dashboard/app/views/NewTutorial/DefineOption/Reason/index.tsx create mode 100644 manager-dashboard/app/views/NewTutorial/DefineOption/Reason/styles.css create mode 100644 manager-dashboard/app/views/NewTutorial/DefineOption/index.tsx create mode 100644 manager-dashboard/app/views/NewTutorial/DefineOption/styles.css delete mode 100644 manager-dashboard/app/views/NewTutorial/DefineOptions/index.tsx delete mode 100644 manager-dashboard/app/views/NewTutorial/DefineOptions/styles.css diff --git a/manager-dashboard/app/components/Tabs/index.tsx b/manager-dashboard/app/components/Tabs/index.tsx index e7bcadf4f..98bc6552a 100644 --- a/manager-dashboard/app/components/Tabs/index.tsx +++ b/manager-dashboard/app/components/Tabs/index.tsx @@ -78,6 +78,7 @@ export function TabList(props: TabListProps) { export interface TabPanelProps extends React.HTMLProps { name: TabKey; elementRef?: React.Ref; + className?: string; } export function TabPanel(props: TabPanelProps) { @@ -86,6 +87,7 @@ export function TabPanel(props: TabPanelProps) { const { name, elementRef, + className, ...otherProps } = props; @@ -98,6 +100,7 @@ export function TabPanel(props: TabPanelProps) { {...otherProps} role="tabpanel" ref={elementRef} + className={className} /> ); } diff --git a/manager-dashboard/app/views/NewTutorial/DefineOption/Reason/index.tsx b/manager-dashboard/app/views/NewTutorial/DefineOption/Reason/index.tsx new file mode 100644 index 000000000..d3cf1e9f0 --- /dev/null +++ b/manager-dashboard/app/views/NewTutorial/DefineOption/Reason/index.tsx @@ -0,0 +1,58 @@ +import React from 'react'; +import { + Error, + SetValueArg, + getErrorObject, + useFormObject, +} from '@togglecorp/toggle-form'; +import { TabPanel } from '#components/Tabs'; +import TextInput from '#components/TextInput'; +import Button from '#components/Button'; + +import { PartialDefineOptionType } from '..'; +import styles from './styles.css'; + +type PartialReasonType = NonNullable[number] + +interface Props { + value: PartialReasonType; + onChange: (value: SetValueArg, index: number) => void; + onRemove: (index: number) => void; + index: number; + error: Error | undefined; +} +export default function Reason(props: Props) { + const { + value, + onChange, + onRemove, + index, + error: riskyError, + } = props; + + const onReasonChange = useFormObject(index, onChange, { reasonId: 1 }); + + const error = getErrorObject(riskyError); + return ( + + + + + ); +} diff --git a/manager-dashboard/app/views/NewTutorial/DefineOption/Reason/styles.css b/manager-dashboard/app/views/NewTutorial/DefineOption/Reason/styles.css new file mode 100644 index 000000000..bdd535e6e --- /dev/null +++ b/manager-dashboard/app/views/NewTutorial/DefineOption/Reason/styles.css @@ -0,0 +1,12 @@ +.reason-content { + display: flex; + gap: var(--spacing-medium); + + .reason-input { + flex-grow: 1; + } + .remove-button { + width: fit-content; + height: fit-content; + } +} \ No newline at end of file diff --git a/manager-dashboard/app/views/NewTutorial/DefineOption/index.tsx b/manager-dashboard/app/views/NewTutorial/DefineOption/index.tsx new file mode 100644 index 000000000..d2b9f7501 --- /dev/null +++ b/manager-dashboard/app/views/NewTutorial/DefineOption/index.tsx @@ -0,0 +1,182 @@ +import React from 'react'; +import { MdAdd } from 'react-icons/md'; + +import { + SetValueArg, + Error, + useFormObject, + getErrorObject, + useFormArray, +} from '@togglecorp/toggle-form'; +import Button from '#components/Button'; +import Heading from '#components/Heading'; +import Tabs, { TabList, TabPanel, Tab } from '#components/Tabs'; +import TextInput from '#components/TextInput'; +import SelectInput from '#components/SelectInput'; + +import Reason from './Reason'; +import { + IconOptions, + PartialTutorialFormType, + iconColorOptions, + iconOptions, +} from '../utils'; + +import styles from './styles.css'; + +export type PartialDefineOptionType = NonNullable[number] +const defaultDefineOptionValue: PartialDefineOptionType = { + optionId: 1, +}; + +type ReasonType = NonNullable[number]; + +interface Props { + value: PartialDefineOptionType; + onChange: (value: SetValueArg, index: number) => void; + onRemove: (index: number) => void; + index: number; + error: Error | undefined; +} + +export default function DefineOption(props: Props) { + const { + value, + onChange, + onRemove, + index, + error: riskyError, + } = props; + + const [activeReason, setActiveReason] = React.useState('1'); + const [reasonId, setReasonId] = React.useState(0); + + const onOptionChange = useFormObject(index, onChange, defaultDefineOptionValue); + + const { + setValue: onReasonAdd, + removeValue: onReasonRemove, + } = useFormArray('reason', onOptionChange); + + const error = getErrorObject(riskyError); + + const reasonError = React.useMemo( + () => getErrorObject(error?.reason), + [error?.reason], + ); + + const handleReasonAdd = React.useCallback( + () => { + const newReasonId = reasonId + 1; + setReasonId(newReasonId); + const newReason : ReasonType = { + reasonId: newReasonId, + }; + onOptionChange( + (oldValue: PartialDefineOptionType['reason']) => ( + [...(oldValue ?? []), newReason] + ), + 'reason', + ); + }, + [onOptionChange, reasonId], + ); + + return ( + +
+
+ + d.key} + labelSelector={(d: IconOptions) => d.label} + onChange={onOptionChange} + error={error?.icon} + /> +
+
+ + d.key} + labelSelector={(d: IconOptions) => d.label} + onChange={onOptionChange} + error={error?.iconColor} + /> +
+ + + Reason + + + {value.reason?.length ? ( + + + {value.reason?.map((rea) => ( + + {`Reason ${rea.reasonId}`} + + ))} + + {value.reason.map((rea, i) => ( + + ))} + + ) : ( +
Add reason
+ )} +
+
+ ); +} diff --git a/manager-dashboard/app/views/NewTutorial/DefineOption/styles.css b/manager-dashboard/app/views/NewTutorial/DefineOption/styles.css new file mode 100644 index 000000000..0a2844aa9 --- /dev/null +++ b/manager-dashboard/app/views/NewTutorial/DefineOption/styles.css @@ -0,0 +1,27 @@ +.option-form { + display: flex; + flex-direction: column; + gap: var(--spacing-medium); + + .option-content { + display: flex; + gap: var(--spacing-medium); + + .option-input { + flex-grow: 1; + } + + .option-icon { + flex-basis: 15%; + } + } + + .remove-button { + align-self: end; + width: fit-content; + } + + .add-button { + width: fit-content; + } +} diff --git a/manager-dashboard/app/views/NewTutorial/DefineOptions/index.tsx b/manager-dashboard/app/views/NewTutorial/DefineOptions/index.tsx deleted file mode 100644 index b728524cf..000000000 --- a/manager-dashboard/app/views/NewTutorial/DefineOptions/index.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import React from 'react'; -import { MdAdd } from 'react-icons/md'; - -import { SetValueArg, Error, useFormObject, getErrorObject } from '@togglecorp/toggle-form'; -import Button from '#components/Button'; -import Heading from '#components/Heading'; -import { TabPanel } from '#components/Tabs'; -import TextInput from '#components/TextInput'; - -import styles from './styles.css'; -import { PartialTutorialFormType } from '../utils'; - -type PartialDefineOptionType = NonNullable[number] -const defaultDefineOptionValue: PartialDefineOptionType = { - option: 1, -}; -interface Props { - value: PartialDefineOptionType, - onChange: (value: SetValueArg, index: number) => void; - index: number; - error: Error | undefined -} - -export default function DefineOptions(props: Props) { - const { - value, - onChange, - index, - error: riskyError, - } = props; - - const onOptionChange = useFormObject(index, onChange, defaultDefineOptionValue); - const error = getErrorObject(riskyError); - return ( - -
- - - - Reasons - - - - -
-
- ); -} diff --git a/manager-dashboard/app/views/NewTutorial/DefineOptions/styles.css b/manager-dashboard/app/views/NewTutorial/DefineOptions/styles.css deleted file mode 100644 index 8a7cfd02c..000000000 --- a/manager-dashboard/app/views/NewTutorial/DefineOptions/styles.css +++ /dev/null @@ -1,11 +0,0 @@ -.options-container{ - display: flex; - flex-direction: column; - gap: var(--spacing-large); - - .option-form { - display: flex; - flex-direction: column; - gap: var(--spacing-medium); - } -} \ No newline at end of file diff --git a/manager-dashboard/app/views/NewTutorial/ScenarioInput/index.tsx b/manager-dashboard/app/views/NewTutorial/ScenarioInput/index.tsx index ffb86928f..ea77c4908 100644 --- a/manager-dashboard/app/views/NewTutorial/ScenarioInput/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/ScenarioInput/index.tsx @@ -14,9 +14,13 @@ import { } from '#components/Tabs'; import styles from './styles.css'; +import { + IconOptions, + iconOptions, +} from '../utils'; type ScenarioType = { - scenario: string; + scenarioId: string; hint: { description: string; icon: string; @@ -34,37 +38,9 @@ type ScenarioType = { }; }; -interface IconOptions { - key: string; - label: string; -} - -const iconOptions: IconOptions[] = [ - { - key: 'swipe-left', - label: 'Swipe Left', - }, - { - key: 'tap-1', - label: 'Tap 1', - }, - { - key: 'tap-2', - label: 'Tap 2', - }, - { - key: 'tap-3', - label: 'Tap 3', - }, - { - key: 'check', - label: 'Check', - }, -]; - -type PartialScenarioType = PartialForm; +type PartialScenarioType = PartialForm; const defaultScenarioTabsValue: PartialScenarioType = { - scenario: 'xxx', + scenarioId: 'xxx', }; interface ScenarioTabsProps { @@ -96,7 +72,7 @@ export default function ScenarioInput(scenarioProps: ScenarioTabsProps) { return (
diff --git a/manager-dashboard/app/views/NewTutorial/index.tsx b/manager-dashboard/app/views/NewTutorial/index.tsx index 16ee11036..022366e23 100644 --- a/manager-dashboard/app/views/NewTutorial/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/index.tsx @@ -65,54 +65,26 @@ import { tutorialFormSchema, TutorialFormType, PartialTutorialFormType, + ScreenType, + DefineOptionType, } from './utils'; -import DefineOptions from './DefineOptions'; +import DefineOption from './DefineOption'; import ScenarioInput from './ScenarioInput'; import styles from './styles.css'; import Heading from '#components/Heading'; -const defaultDefineOptions: PartialTutorialFormType['defineOptions'] = [ +const defaultDefineOption: PartialTutorialFormType['defineOption'] = [ { - option: 1, + optionId: 1, title: 'Yes', - description: '', - icon: '', - iconColor: '', - reasons: [ - { - reason: 1, - description: '', - icon: '', - }, - ], }, { - option: 2, + optionId: 2, title: 'No', - description: '', - icon: '', - iconColor: '', - reasons: [ - { - reason: 1, - description: '', - icon: '', - }, - ], }, { - option: 3, + optionId: 3, title: 'Not sure', - description: '', - icon: '', - iconColor: '', - reasons: [ - { - reason: 1, - description: '', - icon: '', - }, - ], }, ]; const defaultTutorialFormValue: PartialTutorialFormType = { @@ -126,7 +98,7 @@ const defaultTutorialFormValue: PartialTutorialFormType = { name: TILE_SERVER_BING, credits: tileServerDefaultCredits[TILE_SERVER_BING], }, - defineOptions: defaultDefineOptions, + defineOption: defaultDefineOption, }; interface Props { @@ -158,7 +130,9 @@ function NewTutorial(props: Props) { ] = React.useState<'started' | 'imageUpload' | 'tutorialSubmit' | 'success' | 'failed' | undefined>(); const [activeTab, setActiveTab] = React.useState('Scenario 1'); - const [activeOptionsTab, setActiveOptionsTab] = React.useState('option1'); + const [activeOptionsTab, setActiveOptionsTab] = React.useState('1'); + + const [optionId, setOptionId] = React.useState(defaultDefineOption?.length ?? 3); const error = React.useMemo( () => getErrorObject(formError), @@ -170,8 +144,8 @@ function NewTutorial(props: Props) { ); const optionsError = React.useMemo( - () => getErrorObject(error?.defineOptions), - [error?.defineOptions], + () => getErrorObject(error?.defineOption), + [error?.defineOption], ); const handleFormSubmission = React.useCallback(( @@ -268,7 +242,10 @@ function NewTutorial(props: Props) { const { setValue: onScenarioFormChange, - } = useFormArray('screens', setFieldValue); + } = useFormArray< + 'screens', + ScreenType + >('screens', setFieldValue); const hasErrors = React.useMemo( () => analyzeErrors(error), @@ -276,15 +253,29 @@ function NewTutorial(props: Props) { ); const { - setValue: onOptionsAdd, - } = useFormArray('defineOptions', setFieldValue); - - // const handleAddDefineOptions = useCallback( - // () => { - // setFieldValue(defaultDefineOptions, 'defineOptions'); - // }, - // [setFieldValue], - // ); + setValue: onOptionAdd, + removeValue: onOptionRemove, + } = useFormArray< + 'defineOption', + DefineOptionType + >('defineOption', setFieldValue); + + const handleAddDefineOptions = React.useCallback( + () => { + const newOptionId = optionId + 1; + setOptionId(newOptionId); + const newDefineOption: DefineOptionType = { + optionId: newOptionId, + }; + setFieldValue( + (oldValue: PartialTutorialFormType['defineOption']) => ( + [...(oldValue ?? []), newDefineOption] + ), + 'defineOption', + ); + }, + [setFieldValue, optionId], + ); const submissionPending = ( tutorialSubmissionStatus === 'started' @@ -307,7 +298,7 @@ function NewTutorial(props: Props) { const sorted = uniqueArray?.sort((a, b) => a.properties?.screen - b.properties.screen); const tutorialTaskArray = sorted?.map((geo) => ( { - scenario: String(geo.properties.screen), + scenarioId: String(geo.properties.screen), hint: {}, instructions: {}, success: {}, @@ -401,39 +392,42 @@ function NewTutorial(props: Props) { {value.projectType === PROJECT_TYPE_FOOTPRINT && ( - Option Instrucitons + Option Instructions - {value.defineOptions?.length ? ( + {value.defineOption?.length ? ( - {value.defineOptions.map((opt) => ( + {value.defineOption.map((opt) => ( - {`Option ${opt.option}`} + {`Option ${opt.optionId}`} ))} - {value.defineOptions.map((options, index) => ( - ( + ))} @@ -455,20 +449,20 @@ function NewTutorial(props: Props) { {value.screens?.map((task) => ( - {`Scenario ${task.scenario}`} + {`Scenario ${task.scenarioId}`} ))} {value.screens?.map((task, index) => ( ))} diff --git a/manager-dashboard/app/views/NewTutorial/styles.css b/manager-dashboard/app/views/NewTutorial/styles.css index 6fe87ab92..c06bdcc9b 100644 --- a/manager-dashboard/app/views/NewTutorial/styles.css +++ b/manager-dashboard/app/views/NewTutorial/styles.css @@ -16,7 +16,11 @@ .card { display: flex; flex-direction: column; - gap: var(--spacing-extra-large); + gap: var(--spacing-large); + + .add-button { + width: fit-content; + } } .input-group { diff --git a/manager-dashboard/app/views/NewTutorial/utils.ts b/manager-dashboard/app/views/NewTutorial/utils.ts index 7d2687af1..c66f01d3f 100644 --- a/manager-dashboard/app/views/NewTutorial/utils.ts +++ b/manager-dashboard/app/views/NewTutorial/utils.ts @@ -23,6 +23,49 @@ import { PROJECT_TYPE_FOOTPRINT, } from '#utils/common'; +export interface IconOptions { + key: string; + label: string; +} + +export const iconOptions: IconOptions[] = [ + { + key: 'swipe-left', + label: 'Swipe Left', + }, + { + key: 'tap-1', + label: 'Tap 1', + }, + { + key: 'tap-2', + label: 'Tap 2', + }, + { + key: 'tap-3', + label: 'Tap 3', + }, + { + key: 'check', + label: 'Check', + }, +]; + +export const iconColorOptions: IconOptions[] = [ + { + key: 'green', + label: 'Green', + }, + { + key: 'red', + label: 'Red', + }, + { + key: 'yellow', + label: 'Yellow', + }, +]; + // FIXME: include here export type TutorialTasks = GeoJSON.FeatureCollection; -export type DefineOptions = { - option: number; +export type DefineOption = { + optionId: number; title: string; description: string; icon: string; iconColor: string; - reasons: { - reason: number; + reason: { + reasonId: number; description: string; - icon: string; }[]; }[]; @@ -56,7 +98,7 @@ export interface TutorialFormType { name: string; tileServer: TileServer; screens: { - scenario: string; + scenarioId: string; hint: { description: string; icon: string; @@ -79,7 +121,7 @@ export interface TutorialFormType { projectType: ProjectType; tileServerB?: TileServer, zoomLevel?: number; - defineOptions?: DefineOptions; + defineOption?: DefineOption; } export type PartialTutorialFormType = PartialForm< @@ -88,25 +130,25 @@ export type PartialTutorialFormType = PartialForm< exampleImage2?: File; }, // NOTE: we do not want to change File and FeatureCollection to partials - 'scenario' | 'tutorialTasks' | 'exampleImage1' | 'exampleImage2' | 'option' | 'reason' + 'tutorialTasks' | 'exampleImage1' | 'exampleImage2' | 'scenarioId' | 'optionId' | 'reasonId' >; type TutorialFormSchema = ObjectSchema; type TutorialFormSchemaFields = ReturnType; -type ScreenType = NonNullable[number]; +export type ScreenType = NonNullable[number]; type ScreenSchema = ObjectSchema; type ScreenFormSchemaFields = ReturnType; type ScreenFormSchema = ArraySchema; type ScreenFormSchemaMember = ReturnType; -type DefineOptionsType = NonNullable[number]; -type DefineOptionsSchema = ObjectSchema; -type DefineOptionsSchemaFields = ReturnType +export type DefineOptionType = NonNullable[number]; +type DefineOptionSchema = ObjectSchema; +type DefineOptionSchemaFields = ReturnType -type DefineOptionsFormSchema = ArraySchema; -type DefineOptionsFormSchemaMember = ReturnType; +type DefineOptionFormSchema = ArraySchema; +type DefineOptionFormSchemaMember = ReturnType; export const tutorialFormSchema: TutorialFormSchema = { fields: (value): TutorialFormSchemaFields => { @@ -129,10 +171,10 @@ export const tutorialFormSchema: TutorialFormSchema = { fields: tileServerFieldsSchema, }, screens: { - keySelector: (key) => key.scenario, + keySelector: (key) => key.scenarioId, member: (): ScreenFormSchemaMember => ({ fields: (): ScreenFormSchemaFields => ({ - scenario: { + scenarioId: { required: true, }, hint: { @@ -192,13 +234,13 @@ export const tutorialFormSchema: TutorialFormSchema = { baseSchema, value, ['projectType'], - ['defineOptions'], + ['defineOption'], (v) => { - const defineOptionsField: DefineOptionsFormSchema = { - keySelector: (key) => key.option, - member: (): DefineOptionsFormSchemaMember => ({ - fields: (): DefineOptionsSchemaFields => ({ - option: { + const defineOptionField: DefineOptionFormSchema = { + keySelector: (key) => key.optionId, + member: (): DefineOptionFormSchemaMember => ({ + fields: (): DefineOptionSchemaFields => ({ + optionId: { required: true, }, title: { @@ -217,11 +259,11 @@ export const tutorialFormSchema: TutorialFormSchema = { iconColor: { required: true, }, - reasons: { - keySelector: (key) => key.reason, + reason: { + keySelector: (key) => key.reasonId, member: () => ({ fields: () => ({ - reason: { + reasonId: { required: true, }, description: { @@ -229,9 +271,6 @@ export const tutorialFormSchema: TutorialFormSchema = { requiredValidation: requiredStringCondition, validations: [getNoMoreThanNCharacterCondition(500)], }, - icon: { - required: true, - }, }), }), }, @@ -241,11 +280,11 @@ export const tutorialFormSchema: TutorialFormSchema = { if (v?.projectType === PROJECT_TYPE_FOOTPRINT) { return { - defineOptions: defineOptionsField, + defineOption: defineOptionField, }; } return { - defineOptions: { forceValue: nullValue }, + defineOption: { forceValue: nullValue }, }; }, ); From d2d21787ef918fbf40f08d6a86ca9f4244be8b97 Mon Sep 17 00:00:00 2001 From: shreeyash07 Date: Wed, 7 Jun 2023 17:06:48 +0545 Subject: [PATCH 11/72] Add page information form and schema --- .../views/NewTutorial/DefineOption/index.tsx | 24 +-- .../InformationPage/Block/index.tsx | 54 ++++++ .../InformationPage/Block/styles.css | 0 .../NewTutorial/InformationPage/index.tsx | 74 ++++++++ .../NewTutorial/InformationPage/styles.css | 3 + .../views/NewTutorial/ScenarioInput/index.tsx | 6 +- .../app/views/NewTutorial/index.tsx | 168 ++++++++++++++++-- .../app/views/NewTutorial/utils.ts | 78 +++++++- 8 files changed, 376 insertions(+), 31 deletions(-) create mode 100644 manager-dashboard/app/views/NewTutorial/InformationPage/Block/index.tsx create mode 100644 manager-dashboard/app/views/NewTutorial/InformationPage/Block/styles.css create mode 100644 manager-dashboard/app/views/NewTutorial/InformationPage/index.tsx create mode 100644 manager-dashboard/app/views/NewTutorial/InformationPage/styles.css diff --git a/manager-dashboard/app/views/NewTutorial/DefineOption/index.tsx b/manager-dashboard/app/views/NewTutorial/DefineOption/index.tsx index d2b9f7501..fce0bfa2d 100644 --- a/manager-dashboard/app/views/NewTutorial/DefineOption/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/DefineOption/index.tsx @@ -49,7 +49,6 @@ export default function DefineOption(props: Props) { } = props; const [activeReason, setActiveReason] = React.useState('1'); - const [reasonId, setReasonId] = React.useState(0); const onOptionChange = useFormObject(index, onChange, defaultDefineOptionValue); @@ -67,19 +66,24 @@ export default function DefineOption(props: Props) { const handleReasonAdd = React.useCallback( () => { - const newReasonId = reasonId + 1; - setReasonId(newReasonId); - const newReason : ReasonType = { - reasonId: newReasonId, - }; onOptionChange( - (oldValue: PartialDefineOptionType['reason']) => ( - [...(oldValue ?? []), newReason] - ), + (oldValue: PartialDefineOptionType['reason']) => { + const safeOldValue = oldValue ?? []; + + const newReasonId = safeOldValue.length > 0 + ? Math.max(...safeOldValue.map((reason) => reason.reasonId)) + 1 + : 1; + + const newReason: ReasonType = { + reasonId: newReasonId, + }; + + return [...safeOldValue, newReason]; + }, 'reason', ); }, - [onOptionChange, reasonId], + [onOptionChange], ); return ( diff --git a/manager-dashboard/app/views/NewTutorial/InformationPage/Block/index.tsx b/manager-dashboard/app/views/NewTutorial/InformationPage/Block/index.tsx new file mode 100644 index 000000000..daca26f5e --- /dev/null +++ b/manager-dashboard/app/views/NewTutorial/InformationPage/Block/index.tsx @@ -0,0 +1,54 @@ +import React from 'react'; +import { + SetValueArg, + Error, + useFormObject, + getErrorObject, +} from '@togglecorp/toggle-form'; + +import { PartialBlocksType } from '#views/NewTutorial/utils'; +import FileInput from '#components/FileInput'; +import TextInput from '#components/TextInput'; + +type PartialBlockType = NonNullable[number]; +interface Props { + value: PartialBlockType; + onChange: (value: SetValueArg, index: number) => void; + index: number; + error: Error | undefined; +} + +export default function Block(props: Props) { + const { + value, + onChange, + index, + error: riskyError, + } = props; + + const onBlockChange = useFormObject(index, onChange, { block: 1 }); + + const error = getErrorObject(riskyError); + return ( +
+ + +
+ ); +} diff --git a/manager-dashboard/app/views/NewTutorial/InformationPage/Block/styles.css b/manager-dashboard/app/views/NewTutorial/InformationPage/Block/styles.css new file mode 100644 index 000000000..e69de29bb diff --git a/manager-dashboard/app/views/NewTutorial/InformationPage/index.tsx b/manager-dashboard/app/views/NewTutorial/InformationPage/index.tsx new file mode 100644 index 000000000..7a0781cd4 --- /dev/null +++ b/manager-dashboard/app/views/NewTutorial/InformationPage/index.tsx @@ -0,0 +1,74 @@ +import React from 'react'; +import { + SetValueArg, + Error, + useFormObject, + getErrorObject, + useFormArray, +} from '@togglecorp/toggle-form'; + +import { TabPanel } from '#components/Tabs'; +import TextInput from '#components/TextInput'; +import Button from '#components/Button'; + +import { InformationPageType } from '../utils'; +import Block from './Block'; + +interface Props { + value: InformationPageType, + onChange: (value: SetValueArg, index: number) => void; + onRemove: (index: number) => void; + index: number, + error: Error | undefined; +} + +export default function InformationPage(props: Props) { + const { + value, + onChange, + onRemove, + index, + error: riskyError, + } = props; + + const onInformationPageChange = useFormObject(index, onChange, { page: 1 }); + + const { + setValue: onChangeBlock, + } = useFormArray('blocks', onInformationPageChange); + + const error = getErrorObject(riskyError); + + const blockError = React.useMemo( + () => getErrorObject(error?.blocks), + [error?.blocks], + ); + + return ( + + + {value.blocks?.map((block, i) => ( + + ))} + + + ); +} diff --git a/manager-dashboard/app/views/NewTutorial/InformationPage/styles.css b/manager-dashboard/app/views/NewTutorial/InformationPage/styles.css new file mode 100644 index 000000000..aad82becd --- /dev/null +++ b/manager-dashboard/app/views/NewTutorial/InformationPage/styles.css @@ -0,0 +1,3 @@ +.information { + display: flex; +} \ No newline at end of file diff --git a/manager-dashboard/app/views/NewTutorial/ScenarioInput/index.tsx b/manager-dashboard/app/views/NewTutorial/ScenarioInput/index.tsx index ea77c4908..8d27831b9 100644 --- a/manager-dashboard/app/views/NewTutorial/ScenarioInput/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/ScenarioInput/index.tsx @@ -43,20 +43,20 @@ const defaultScenarioTabsValue: PartialScenarioType = { scenarioId: 'xxx', }; -interface ScenarioTabsProps { +interface Props { value: PartialScenarioType, onChange: (value: SetValueArg, index: number) => void; index: number, error: Error | undefined; } -export default function ScenarioInput(scenarioProps: ScenarioTabsProps) { +export default function ScenarioInput(props: Props) { const { value, onChange, index, error: riskyError, - } = scenarioProps; + } = props; const onFieldChange = useFormObject(index, onChange, defaultScenarioTabsValue); diff --git a/manager-dashboard/app/views/NewTutorial/index.tsx b/manager-dashboard/app/views/NewTutorial/index.tsx index 022366e23..55db465f3 100644 --- a/manager-dashboard/app/views/NewTutorial/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/index.tsx @@ -39,7 +39,7 @@ import Modal from '#components/Modal'; import TextInput from '#components/TextInput'; import NumberInput from '#components/NumberInput'; import SegmentInput from '#components/SegmentInput'; -import FileInput from '#components/FileInput'; +// import FileInput from '#components/FileInput'; import GeoJsonFileInput from '#components/GeoJsonFileInput'; import { Tabs, @@ -52,6 +52,7 @@ import TileServerInput, { } from '#components/TileServerInput'; import InputSection from '#components/InputSection'; import Button from '#components/Button'; +import Heading from '#components/Heading'; import { valueSelector, labelSelector, @@ -67,11 +68,24 @@ import { PartialTutorialFormType, ScreenType, DefineOptionType, + pageOptions, + PageTemplateType, + InformationPageType, + PartialInformationPageType, + PartialBlocksType, } from './utils'; import DefineOption from './DefineOption'; import ScenarioInput from './ScenarioInput'; +import InformationPage from './InformationPage'; import styles from './styles.css'; -import Heading from '#components/Heading'; +import SelectInput from '#components/SelectInput'; + +function pageKeySelector(d: PageTemplateType) { + return d.key; +} +function pageLabelSelector(d: PageTemplateType) { + return d.label; +} const defaultDefineOption: PartialTutorialFormType['defineOption'] = [ { @@ -84,7 +98,7 @@ const defaultDefineOption: PartialTutorialFormType['defineOption'] = [ }, { optionId: 3, - title: 'Not sure', + title: 'Not Sure', }, ]; const defaultTutorialFormValue: PartialTutorialFormType = { @@ -129,10 +143,12 @@ function NewTutorial(props: Props) { setTutorialSubmissionStatus, ] = React.useState<'started' | 'imageUpload' | 'tutorialSubmit' | 'success' | 'failed' | undefined>(); + // NOTE: scenario const [activeTab, setActiveTab] = React.useState('Scenario 1'); + // NOTE: options const [activeOptionsTab, setActiveOptionsTab] = React.useState('1'); - - const [optionId, setOptionId] = React.useState(defaultDefineOption?.length ?? 3); + // NOTE: Information Page + const [activeInformationPage, setActiveInformationPage] = React.useState('1'); const error = React.useMemo( () => getErrorObject(formError), @@ -148,6 +164,11 @@ function NewTutorial(props: Props) { [error?.defineOption], ); + const informationPageError = React.useMemo( + () => getErrorObject(error?.informationPage), + [error?.informationPage], + ); + const handleFormSubmission = React.useCallback(( finalValuesFromProps: PartialTutorialFormType, ) => { @@ -260,21 +281,35 @@ function NewTutorial(props: Props) { DefineOptionType >('defineOption', setFieldValue); + const { + setValue: onInformationPageAdd, + removeValue: onInformationPageRemove, + } = useFormArray< + 'informationPage', + InformationPageType + >('informationPage', setFieldValue); + const handleAddDefineOptions = React.useCallback( () => { - const newOptionId = optionId + 1; - setOptionId(newOptionId); - const newDefineOption: DefineOptionType = { - optionId: newOptionId, - }; setFieldValue( - (oldValue: PartialTutorialFormType['defineOption']) => ( - [...(oldValue ?? []), newDefineOption] - ), + (oldValue: PartialTutorialFormType['defineOption']) => { + const safeOldValues = oldValue ?? []; + + const newOptionId = safeOldValues.length > 0 + ? Math.max(...safeOldValues.map((option) => option.optionId)) + 1 + : 1; + + const newDefineOption: DefineOptionType = { + optionId: newOptionId, + }; + + return [...safeOldValues, newDefineOption]; + }, 'defineOption', ); + // TODO: Set the new option as selected }, - [setFieldValue, optionId], + [setFieldValue], ); const submissionPending = ( @@ -308,6 +343,62 @@ function NewTutorial(props: Props) { setFieldValue(tutorialTaskArray, 'screens'); }, [setFieldValue]); + const handleAddInformationPage = React.useCallback( + (template: PageTemplateType['key']) => { + setFieldValue( + (oldValue: PartialInformationPageType) => { + const newOldValue = oldValue ?? []; + let blocks: PartialBlocksType = []; + + const newPage = newOldValue.length > 0 + ? Math.max(...newOldValue.map((info) => info.page)) + 1 + : 1; + + if (template === '2-picture') { + blocks = [ + { + block: 1, + }, + { + block: 2, + }, + ]; + } + if (template === '3-picture') { + blocks = [ + { + block: 1, + }, + { + block: 2, + }, + { + block: 3, + }, + ]; + } + if (template === '1-picture') { + blocks = [ + { + block: 1, + }, + ]; + } + + const newPageInformation: InformationPageType = { + page: newPage, + blocks, + }; + return [...newOldValue, newPageInformation]; + }, + 'informationPage', + ); + }, + [setFieldValue], + ); + + console.log('value', value.informationPage); + return (
@@ -365,13 +456,14 @@ function NewTutorial(props: Props) { disabled={submissionPending} /> - + {/* + */} + + + {value.informationPage?.length ? ( + + + {value.informationPage.map((info) => ( + + {`Intro ${info.page}`} + + ))} + + {value.informationPage?.map((page, i) => ( + + ))} + + ) : ( +
Add Page
+ )}
{value.projectType === PROJECT_TYPE_FOOTPRINT && ( ; type TutorialFormSchema = ObjectSchema; @@ -150,6 +184,16 @@ type DefineOptionSchemaFields = ReturnType type DefineOptionFormSchema = ArraySchema; type DefineOptionFormSchemaMember = ReturnType; +export type InformationPageType = NonNullable[number]; +type InformationPageSchema = ObjectSchema; +type InformationPageSchemaFields = ReturnType + +type InformationPageFormSchema = ArraySchema; +type InformationPageFormSchemaMember = ReturnType; + +export type PartialInformationPageType = PartialTutorialFormType['informationPage']; +export type PartialBlocksType = NonNullable[number]>['blocks']; + export const tutorialFormSchema: TutorialFormSchema = { fields: (value): TutorialFormSchemaFields => { let baseSchema: TutorialFormSchemaFields = { @@ -225,6 +269,38 @@ export const tutorialFormSchema: TutorialFormSchema = { }), }), }, + informationPage: { + keySelector: (key) => key.page, + member: (): InformationPageFormSchemaMember => ({ + fields: (): InformationPageSchemaFields => ({ + templeteType: { required: true }, + page: { required: true }, + title: { + required: true, + requiredValidation: requiredStringCondition, + validations: [getNoMoreThanNCharacterCondition(500)], + }, + blocks: { + keySelector: (key) => key.block, + member: () => ({ + fields: () => ({ + block: { + required: true, + }, + image: { + required: true, + }, + description: { + required: true, + requiredValidation: requiredStringCondition, + validations: [getNoMoreThanNCharacterCondition(1000)], + }, + }), + }), + }, + }), + }), + }, // FIXME: add validation for tutorialTasks (geojson) tutorialTasks: { required: true }, exampleImage1: { required: true }, From 6eb04947c76a951bf9ef0d9294bea775921540c0 Mon Sep 17 00:00:00 2001 From: shreeyash07 Date: Fri, 9 Jun 2023 10:07:12 +0545 Subject: [PATCH 12/72] Add block in information form --- .../InformationPage/Block/index.tsx | 42 ++++++----- .../InformationPage/Block/styles.css | 3 + .../app/views/NewTutorial/index.tsx | 32 ++++++++- .../app/views/NewTutorial/utils.ts | 69 ++++++++++++------- 4 files changed, 101 insertions(+), 45 deletions(-) diff --git a/manager-dashboard/app/views/NewTutorial/InformationPage/Block/index.tsx b/manager-dashboard/app/views/NewTutorial/InformationPage/Block/index.tsx index daca26f5e..3b45d42b4 100644 --- a/manager-dashboard/app/views/NewTutorial/InformationPage/Block/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/InformationPage/Block/index.tsx @@ -10,6 +10,8 @@ import { PartialBlocksType } from '#views/NewTutorial/utils'; import FileInput from '#components/FileInput'; import TextInput from '#components/TextInput'; +import styles from './styles.css'; + type PartialBlockType = NonNullable[number]; interface Props { value: PartialBlockType; @@ -26,29 +28,31 @@ export default function Block(props: Props) { error: riskyError, } = props; - const onBlockChange = useFormObject(index, onChange, { block: 1 }); + const onBlockChange = useFormObject(index, onChange, { block: 1, blockType: 'text' }); const error = getErrorObject(riskyError); return (
- - + {value.blockType === 'image' ? ( + + ) : ( + + )}
); } diff --git a/manager-dashboard/app/views/NewTutorial/InformationPage/Block/styles.css b/manager-dashboard/app/views/NewTutorial/InformationPage/Block/styles.css index e69de29bb..2add2c32c 100644 --- a/manager-dashboard/app/views/NewTutorial/InformationPage/Block/styles.css +++ b/manager-dashboard/app/views/NewTutorial/InformationPage/Block/styles.css @@ -0,0 +1,3 @@ +.block { + display: flex; +} \ No newline at end of file diff --git a/manager-dashboard/app/views/NewTutorial/index.tsx b/manager-dashboard/app/views/NewTutorial/index.tsx index 55db465f3..4cb876490 100644 --- a/manager-dashboard/app/views/NewTutorial/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/index.tsx @@ -358,9 +358,19 @@ function NewTutorial(props: Props) { blocks = [ { block: 1, + blockType: 'image', }, { block: 2, + blockType: 'text', + }, + { + block: 3, + blockType: 'image', + }, + { + block: 4, + blockType: 'text', }, ]; } @@ -368,12 +378,27 @@ function NewTutorial(props: Props) { blocks = [ { block: 1, + blockType: 'image', }, { block: 2, + blockType: 'text', }, { block: 3, + blockType: 'image', + }, + { + block: 4, + blockType: 'text', + }, + { + block: 5, + blockType: 'image', + }, + { + block: 6, + blockType: 'text', }, ]; } @@ -381,6 +406,11 @@ function NewTutorial(props: Props) { blocks = [ { block: 1, + blockType: 'image', + }, + { + block: 2, + blockType: 'text', }, ]; } @@ -487,7 +517,7 @@ function NewTutorial(props: Props) { > ; type TutorialFormSchema = ObjectSchema; @@ -184,7 +183,7 @@ type DefineOptionSchemaFields = ReturnType type DefineOptionFormSchema = ArraySchema; type DefineOptionFormSchemaMember = ReturnType; -export type InformationPageType = NonNullable[number]; +export type InformationPageType = NonNullable[number] type InformationPageSchema = ObjectSchema; type InformationPageSchemaFields = ReturnType @@ -273,7 +272,6 @@ export const tutorialFormSchema: TutorialFormSchema = { keySelector: (key) => key.page, member: (): InformationPageFormSchemaMember => ({ fields: (): InformationPageSchemaFields => ({ - templeteType: { required: true }, page: { required: true }, title: { required: true, @@ -283,29 +281,50 @@ export const tutorialFormSchema: TutorialFormSchema = { blocks: { keySelector: (key) => key.block, member: () => ({ - fields: () => ({ - block: { - required: true, - }, - image: { - required: true, - }, - description: { - required: true, - requiredValidation: requiredStringCondition, - validations: [getNoMoreThanNCharacterCondition(1000)], - }, - }), + fields: (blockValue) => { + let fields = { + block: { required: true }, + blockType: { required: true }, + }; + + fields = addCondition( + fields, + blockValue, + ['blockType'], + ['imageFile', 'textDescription'], + (v) => { + if (v?.blockType === 'text') { + return { + textDescription: { + required: true, + // eslint-disable-next-line max-len + requiredValidations: requiredStringCondition, + // eslint-disable-next-line max-len + validations: [getNoMoreThanNCharacterCondition(2000)], + }, + imageFile: { forceValue: nullValue }, + }; + } + return { + imageFile: { required: true }, + textDescription: { forceValue: nullValue }, + + }; + }, + ); + return fields; + }, }), }, }), }), }, - // FIXME: add validation for tutorialTasks (geojson) - tutorialTasks: { required: true }, - exampleImage1: { required: true }, - exampleImage2: { required: true }, + // FIXME: we do not send this anymore + tutorialTasks: {}, + exampleImage1: {}, + exampleImage2: {}, }; + baseSchema = addCondition( baseSchema, value, From 7b71ef377ff40b90822cc6d24cfdd92edd1b6d65 Mon Sep 17 00:00:00 2001 From: frozenhelium Date: Fri, 9 Jun 2023 11:05:16 +0545 Subject: [PATCH 13/72] Rename define option to custom options --- .../Reason/index.tsx | 0 .../Reason/styles.css | 0 .../index.tsx | 11 +- .../styles.css | 0 .../InformationPage/Block/index.tsx | 4 +- .../NewTutorial/InformationPage/index.tsx | 7 +- .../app/views/NewTutorial/index.tsx | 200 +++++++++--------- .../app/views/NewTutorial/utils.ts | 40 ++-- 8 files changed, 142 insertions(+), 120 deletions(-) rename manager-dashboard/app/views/NewTutorial/{DefineOption => CustomOptionInput}/Reason/index.tsx (100%) rename manager-dashboard/app/views/NewTutorial/{DefineOption => CustomOptionInput}/Reason/styles.css (100%) rename manager-dashboard/app/views/NewTutorial/{DefineOption => CustomOptionInput}/index.tsx (93%) rename manager-dashboard/app/views/NewTutorial/{DefineOption => CustomOptionInput}/styles.css (100%) diff --git a/manager-dashboard/app/views/NewTutorial/DefineOption/Reason/index.tsx b/manager-dashboard/app/views/NewTutorial/CustomOptionInput/Reason/index.tsx similarity index 100% rename from manager-dashboard/app/views/NewTutorial/DefineOption/Reason/index.tsx rename to manager-dashboard/app/views/NewTutorial/CustomOptionInput/Reason/index.tsx diff --git a/manager-dashboard/app/views/NewTutorial/DefineOption/Reason/styles.css b/manager-dashboard/app/views/NewTutorial/CustomOptionInput/Reason/styles.css similarity index 100% rename from manager-dashboard/app/views/NewTutorial/DefineOption/Reason/styles.css rename to manager-dashboard/app/views/NewTutorial/CustomOptionInput/Reason/styles.css diff --git a/manager-dashboard/app/views/NewTutorial/DefineOption/index.tsx b/manager-dashboard/app/views/NewTutorial/CustomOptionInput/index.tsx similarity index 93% rename from manager-dashboard/app/views/NewTutorial/DefineOption/index.tsx rename to manager-dashboard/app/views/NewTutorial/CustomOptionInput/index.tsx index fce0bfa2d..1e67201c5 100644 --- a/manager-dashboard/app/views/NewTutorial/DefineOption/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/CustomOptionInput/index.tsx @@ -12,6 +12,7 @@ import Button from '#components/Button'; import Heading from '#components/Heading'; import Tabs, { TabList, TabPanel, Tab } from '#components/Tabs'; import TextInput from '#components/TextInput'; +import NumberInput from '#components/NumberInput'; import SelectInput from '#components/SelectInput'; import Reason from './Reason'; @@ -88,7 +89,7 @@ export default function DefineOption(props: Props) { return (
@@ -100,6 +101,14 @@ export default function DefineOption(props: Props) { onChange={onOptionChange} error={error?.title} /> + [number]; interface Props { @@ -28,7 +28,7 @@ export default function Block(props: Props) { error: riskyError, } = props; - const onBlockChange = useFormObject(index, onChange, { block: 1, blockType: 'text' }); + const onBlockChange = useFormObject(index, onChange, { blockNumber: 1, blockType: 'text' }); const error = getErrorObject(riskyError); return ( diff --git a/manager-dashboard/app/views/NewTutorial/InformationPage/index.tsx b/manager-dashboard/app/views/NewTutorial/InformationPage/index.tsx index 7a0781cd4..52ab2c2df 100644 --- a/manager-dashboard/app/views/NewTutorial/InformationPage/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/InformationPage/index.tsx @@ -31,7 +31,7 @@ export default function InformationPage(props: Props) { error: riskyError, } = props; - const onInformationPageChange = useFormObject(index, onChange, { page: 1 }); + const onInformationPageChange = useFormObject(index, onChange, { page_number: 1 }); const { setValue: onChangeBlock, @@ -46,7 +46,7 @@ export default function InformationPage(props: Props) { return ( {value.blocks?.map((block, i) => ( ))} - {value.defineOption?.length ? ( + {value.customOptions?.length ? ( - {value.defineOption.map((opt) => ( + {value.customOptions.map((opt) => ( ))} - {value.defineOption.map((options, index) => ( - ( + )} - - - {value.screens?.length ? ( - <> - - {value.screens?.map((task) => ( - - {`Scenario ${task.scenarioId}`} - - ))} - - {value.screens?.map((task, index) => ( - - ))} - - ) : ( -
No Scenarios
- )} -
-
{(value?.projectType === PROJECT_TYPE_BUILD_AREA || value?.projectType === PROJECT_TYPE_CHANGE_DETECTION diff --git a/manager-dashboard/app/views/NewTutorial/utils.ts b/manager-dashboard/app/views/NewTutorial/utils.ts index 7546e0e27..5920017c5 100644 --- a/manager-dashboard/app/views/NewTutorial/utils.ts +++ b/manager-dashboard/app/views/NewTutorial/utils.ts @@ -101,9 +101,10 @@ export type TutorialTasks = GeoJSON.FeatureCollection; -export type DefineOption = { +export type CustomOptions = { optionId: number; title: string; + value: number; description: string; icon: string; iconColor: string; @@ -114,10 +115,10 @@ export type DefineOption = { }[]; export type InformationPage = { - page: number; + pageNumber: number; title: string; blocks: { - block: number; + blockNumber: number; blockType: 'image' | 'text'; imageFile?: File; textDescription?: string; @@ -152,7 +153,7 @@ export interface TutorialFormType { projectType: ProjectType; tileServerB?: TileServer, zoomLevel?: number; - defineOption?: DefineOption; + customOptions?: CustomOptions; informationPage: InformationPage; } @@ -163,7 +164,7 @@ export type PartialTutorialFormType = PartialForm< }, // NOTE: we do not want to change File and FeatureCollection to partials // FIXME: rename page to pageNumber, block to blockNumber - 'image' |'tutorialTasks' | 'exampleImage1' | 'exampleImage2' | 'scenarioId' | 'optionId' | 'reasonId' | 'page' | 'block' | 'blockType' | 'imageFile' + 'image' |'tutorialTasks' | 'exampleImage1' | 'exampleImage2' | 'scenarioId' | 'optionId' | 'reasonId' | 'pageNumber' | 'blockNumber' | 'blockType' | 'imageFile' >; type TutorialFormSchema = ObjectSchema; @@ -176,11 +177,11 @@ type ScreenFormSchemaFields = ReturnType; type ScreenFormSchema = ArraySchema; type ScreenFormSchemaMember = ReturnType; -export type DefineOptionType = NonNullable[number]; -type DefineOptionSchema = ObjectSchema; +export type CustomOptionType = NonNullable[number]; +type DefineOptionSchema = ObjectSchema; type DefineOptionSchemaFields = ReturnType -type DefineOptionFormSchema = ArraySchema; +type DefineOptionFormSchema = ArraySchema; type DefineOptionFormSchemaMember = ReturnType; export type InformationPageType = NonNullable[number] @@ -269,21 +270,21 @@ export const tutorialFormSchema: TutorialFormSchema = { }), }, informationPage: { - keySelector: (key) => key.page, + keySelector: (key) => key.pageNumber, member: (): InformationPageFormSchemaMember => ({ fields: (): InformationPageSchemaFields => ({ - page: { required: true }, + pageNumber: { required: true }, title: { required: true, requiredValidation: requiredStringCondition, validations: [getNoMoreThanNCharacterCondition(500)], }, blocks: { - keySelector: (key) => key.block, + keySelector: (key) => key.blockNumber, member: () => ({ fields: (blockValue) => { let fields = { - block: { required: true }, + blockNumber: { required: true }, blockType: { required: true }, }; @@ -329,9 +330,9 @@ export const tutorialFormSchema: TutorialFormSchema = { baseSchema, value, ['projectType'], - ['defineOption'], - (v) => { - const defineOptionField: DefineOptionFormSchema = { + ['customOptions'], + (formValues) => { + const customOptionField: DefineOptionFormSchema = { keySelector: (key) => key.optionId, member: (): DefineOptionFormSchemaMember => ({ fields: (): DefineOptionSchemaFields => ({ @@ -343,6 +344,9 @@ export const tutorialFormSchema: TutorialFormSchema = { requiredValidation: requiredStringCondition, validations: [getNoMoreThanNCharacterCondition(500)], }, + value: { + required: true, + }, description: { required: true, requiredValidation: requiredStringCondition, @@ -373,13 +377,13 @@ export const tutorialFormSchema: TutorialFormSchema = { }), }; - if (v?.projectType === PROJECT_TYPE_FOOTPRINT) { + if (formValues?.projectType === PROJECT_TYPE_FOOTPRINT) { return { - defineOption: defineOptionField, + customOptions: customOptionField, }; } return { - defineOption: { forceValue: nullValue }, + customOptions: { forceValue: nullValue }, }; }, ); From bb3b238adbc65730a5c70f6fc289fe2952fc5214 Mon Sep 17 00:00:00 2001 From: shreeyash07 Date: Fri, 9 Jun 2023 11:50:01 +0545 Subject: [PATCH 14/72] Rename reason to sub option --- .../{Reason => SubOption}/index.tsx | 16 ++-- .../{Reason => SubOption}/styles.css | 0 .../NewTutorial/CustomOptionInput/index.tsx | 84 +++++++++---------- .../NewTutorial/InformationPage/index.tsx | 12 +-- .../app/views/NewTutorial/index.tsx | 76 +++++++++-------- .../app/views/NewTutorial/utils.ts | 68 +++++++-------- 6 files changed, 129 insertions(+), 127 deletions(-) rename manager-dashboard/app/views/NewTutorial/CustomOptionInput/{Reason => SubOption}/index.tsx (70%) rename manager-dashboard/app/views/NewTutorial/CustomOptionInput/{Reason => SubOption}/styles.css (100%) diff --git a/manager-dashboard/app/views/NewTutorial/CustomOptionInput/Reason/index.tsx b/manager-dashboard/app/views/NewTutorial/CustomOptionInput/SubOption/index.tsx similarity index 70% rename from manager-dashboard/app/views/NewTutorial/CustomOptionInput/Reason/index.tsx rename to manager-dashboard/app/views/NewTutorial/CustomOptionInput/SubOption/index.tsx index d3cf1e9f0..54f2227f2 100644 --- a/manager-dashboard/app/views/NewTutorial/CustomOptionInput/Reason/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/CustomOptionInput/SubOption/index.tsx @@ -9,19 +9,19 @@ import { TabPanel } from '#components/Tabs'; import TextInput from '#components/TextInput'; import Button from '#components/Button'; -import { PartialDefineOptionType } from '..'; +import { PartialCustomOptionsType } from '..'; import styles from './styles.css'; -type PartialReasonType = NonNullable[number] +type PartialSubOptionType = NonNullable[number] interface Props { - value: PartialReasonType; - onChange: (value: SetValueArg, index: number) => void; + value: PartialSubOptionType; + onChange: (value: SetValueArg, index: number) => void; onRemove: (index: number) => void; index: number; - error: Error | undefined; + error: Error | undefined; } -export default function Reason(props: Props) { +export default function SubOption(props: Props) { const { value, onChange, @@ -30,12 +30,12 @@ export default function Reason(props: Props) { error: riskyError, } = props; - const onReasonChange = useFormObject(index, onChange, { reasonId: 1 }); + const onReasonChange = useFormObject(index, onChange, { subOptionsId: 1 }); const error = getErrorObject(riskyError); return ( [number] -const defaultDefineOptionValue: PartialDefineOptionType = { +export type PartialCustomOptionsType = NonNullable[number] +const defaultcustomOptionsValue: PartialCustomOptionsType = { optionId: 1, }; -type ReasonType = NonNullable[number]; +type subOptionsType = NonNullable[number]; interface Props { - value: PartialDefineOptionType; - onChange: (value: SetValueArg, index: number) => void; + value: PartialCustomOptionsType; + onChange: (value: SetValueArg, index: number) => void; onRemove: (index: number) => void; index: number; - error: Error | undefined; + error: Error | undefined; } -export default function DefineOption(props: Props) { +export default function CustomOptions(props: Props) { const { value, onChange, @@ -49,39 +49,39 @@ export default function DefineOption(props: Props) { error: riskyError, } = props; - const [activeReason, setActiveReason] = React.useState('1'); + const [activesubOptions, setActiveSubOptions] = React.useState('1'); - const onOptionChange = useFormObject(index, onChange, defaultDefineOptionValue); + const onOptionChange = useFormObject(index, onChange, defaultcustomOptionsValue); const { - setValue: onReasonAdd, - removeValue: onReasonRemove, - } = useFormArray('reason', onOptionChange); + setValue: onsubOptionsAdd, + removeValue: onsubOptionsRemove, + } = useFormArray('subOptions', onOptionChange); const error = getErrorObject(riskyError); - const reasonError = React.useMemo( - () => getErrorObject(error?.reason), - [error?.reason], + const subOptionsError = React.useMemo( + () => getErrorObject(error?.subOptions), + [error?.subOptions], ); - const handleReasonAdd = React.useCallback( + const handlesubOptionsAdd = React.useCallback( () => { onOptionChange( - (oldValue: PartialDefineOptionType['reason']) => { + (oldValue: PartialCustomOptionsType['subOptions']) => { const safeOldValue = oldValue ?? []; - const newReasonId = safeOldValue.length > 0 - ? Math.max(...safeOldValue.map((reason) => reason.reasonId)) + 1 + const newsubOptionsId = safeOldValue.length > 0 + ? Math.max(...safeOldValue.map((subOptions) => subOptions.subOptionsId)) + 1 : 1; - const newReason: ReasonType = { - reasonId: newReasonId, + const newsubOptions: subOptionsType = { + subOptionsId: newsubOptionsId, }; - return [...safeOldValue, newReason]; + return [...safeOldValue, newsubOptions]; }, - 'reason', + 'subOptions', ); }, [onOptionChange], @@ -150,44 +150,44 @@ export default function DefineOption(props: Props) { Delete this option - Reason + Sub Options - {value.reason?.length ? ( + {value.subOptions?.length ? ( - {value.reason?.map((rea) => ( + {value.subOptions?.map((rea) => ( - {`Reason ${rea.reasonId}`} + {`Sub Options ${rea.subOptionsId}`} ))} - {value.reason.map((rea, i) => ( - ( + ))} ) : ( -
Add reason
+
Add Sub Options
)}
diff --git a/manager-dashboard/app/views/NewTutorial/InformationPage/index.tsx b/manager-dashboard/app/views/NewTutorial/InformationPage/index.tsx index 52ab2c2df..7a87491fb 100644 --- a/manager-dashboard/app/views/NewTutorial/InformationPage/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/InformationPage/index.tsx @@ -11,15 +11,15 @@ import { TabPanel } from '#components/Tabs'; import TextInput from '#components/TextInput'; import Button from '#components/Button'; -import { InformationPageType } from '../utils'; +import { InformationPagesType } from '../utils'; import Block from './Block'; interface Props { - value: InformationPageType, - onChange: (value: SetValueArg, index: number) => void; + value: InformationPagesType, + onChange: (value: SetValueArg, index: number) => void; onRemove: (index: number) => void; index: number, - error: Error | undefined; + error: Error | undefined; } export default function InformationPage(props: Props) { @@ -31,7 +31,7 @@ export default function InformationPage(props: Props) { error: riskyError, } = props; - const onInformationPageChange = useFormObject(index, onChange, { page_number: 1 }); + const onInformationPageChange = useFormObject(index, onChange, { pageNumber: 1 }); const { setValue: onChangeBlock, @@ -46,7 +46,7 @@ export default function InformationPage(props: Props) { return ( getErrorObject(formError), [formError], ); const scenarioError = React.useMemo( - () => getErrorObject(error?.screens), - [error?.screens], + () => getErrorObject(error?.scenarioPages), + [error?.scenarioPages], ); const optionsError = React.useMemo( @@ -173,9 +173,9 @@ function NewTutorial(props: Props) { [error?.customOptions], ); - const informationPageError = React.useMemo( - () => getErrorObject(error?.informationPage), - [error?.informationPage], + const informationPagesError = React.useMemo( + () => getErrorObject(error?.informationPages), + [error?.informationPages], ); const handleFormSubmission = React.useCallback(( @@ -184,11 +184,11 @@ function NewTutorial(props: Props) { const userId = user?.id; const finalValues = finalValuesFromProps as TutorialFormType; - type Screens = typeof finalValues.screens; + type Screens = typeof finalValues.scenarioPages; type Screen = Screens[number]; type CustomScreen = Omit; - const newScreens = finalValues.screens.reduce( + const screens = finalValues.scenarioPages.reduce( (acc, currentValue) => { const { scenarioId, ...other } = currentValue; acc[scenarioId] = { @@ -200,7 +200,7 @@ function NewTutorial(props: Props) { {} as Record, ); - console.info(newScreens, finalValues); + console.info(screens, finalValues); if (finalValues) { return; @@ -295,9 +295,9 @@ function NewTutorial(props: Props) { const { setValue: onScenarioFormChange, } = useFormArray< - 'screens', - ScreenType - >('screens', setFieldValue); + 'scenarioPages', + ScenarioPagesType + >('scenarioPages', setFieldValue); const hasErrors = React.useMemo( () => analyzeErrors(error), @@ -313,12 +313,12 @@ function NewTutorial(props: Props) { >('customOptions', setFieldValue); const { - setValue: onInformationPageAdd, - removeValue: onInformationPageRemove, + setValue: onInformationPagesAdd, + removeValue: onInformationPagesRemove, } = useFormArray< - 'informationPage', - InformationPageType - >('informationPage', setFieldValue); + 'informationPages', + InformationPagesType + >('informationPages', setFieldValue); const handleAddDefineOptions = React.useCallback( () => { @@ -376,13 +376,13 @@ function NewTutorial(props: Props) { } )); - setFieldValue(tutorialTaskArray, 'screens'); + setFieldValue(tutorialTaskArray, 'scenarioPages'); }, [setFieldValue]); - const handleAddInformationPage = React.useCallback( + const handleAddInformationPages = React.useCallback( (template: PageTemplateType['key']) => { setFieldValue( - (oldValue: PartialInformationPageType) => { + (oldValue: PartialInformationPagesType) => { const newOldValue = oldValue ?? []; let blocks: PartialBlocksType = []; @@ -451,13 +451,13 @@ function NewTutorial(props: Props) { ]; } - const newPageInformation: InformationPageType = { + const newPageInformation: InformationPagesType = { pageNumber: newPage, blocks, }; return [...newOldValue, newPageInformation]; }, - 'informationPage', + 'informationPages', ); }, [setFieldValue], @@ -528,10 +528,10 @@ function NewTutorial(props: Props) { value={activeTab} onChange={setActiveTab} > - {value.screens?.length ? ( + {value.scenarioPages?.length ? ( <> - {value.screens?.map((task) => ( + {value.scenarioPages?.map((task) => ( ))} - {value.screens?.map((task, index) => ( + {value.scenarioPages?.map((task, index) => ( - {value.informationPage?.length ? ( + {value.informationPages?.length ? ( - {value.informationPage.map((info) => ( + {value.informationPages.map((info) => ( {`Intro ${info.pageNumber}`} ))} - {value.informationPage?.map((page, i) => ( + {value.informationPages?.map((page, i) => ( ))} diff --git a/manager-dashboard/app/views/NewTutorial/utils.ts b/manager-dashboard/app/views/NewTutorial/utils.ts index 5920017c5..d4d2667eb 100644 --- a/manager-dashboard/app/views/NewTutorial/utils.ts +++ b/manager-dashboard/app/views/NewTutorial/utils.ts @@ -108,13 +108,13 @@ export type CustomOptions = { description: string; icon: string; iconColor: string; - reason: { - reasonId: number; + subOptions: { + subOptionsId: number; description: string; }[]; }[]; -export type InformationPage = { +export type InformationPages = { pageNumber: number; title: string; blocks: { @@ -129,7 +129,7 @@ export interface TutorialFormType { lookFor: string; name: string; tileServer: TileServer; - screens: { + scenarioPages: { scenarioId: string; hint: { description: string; @@ -154,7 +154,7 @@ export interface TutorialFormType { tileServerB?: TileServer, zoomLevel?: number; customOptions?: CustomOptions; - informationPage: InformationPage; + informationPages: InformationPages; } export type PartialTutorialFormType = PartialForm< @@ -164,35 +164,35 @@ export type PartialTutorialFormType = PartialForm< }, // NOTE: we do not want to change File and FeatureCollection to partials // FIXME: rename page to pageNumber, block to blockNumber - 'image' |'tutorialTasks' | 'exampleImage1' | 'exampleImage2' | 'scenarioId' | 'optionId' | 'reasonId' | 'pageNumber' | 'blockNumber' | 'blockType' | 'imageFile' + 'image' |'tutorialTasks' | 'exampleImage1' | 'exampleImage2' | 'scenarioId' | 'optionId' | 'subOptionsId' | 'pageNumber' | 'blockNumber' | 'blockType' | 'imageFile' >; type TutorialFormSchema = ObjectSchema; type TutorialFormSchemaFields = ReturnType; -export type ScreenType = NonNullable[number]; -type ScreenSchema = ObjectSchema; -type ScreenFormSchemaFields = ReturnType; +export type ScenarioPagesType = NonNullable[number]; +type ScenarioPagesSchema = ObjectSchema; +type ScenarioPagesFormSchemaFields = ReturnType; -type ScreenFormSchema = ArraySchema; -type ScreenFormSchemaMember = ReturnType; +type ScenarioPagesFormSchema = ArraySchema; +type ScenarioPagesFormSchemaMember = ReturnType; export type CustomOptionType = NonNullable[number]; -type DefineOptionSchema = ObjectSchema; -type DefineOptionSchemaFields = ReturnType +type CustomOptionSchema = ObjectSchema; +type CustomOptionSchemaFields = ReturnType -type DefineOptionFormSchema = ArraySchema; -type DefineOptionFormSchemaMember = ReturnType; +type CustomOptionFormSchema = ArraySchema; +type CustomOptionFormSchemaMember = ReturnType; -export type InformationPageType = NonNullable[number] -type InformationPageSchema = ObjectSchema; -type InformationPageSchemaFields = ReturnType +export type InformationPagesType = NonNullable[number] +type InformationPagesSchema = ObjectSchema; +type InformationPagesSchemaFields = ReturnType -type InformationPageFormSchema = ArraySchema; -type InformationPageFormSchemaMember = ReturnType; +type InformationPagesFormSchema = ArraySchema; +type InformationPagesFormSchemaMember = ReturnType; -export type PartialInformationPageType = PartialTutorialFormType['informationPage']; -export type PartialBlocksType = NonNullable[number]>['blocks']; +export type PartialInformationPagesType = PartialTutorialFormType['informationPages']; +export type PartialBlocksType = NonNullable[number]>['blocks']; export const tutorialFormSchema: TutorialFormSchema = { fields: (value): TutorialFormSchemaFields => { @@ -214,10 +214,10 @@ export const tutorialFormSchema: TutorialFormSchema = { tileServer: { fields: tileServerFieldsSchema, }, - screens: { + scenarioPages: { keySelector: (key) => key.scenarioId, - member: (): ScreenFormSchemaMember => ({ - fields: (): ScreenFormSchemaFields => ({ + member: (): ScenarioPagesFormSchemaMember => ({ + fields: (): ScenarioPagesFormSchemaFields => ({ scenarioId: { required: true, }, @@ -269,10 +269,10 @@ export const tutorialFormSchema: TutorialFormSchema = { }), }), }, - informationPage: { + informationPages: { keySelector: (key) => key.pageNumber, - member: (): InformationPageFormSchemaMember => ({ - fields: (): InformationPageSchemaFields => ({ + member: (): InformationPagesFormSchemaMember => ({ + fields: (): InformationPagesSchemaFields => ({ pageNumber: { required: true }, title: { required: true, @@ -332,10 +332,10 @@ export const tutorialFormSchema: TutorialFormSchema = { ['projectType'], ['customOptions'], (formValues) => { - const customOptionField: DefineOptionFormSchema = { + const customOptionField: CustomOptionFormSchema = { keySelector: (key) => key.optionId, - member: (): DefineOptionFormSchemaMember => ({ - fields: (): DefineOptionSchemaFields => ({ + member: (): CustomOptionFormSchemaMember => ({ + fields: (): CustomOptionSchemaFields => ({ optionId: { required: true, }, @@ -358,11 +358,11 @@ export const tutorialFormSchema: TutorialFormSchema = { iconColor: { required: true, }, - reason: { - keySelector: (key) => key.reasonId, + subOptions: { + keySelector: (key) => key.subOptionsId, member: () => ({ fields: () => ({ - reasonId: { + subOptionsId: { required: true, }, description: { From a1b9cedfd0b52215d858a9f5ade86fe9efbbf019 Mon Sep 17 00:00:00 2001 From: frozenhelium Date: Mon, 12 Jun 2023 11:41:29 +0545 Subject: [PATCH 15/72] Improve new tutorial page --- .../app/components/TutorialList/index.tsx | 2 + .../NewTutorial/CustomOptionInput/index.tsx | 2 +- .../Block/index.tsx | 0 .../Block/styles.css | 0 .../index.tsx | 2 +- .../styles.css | 0 .../index.tsx | 2 +- .../styles.css | 0 .../app/views/NewTutorial/index.tsx | 74 +++++++++++-------- .../app/views/NewTutorial/utils.ts | 6 +- 10 files changed, 50 insertions(+), 38 deletions(-) rename manager-dashboard/app/views/NewTutorial/{InformationPage => InformationPageInput}/Block/index.tsx (100%) rename manager-dashboard/app/views/NewTutorial/{InformationPage => InformationPageInput}/Block/styles.css (100%) rename manager-dashboard/app/views/NewTutorial/{InformationPage => InformationPageInput}/index.tsx (96%) rename manager-dashboard/app/views/NewTutorial/{InformationPage => InformationPageInput}/styles.css (100%) rename manager-dashboard/app/views/NewTutorial/{ScenarioInput => ScenarioPageInput}/index.tsx (99%) rename manager-dashboard/app/views/NewTutorial/{ScenarioInput => ScenarioPageInput}/styles.css (100%) diff --git a/manager-dashboard/app/components/TutorialList/index.tsx b/manager-dashboard/app/components/TutorialList/index.tsx index 9ab818f41..9065f87a8 100644 --- a/manager-dashboard/app/components/TutorialList/index.tsx +++ b/manager-dashboard/app/components/TutorialList/index.tsx @@ -53,6 +53,8 @@ function TutorialList(props: Props) { query: tutorialsQuery, }); + console.info(tutorials); + const tutorialList = React.useMemo( () => (tutorials ? Object.entries(tutorials).reverse() : []), [tutorials], diff --git a/manager-dashboard/app/views/NewTutorial/CustomOptionInput/index.tsx b/manager-dashboard/app/views/NewTutorial/CustomOptionInput/index.tsx index 75c1f3f7e..25fab0186 100644 --- a/manager-dashboard/app/views/NewTutorial/CustomOptionInput/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/CustomOptionInput/index.tsx @@ -40,7 +40,7 @@ interface Props { error: Error | undefined; } -export default function CustomOptions(props: Props) { +export default function CustomOptionInput(props: Props) { const { value, onChange, diff --git a/manager-dashboard/app/views/NewTutorial/InformationPage/Block/index.tsx b/manager-dashboard/app/views/NewTutorial/InformationPageInput/Block/index.tsx similarity index 100% rename from manager-dashboard/app/views/NewTutorial/InformationPage/Block/index.tsx rename to manager-dashboard/app/views/NewTutorial/InformationPageInput/Block/index.tsx diff --git a/manager-dashboard/app/views/NewTutorial/InformationPage/Block/styles.css b/manager-dashboard/app/views/NewTutorial/InformationPageInput/Block/styles.css similarity index 100% rename from manager-dashboard/app/views/NewTutorial/InformationPage/Block/styles.css rename to manager-dashboard/app/views/NewTutorial/InformationPageInput/Block/styles.css diff --git a/manager-dashboard/app/views/NewTutorial/InformationPage/index.tsx b/manager-dashboard/app/views/NewTutorial/InformationPageInput/index.tsx similarity index 96% rename from manager-dashboard/app/views/NewTutorial/InformationPage/index.tsx rename to manager-dashboard/app/views/NewTutorial/InformationPageInput/index.tsx index 7a87491fb..958a5ece1 100644 --- a/manager-dashboard/app/views/NewTutorial/InformationPage/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/InformationPageInput/index.tsx @@ -22,7 +22,7 @@ interface Props { error: Error | undefined; } -export default function InformationPage(props: Props) { +export default function InformationPageInput(props: Props) { const { value, onChange, diff --git a/manager-dashboard/app/views/NewTutorial/InformationPage/styles.css b/manager-dashboard/app/views/NewTutorial/InformationPageInput/styles.css similarity index 100% rename from manager-dashboard/app/views/NewTutorial/InformationPage/styles.css rename to manager-dashboard/app/views/NewTutorial/InformationPageInput/styles.css diff --git a/manager-dashboard/app/views/NewTutorial/ScenarioInput/index.tsx b/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/index.tsx similarity index 99% rename from manager-dashboard/app/views/NewTutorial/ScenarioInput/index.tsx rename to manager-dashboard/app/views/NewTutorial/ScenarioPageInput/index.tsx index 8d27831b9..867e28981 100644 --- a/manager-dashboard/app/views/NewTutorial/ScenarioInput/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/index.tsx @@ -50,7 +50,7 @@ interface Props { error: Error | undefined; } -export default function ScenarioInput(props: Props) { +export default function ScenarioPageInput(props: Props) { const { value, onChange, diff --git a/manager-dashboard/app/views/NewTutorial/ScenarioInput/styles.css b/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/styles.css similarity index 100% rename from manager-dashboard/app/views/NewTutorial/ScenarioInput/styles.css rename to manager-dashboard/app/views/NewTutorial/ScenarioPageInput/styles.css diff --git a/manager-dashboard/app/views/NewTutorial/index.tsx b/manager-dashboard/app/views/NewTutorial/index.tsx index 0c2ff0611..8ffdb1cb3 100644 --- a/manager-dashboard/app/views/NewTutorial/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/index.tsx @@ -11,12 +11,14 @@ import { analyzeErrors, useFormArray, } from '@togglecorp/toggle-form'; +/* import { getStorage, ref as storageRef, uploadBytes, getDownloadURL, } from 'firebase/storage'; +*/ import { getDatabase, ref as databaseRef, @@ -75,8 +77,8 @@ import { PartialBlocksType, } from './utils'; import CustomOptionInput from './CustomOptionInput'; -import ScenarioInput from './ScenarioInput'; -import InformationPage from './InformationPage'; +import ScenarioPageInput from './ScenarioPageInput'; +import InformationPageInput from './InformationPageInput'; import styles from './styles.css'; import SelectInput from '#components/SelectInput'; @@ -184,28 +186,6 @@ function NewTutorial(props: Props) { const userId = user?.id; const finalValues = finalValuesFromProps as TutorialFormType; - type Screens = typeof finalValues.scenarioPages; - type Screen = Screens[number]; - type CustomScreen = Omit; - - const screens = finalValues.scenarioPages.reduce( - (acc, currentValue) => { - const { scenarioId, ...other } = currentValue; - acc[scenarioId] = { - ...other, - }; - - return acc; - }, - {} as Record, - ); - - console.info(screens, finalValues); - - if (finalValues) { - return; - } - if (!userId) { // eslint-disable-next-line no-console console.error('Cannot submit form because user is not defined'); @@ -221,16 +201,39 @@ function NewTutorial(props: Props) { const { exampleImage1, exampleImage2, + scenarioPages, ...valuesToCopy } = finalValues; + type Screens = typeof finalValues.scenarioPages; + type Screen = Screens[number]; + type CustomScreen = Omit; + + const screens = scenarioPages.reduce( + (acc, currentValue) => { + const { scenarioId, ...other } = currentValue; + acc[scenarioId] = { + ...other, + }; + + return acc; + }, + {} as Record, + ); - const storage = getStorage(); - const timestamp = (new Date()).getTime(); - const uploadedImage1Ref = storageRef(storage, `projectImages/${timestamp}-tutorial-image-1-${exampleImage1.name}`); - const uploadedImage2Ref = storageRef(storage, `projectImages/${timestamp}-tutorial-image-2-${exampleImage2.name}`); + // const storage = getStorage(); + // const timestamp = (new Date()).getTime(); + // const uploadedImage1Ref = storageRef( + // storage, + // `projectImages/${timestamp}-tutorial-image-1-${exampleImage1.name}`, + // ); + // const uploadedImage2Ref = storageRef( + // storage, + // `projectImages/${timestamp}-tutorial-image-2-${exampleImage2.name}`, + // ); setTutorialSubmissionStatus('imageUpload'); try { + /* const uploadTask1 = await uploadBytes(uploadedImage1Ref, exampleImage1); if (!mountedRef.current) { return; @@ -247,14 +250,21 @@ function NewTutorial(props: Props) { if (!mountedRef.current) { return; } + */ const uploadData = { ...valuesToCopy, - exampleImage1: downloadUrl1, - exampleImage2: downloadUrl2, + screens, + // exampleImage1: downloadUrl1, + // exampleImage2: downloadUrl2, createdBy: userId, }; + if (uploadData) { + console.info(uploadData); + return; + } + const database = getDatabase(); const tutorialDraftsRef = databaseRef(database, 'v2/tutorialDrafts/'); const newTutorialDraftsRef = await pushToDatabase(tutorialDraftsRef); @@ -541,7 +551,7 @@ function NewTutorial(props: Props) { ))} {value.scenarioPages?.map((task, index) => ( - {value.informationPages?.map((page, i) => ( - Date: Mon, 12 Jun 2023 13:44:19 +0545 Subject: [PATCH 16/72] Get image url --- .../InformationPageInput/index.tsx | 46 +++++++------ .../InformationPageInput/styles.css | 9 ++- .../app/views/NewTutorial/index.tsx | 67 +++++++++---------- 3 files changed, 63 insertions(+), 59 deletions(-) diff --git a/manager-dashboard/app/views/NewTutorial/InformationPageInput/index.tsx b/manager-dashboard/app/views/NewTutorial/InformationPageInput/index.tsx index 958a5ece1..9f1628b71 100644 --- a/manager-dashboard/app/views/NewTutorial/InformationPageInput/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/InformationPageInput/index.tsx @@ -13,6 +13,7 @@ import Button from '#components/Button'; import { InformationPagesType } from '../utils'; import Block from './Block'; +import styles from './styles.css'; interface Props { value: InformationPagesType, @@ -48,28 +49,31 @@ export default function InformationPageInput(props: Props) { - - {value.blocks?.map((block, i) => ( - + - ))} - + {value.blocks?.map((block, i) => ( + + ))} + +
); } diff --git a/manager-dashboard/app/views/NewTutorial/InformationPageInput/styles.css b/manager-dashboard/app/views/NewTutorial/InformationPageInput/styles.css index aad82becd..5ce2c3ee8 100644 --- a/manager-dashboard/app/views/NewTutorial/InformationPageInput/styles.css +++ b/manager-dashboard/app/views/NewTutorial/InformationPageInput/styles.css @@ -1,3 +1,10 @@ -.information { +.informationForm { display: flex; + flex-direction: column; + gap: var(--spacing-medium); + + .remove-button { + align-self: end; + width: fit-content; + } } \ No newline at end of file diff --git a/manager-dashboard/app/views/NewTutorial/index.tsx b/manager-dashboard/app/views/NewTutorial/index.tsx index 8ffdb1cb3..52dea7d7e 100644 --- a/manager-dashboard/app/views/NewTutorial/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/index.tsx @@ -11,14 +11,12 @@ import { analyzeErrors, useFormArray, } from '@togglecorp/toggle-form'; -/* import { getStorage, ref as storageRef, uploadBytes, getDownloadURL, } from 'firebase/storage'; -*/ import { getDatabase, ref as databaseRef, @@ -202,6 +200,7 @@ function NewTutorial(props: Props) { exampleImage1, exampleImage2, scenarioPages, + informationPages, ...valuesToCopy } = finalValues; type Screens = typeof finalValues.scenarioPages; @@ -220,51 +219,44 @@ function NewTutorial(props: Props) { {} as Record, ); - // const storage = getStorage(); - // const timestamp = (new Date()).getTime(); - // const uploadedImage1Ref = storageRef( - // storage, - // `projectImages/${timestamp}-tutorial-image-1-${exampleImage1.name}`, - // ); - // const uploadedImage2Ref = storageRef( - // storage, - // `projectImages/${timestamp}-tutorial-image-2-${exampleImage2.name}`, - // ); + const storage = getStorage(); + const timestamp = (new Date()).getTime(); setTutorialSubmissionStatus('imageUpload'); try { - /* - const uploadTask1 = await uploadBytes(uploadedImage1Ref, exampleImage1); - if (!mountedRef.current) { - return; - } - const downloadUrl1 = await getDownloadURL(uploadTask1.ref); - if (!mountedRef.current) { - return; - } - const uploadTask2 = await uploadBytes(uploadedImage2Ref, exampleImage2); - if (!mountedRef.current) { - return; - } - const downloadUrl2 = await getDownloadURL(uploadTask2.ref); - if (!mountedRef.current) { - return; - } - */ + const finalInformationPages = informationPages.map(async (info) => ( + info.blocks.map(async (block) => { + if (!block.imageFile) { + return block; + } + const { + imageFile, + ...otherBlocks + } = block; + + const uploadImagesRef = storageRef( + storage, + `projectImage/${timestamp}-tutorial-image-${block.blockNumber}-${imageFile?.name}`, + ); + const uploadTask = await uploadBytes(uploadImagesRef, block.imageFile); + const downloadUrl = await getDownloadURL(uploadTask.ref); + return { + + ...otherBlocks, + image: downloadUrl, + }; + }) + )); + + console.log('finalInformation', finalInformationPages); const uploadData = { ...valuesToCopy, screens, - // exampleImage1: downloadUrl1, - // exampleImage2: downloadUrl2, + informationPages: finalInformationPages, createdBy: userId, }; - if (uploadData) { - console.info(uploadData); - return; - } - const database = getDatabase(); const tutorialDraftsRef = databaseRef(database, 'v2/tutorialDrafts/'); const newTutorialDraftsRef = await pushToDatabase(tutorialDraftsRef); @@ -567,6 +559,7 @@ function NewTutorial(props: Props) {
Date: Mon, 12 Jun 2023 17:32:35 +0545 Subject: [PATCH 17/72] Add custom option on new project page --- .../CustomOptionReadOnly/index.tsx | 105 ++++++++++++++++++ .../CustomOptionReadOnly/styles.css | 0 .../NewProject/BasicProjectInfoForm/index.tsx | 65 ++++++++++- .../app/views/NewProject/useProjectOptions.ts | 3 + .../app/views/NewProject/utils.ts | 69 +++++++++++- .../app/views/NewTutorial/utils.ts | 7 +- 6 files changed, 241 insertions(+), 8 deletions(-) create mode 100644 manager-dashboard/app/views/NewProject/BasicProjectInfoForm/CustomOptionReadOnly/index.tsx create mode 100644 manager-dashboard/app/views/NewProject/BasicProjectInfoForm/CustomOptionReadOnly/styles.css diff --git a/manager-dashboard/app/views/NewProject/BasicProjectInfoForm/CustomOptionReadOnly/index.tsx b/manager-dashboard/app/views/NewProject/BasicProjectInfoForm/CustomOptionReadOnly/index.tsx new file mode 100644 index 000000000..0e86f8183 --- /dev/null +++ b/manager-dashboard/app/views/NewProject/BasicProjectInfoForm/CustomOptionReadOnly/index.tsx @@ -0,0 +1,105 @@ +import React from 'react'; + +import { PartialProjectFormType } from '#views/NewProject/utils'; +import Tabs, { Tab, TabList, TabPanel } from '#components/Tabs'; +import TextInput from '#components/TextInput'; +import NumberInput from '#components/NumberInput'; +import Heading from '#components/Heading'; + +import styles from './styles.css'; + +interface Props { + value: NonNullable[number]; +} + +export default function CustomOptionReadOnly(props: Props) { + const { + value, + } = props; + + const [activesubOptions, setActiveSubOptions] = React.useState('1'); + + return ( + +
+
+ + + +
+
+ + +
+ + Sub Options + + {value.subOptions?.length ? ( + + + {value.subOptions?.map((rea) => ( + + {`Sub Options ${rea.subOptionsId}`} + + ))} + + {value.subOptions.map((rea) => ( + + + + ))} + + ) : ( +
No Sub Options
+ )} +
+
+ ); +} diff --git a/manager-dashboard/app/views/NewProject/BasicProjectInfoForm/CustomOptionReadOnly/styles.css b/manager-dashboard/app/views/NewProject/BasicProjectInfoForm/CustomOptionReadOnly/styles.css new file mode 100644 index 000000000..e69de29bb diff --git a/manager-dashboard/app/views/NewProject/BasicProjectInfoForm/index.tsx b/manager-dashboard/app/views/NewProject/BasicProjectInfoForm/index.tsx index 648f5d3ef..840d048c2 100644 --- a/manager-dashboard/app/views/NewProject/BasicProjectInfoForm/index.tsx +++ b/manager-dashboard/app/views/NewProject/BasicProjectInfoForm/index.tsx @@ -1,14 +1,23 @@ -import React from 'react'; +import React, { useCallback } from 'react'; import { EntriesAsList, ObjectError, SetBaseValueArg } from '@togglecorp/toggle-form'; import TextInput from '#components/TextInput'; import { generateProjectName, PartialProjectFormType } from '#views/NewProject/utils'; -import { labelSelector, valueSelector } from '#utils/common'; +import { PROJECT_TYPE_FOOTPRINT, labelSelector, valueSelector } from '#utils/common'; import NumberInput from '#components/NumberInput'; import TextArea from '#components/TextArea'; import ImageInput from '#components/ImageInput'; import SelectInput from '#components/SelectInput'; import useProjectOptions from '#views/NewProject/useProjectOptions'; import styles from '#views/NewProject/styles.css'; +import Card from '#components/Card'; +import Heading from '#components/Heading'; +import Tabs, +{ + Tab, + TabList, +} from '#components/Tabs'; + +import CustomOptionReadOnly from './CustomOptionReadOnly'; export interface Props { className?: string; @@ -37,6 +46,8 @@ function BasicProjectInfoForm(props: Props) { organisationsPending, } = useProjectOptions(value?.projectType); + const [activeOptionsTab, setActiveOptionsTab] = React.useState('1'); + React.useEffect(() => { setFieldValue(tutorialOptions?.[0]?.value, 'tutorialId'); }, [setFieldValue, value?.projectType, tutorialOptions]); @@ -61,6 +72,21 @@ function BasicProjectInfoForm(props: Props) { }, [setFieldValue, setValue], ); + + const handleTutorialOptions = useCallback( + (tutorialId: string) => { + setFieldValue(tutorialId, 'tutorialId'); + + const newTutorial = tutorialOptions.find((tutorial) => tutorial.value === tutorialId); + + setFieldValue(newTutorial?.customOptions, 'customOptions'); + }, + [ + tutorialOptions, + setFieldValue, + ], + ); + return ( <> @@ -168,12 +194,13 @@ function BasicProjectInfoForm(props: Props) { hint="Choose which tutorial should be used for this project. Make sure that this aligns with what you are looking for." name={'tutorialId' as const} value={value?.tutorialId} - onChange={setFieldValue} + onChange={handleTutorialOptions} options={tutorialOptions} error={error?.tutorialId} keySelector={valueSelector} labelSelector={labelSelector} disabled={submissionPending || tutorialsPending} + nonClearable /> ) { />
+ {(value.projectType === PROJECT_TYPE_FOOTPRINT && value?.customOptions) && ( + + + Option Instructions + + + + {value.customOptions.map((custom) => ( + + + {`Option ${custom.optionId}`} + + ))} + + {value.customOptions.map((options) => ( + + ))} + + + )} ); } diff --git a/manager-dashboard/app/views/NewProject/useProjectOptions.ts b/manager-dashboard/app/views/NewProject/useProjectOptions.ts index a18f17d04..057d64ae5 100644 --- a/manager-dashboard/app/views/NewProject/useProjectOptions.ts +++ b/manager-dashboard/app/views/NewProject/useProjectOptions.ts @@ -8,6 +8,7 @@ import { } from 'firebase/database'; import useFirebaseDatabase from '#hooks/useFirebaseDatabase'; +import { CustomOptions } from '#views/NewTutorial/utils'; // FIXME: these typings are reusable interface Organisation { @@ -56,6 +57,7 @@ interface Tutorial { tileServerB?: TileServerDetails; // For Change detection and Completeness tutorialDraftId: string; zoomLevel: number; + customOptions: CustomOptions; } function useProjectOptions(selectedProjectType: number | undefined) { @@ -129,6 +131,7 @@ function useProjectOptions(selectedProjectType: number | undefined) { .map((tutorial) => ({ value: tutorial.projectId, label: tutorial.name, + customOptions: tutorial.customOptions, })), [tutorials, selectedProjectType], ); diff --git a/manager-dashboard/app/views/NewProject/utils.ts b/manager-dashboard/app/views/NewProject/utils.ts index 8c07e5a0d..ee9ecead9 100644 --- a/manager-dashboard/app/views/NewProject/utils.ts +++ b/manager-dashboard/app/views/NewProject/utils.ts @@ -22,6 +22,12 @@ import { TileServer, tileServerFieldsSchema, } from '#components/TileServerInput'; +import { + CustomOptions, + CustomOptionFormSchema, + CustomOptionFormSchemaMember, + CustomOptionSchemaFields, +} from '#views/NewTutorial/utils'; import { getNoMoreThanNCharacterCondition, @@ -56,6 +62,7 @@ export interface ProjectFormType { maxTasksPerUser: number; tileServer: TileServer; tileServerB?: TileServer; + customOptions?: CustomOptions; } export const PROJECT_INPUT_TYPE_UPLOAD = 'aoi_file'; @@ -84,7 +91,7 @@ export const filterOptions = [ export type PartialProjectFormType = PartialForm< Omit & { projectImage?: File }, // NOTE: we do not want to change File and FeatureCollection to partials - 'geometry' | 'projectImage' + 'geometry' | 'projectImage' | 'optionId' | 'subOptionsId' >; type ProjectFormSchema = ObjectSchema; @@ -225,7 +232,67 @@ export const projectFormSchema: ProjectFormSchema = { validations: [greaterThanCondition(0)], }, }; + baseSchema = addCondition( + baseSchema, + value, + ['projectType'], + ['customOptions'], + (formValues) => { + const customOptionField: CustomOptionFormSchema = { + keySelector: (key) => key.optionId, + member: (): CustomOptionFormSchemaMember => ({ + fields: (): CustomOptionSchemaFields => ({ + optionId: { + required: true, + }, + title: { + required: true, + requiredValidation: requiredStringCondition, + validations: [getNoMoreThanNCharacterCondition(500)], + }, + value: { + required: true, + }, + description: { + required: true, + requiredValidation: requiredStringCondition, + validations: [getNoMoreThanNCharacterCondition(500)], + }, + icon: { + required: true, + }, + iconColor: { + required: true, + }, + subOptions: { + keySelector: (key) => key.subOptionsId, + member: () => ({ + fields: () => ({ + subOptionsId: { + required: true, + }, + description: { + required: true, + requiredValidation: requiredStringCondition, + validations: [getNoMoreThanNCharacterCondition(500)], + }, + }), + }), + }, + }), + }), + }; + if (formValues?.projectType === PROJECT_TYPE_FOOTPRINT) { + return { + customOptions: customOptionField, + }; + } + return { + customOptions: { forceValue: nullValue }, + }; + }, + ); baseSchema = addCondition( baseSchema, value, diff --git a/manager-dashboard/app/views/NewTutorial/utils.ts b/manager-dashboard/app/views/NewTutorial/utils.ts index f68b1c326..b7e3887e9 100644 --- a/manager-dashboard/app/views/NewTutorial/utils.ts +++ b/manager-dashboard/app/views/NewTutorial/utils.ts @@ -163,7 +163,6 @@ export type PartialTutorialFormType = PartialForm< exampleImage2?: File; }, // NOTE: we do not want to change File and FeatureCollection to partials - // FIXME: rename page to pageNumber, block to blockNumber 'image' |'tutorialTasks' | 'exampleImage1' | 'exampleImage2' | 'scenarioId' | 'optionId' | 'subOptionsId' | 'pageNumber' | 'blockNumber' | 'blockType' | 'imageFile' >; @@ -179,10 +178,10 @@ type ScenarioPagesFormSchemaMember = ReturnType[number]; type CustomOptionSchema = ObjectSchema; -type CustomOptionSchemaFields = ReturnType +export type CustomOptionSchemaFields = ReturnType -type CustomOptionFormSchema = ArraySchema; -type CustomOptionFormSchemaMember = ReturnType; +export type CustomOptionFormSchema = ArraySchema; +export type CustomOptionFormSchemaMember = ReturnType; export type InformationPagesType = NonNullable[number] type InformationPagesSchema = ObjectSchema; From b034304d94147fdb548f65be85c7b4976528a0ee Mon Sep 17 00:00:00 2001 From: shreeyash07 Date: Wed, 14 Jun 2023 17:53:19 +0545 Subject: [PATCH 18/72] Add preview screen for scenario --- .../ScenarioGeoJsonPreview/index.tsx | 127 ++++++++++++++++++ .../ScenarioGeoJsonPreview/styles.css | 13 ++ .../CustomOptionReadOnly/styles.css | 3 + .../app/views/NewProject/utils.ts | 51 +++++-- .../CustomOptionInput/SubOption/index.tsx | 9 ++ .../NewTutorial/ScenarioPageInput/index.tsx | 2 +- .../app/views/NewTutorial/index.tsx | 68 ++++++---- .../app/views/NewTutorial/styles.css | 11 ++ .../app/views/NewTutorial/utils.ts | 6 + 9 files changed, 248 insertions(+), 42 deletions(-) create mode 100644 manager-dashboard/app/components/ScenarioGeoJsonPreview/index.tsx create mode 100644 manager-dashboard/app/components/ScenarioGeoJsonPreview/styles.css diff --git a/manager-dashboard/app/components/ScenarioGeoJsonPreview/index.tsx b/manager-dashboard/app/components/ScenarioGeoJsonPreview/index.tsx new file mode 100644 index 000000000..eb3ff7ebb --- /dev/null +++ b/manager-dashboard/app/components/ScenarioGeoJsonPreview/index.tsx @@ -0,0 +1,127 @@ +import React, { useCallback } from 'react'; +import { + map as createMap, + Map, + tileLayer, + geoJSON, +} from 'leaflet'; +import { _cs } from '@togglecorp/fujs'; +import SegmentInput from '#components/SegmentInput'; +import { valueSelector, labelSelector } from '#utils/common'; + +import styles from './styles.css'; +import Heading from '#components/Heading'; + +interface Props { + className?: string; + geoJson: GeoJSON.GeoJSON | undefined; +} + +const previewOptions: { + value: string; + label: string; +}[] = [ + { value: 'instruction', label: 'Instruction' }, + { value: 'hint', label: 'Hint' }, + { value: 'success', label: 'Success' }, +]; + +function ScenarioGeoJsonPreview(props: Props) { + const { + className, + geoJson, + } = props; + + const mapRef = React.useRef(); + const mapContainerRef = React.useRef(null); + + React.useEffect( + () => { + if (mapContainerRef.current && !mapRef.current) { + mapRef.current = createMap(mapContainerRef.current); + } + + if (mapRef.current) { + // NOTE: show whole world by default + mapRef.current.setView( + [0.0, 0.0], + 1, + ); + + const layer = tileLayer( + 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', + { + attribution: '© OpenStreetMap', + subdomains: ['a', 'b', 'c'], + }, + ); + + layer.addTo(mapRef.current); + mapRef.current.invalidateSize(); + } + + return () => { + if (mapRef.current) { + mapRef.current.remove(); + mapRef.current = undefined; + } + }; + }, + [], + ); + + React.useEffect( + () => { + if (!geoJson) { + return undefined; + } + + const map = mapRef.current; + if (!map) { + return undefined; + } + + const newGeoJson = geoJSON(); + newGeoJson.addTo(map); + + newGeoJson.addData(geoJson); + const bounds = newGeoJson.getBounds(); + + if (bounds.isValid()) { + map.fitBounds(bounds); + } + + return () => { + newGeoJson.removeFrom(map); + newGeoJson.remove(); + }; + }, + [geoJson], + ); + + const handleSenarioType = useCallback((handleProps) => ( + console.log(handleProps) + ), []); + + return ( +
+ + Preview + +
+ +
+ ); +} + +export default ScenarioGeoJsonPreview; diff --git a/manager-dashboard/app/components/ScenarioGeoJsonPreview/styles.css b/manager-dashboard/app/components/ScenarioGeoJsonPreview/styles.css new file mode 100644 index 000000000..19973949d --- /dev/null +++ b/manager-dashboard/app/components/ScenarioGeoJsonPreview/styles.css @@ -0,0 +1,13 @@ +.geo-json-preview { + position: relative; + flex-shrink: 0; + z-index: 0; + width: 100%; + max-width: 20rem; + height: 30rem; + + .map-container { + width: 100%; + height: 100%; + } +} diff --git a/manager-dashboard/app/views/NewProject/BasicProjectInfoForm/CustomOptionReadOnly/styles.css b/manager-dashboard/app/views/NewProject/BasicProjectInfoForm/CustomOptionReadOnly/styles.css index e69de29bb..70f67625f 100644 --- a/manager-dashboard/app/views/NewProject/BasicProjectInfoForm/CustomOptionReadOnly/styles.css +++ b/manager-dashboard/app/views/NewProject/BasicProjectInfoForm/CustomOptionReadOnly/styles.css @@ -0,0 +1,3 @@ +.custom-option { + display: flex; +} \ No newline at end of file diff --git a/manager-dashboard/app/views/NewProject/utils.ts b/manager-dashboard/app/views/NewProject/utils.ts index ee9ecead9..350b82c89 100644 --- a/manager-dashboard/app/views/NewProject/utils.ts +++ b/manager-dashboard/app/views/NewProject/utils.ts @@ -293,11 +293,46 @@ export const projectFormSchema: ProjectFormSchema = { }; }, ); + + baseSchema = addCondition( + baseSchema, + value, + ['projectType', 'inputType'], + ['geometry'], + (v) => { + const projectType = v?.projectType; + const inputType = v?.inputType; + if (projectType === PROJECT_TYPE_BUILD_AREA + || projectType === PROJECT_TYPE_CHANGE_DETECTION + || projectType === PROJECT_TYPE_COMPLETENESS) { + return { + geometry: { + required: true, + validations: [validGeometryCondition], + }, + }; + } + if (( + inputType === PROJECT_INPUT_TYPE_LINK + || inputType === PROJECT_INPUT_TYPE_UPLOAD + ) && projectType === PROJECT_TYPE_FOOTPRINT) { + return { + geometry: { + required: true, + validations: [validGeometryCondition], + }, + }; + } + return { + geometry: { forceValue: nullValue }, + }; + }, + ); baseSchema = addCondition( baseSchema, value, ['projectType'], - ['zoomLevel', 'geometry'], + ['zoomLevel'], (v) => { const projectType = v?.projectType; if ( @@ -314,15 +349,10 @@ export const projectFormSchema: ProjectFormSchema = { integerCondition, ], }, - geometry: { - required: true, - validations: [validGeometryCondition], - }, }; } return { zoomLevel: { forceValue: nullValue }, - geometry: { forceValue: nullValue }, }; }, ); @@ -372,7 +402,7 @@ export const projectFormSchema: ProjectFormSchema = { baseSchema, value, ['projectType', 'inputType'], - ['filter', 'geometry', 'TMId'], + ['filter', 'TMId'], (v) => { const projectType = v?.projectType; const inputType = v?.inputType; @@ -383,13 +413,6 @@ export const projectFormSchema: ProjectFormSchema = { ) && projectType === PROJECT_TYPE_FOOTPRINT ? { required: true } : { forceValue: nullValue }, - // FIXME: geometry type is either string or object update validation - geometry: ( - inputType === PROJECT_INPUT_TYPE_LINK - || inputType === PROJECT_INPUT_TYPE_UPLOAD - ) && projectType === PROJECT_TYPE_FOOTPRINT - ? { required: true, validations: [validGeometryCondition] } - : { forceValue: nullValue }, // FIXME: number string condition TMId: ( inputType === PROJECT_INPUT_TYPE_TASKING_MANAGER_ID diff --git a/manager-dashboard/app/views/NewTutorial/CustomOptionInput/SubOption/index.tsx b/manager-dashboard/app/views/NewTutorial/CustomOptionInput/SubOption/index.tsx index 54f2227f2..888e25158 100644 --- a/manager-dashboard/app/views/NewTutorial/CustomOptionInput/SubOption/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/CustomOptionInput/SubOption/index.tsx @@ -8,6 +8,7 @@ import { import { TabPanel } from '#components/Tabs'; import TextInput from '#components/TextInput'; import Button from '#components/Button'; +import NumberInput from '#components/NumberInput'; import { PartialCustomOptionsType } from '..'; import styles from './styles.css'; @@ -46,6 +47,14 @@ export default function SubOption(props: Props) { error={error?.description} onChange={onReasonChange} /> + + {subOptionsError && subOptionsError?.[nonFieldError]} {value.subOptions?.length ? ( 0 ? Math.max(...safeOldValues.map((option) => option.optionId)) + 1 : 1; - const newValue = safeOldValues.length > 0 ? Math.max(...safeOldValues.map((option) => option.value ?? 0)) + 1 : 1; @@ -369,7 +367,9 @@ function NewTutorial(props: Props) { ); // TODO: Set the new option as selected }, - [setFieldValue], + [ + setFieldValue, + ], ); const submissionPending = ( @@ -596,8 +596,13 @@ function NewTutorial(props: Props) { keySelector={pageKeySelector} labelSelector={pageLabelSelector} onChange={handleAddInformationPages} + disabled={ + value.informationPages + && value.informationPages.length >= 10 + } nonClearable /> + {informationPagesError &&

{informationPagesError?.[nonFieldError]}

} {value.informationPages?.length ? ( } onClick={handleAddDefineOptions} + disabled={ + value.customOptions + && value.customOptions?.length >= 6 + } > Add instruction + {optionsError &&

{optionsError?.[nonFieldError]}

} {value.customOptions?.length ? ( )} - {(value?.projectType === PROJECT_TYPE_BUILD_AREA - || value?.projectType === PROJECT_TYPE_CHANGE_DETECTION - || value?.projectType === PROJECT_TYPE_COMPLETENESS) && ( - - - - )} + { + (value?.projectType === PROJECT_TYPE_BUILD_AREA + || value?.projectType === PROJECT_TYPE_CHANGE_DETECTION + || value?.projectType === PROJECT_TYPE_COMPLETENESS) + && ( + + + + ) + } diff --git a/manager-dashboard/app/views/NewTutorial/utils.ts b/manager-dashboard/app/views/NewTutorial/utils.ts index 725183d1e..c33b6c55b 100644 --- a/manager-dashboard/app/views/NewTutorial/utils.ts +++ b/manager-dashboard/app/views/NewTutorial/utils.ts @@ -30,24 +30,24 @@ export interface IconOptions { export const iconOptions: IconOptions[] = [ { - key: 'swipe-left', - label: 'Swipe Left', + key: 'addOutline', + label: 'Add Outline', }, { - key: 'tap-1', - label: 'Tap 1', + key: 'alertOutline', + label: 'Alert Outline', }, { - key: 'tap-2', - label: 'Tap 2', + key: 'banOutline', + label: 'Ban Outline', }, { - key: 'tap-3', - label: 'Tap 3', + key: 'checkmarkOutline', + label: 'Checkmark Outline', }, { - key: 'check', - label: 'Check', + key: 'closeOutline', + label: 'Close Outline', }, ]; @@ -270,6 +270,12 @@ export const tutorialFormSchema: TutorialFormSchema = { }), }, informationPages: { + validation: (info) => { + if (info && info.length >= 10) { + return 'Information page cannot be more than 10'; + } + return undefined; + }, keySelector: (key) => key.pageNumber, member: (): InformationPagesFormSchemaMember => ({ fields: (): InformationPagesSchemaFields => ({ @@ -333,6 +339,15 @@ export const tutorialFormSchema: TutorialFormSchema = { ['customOptions'], (formValues) => { const customOptionField: CustomOptionFormSchema = { + validation: (option) => { + if (option && option.length >= 6) { + return 'Options cannot be more than 6'; + } + if (option && option.length <= 2) { + return 'Options cannot be less than 2'; + } + return undefined; + }, keySelector: (key) => key.optionId, member: (): CustomOptionFormSchemaMember => ({ fields: (): CustomOptionSchemaFields => ({ @@ -361,6 +376,15 @@ export const tutorialFormSchema: TutorialFormSchema = { }, subOptions: { keySelector: (key) => key.subOptionsId, + validation: (sub) => { + if (sub && sub?.length >= 6) { + return 'Sub options cannot be more than 6'; + } + if (sub && sub.length <= 2) { + return 'Sub options cannot be less than 2'; + } + return undefined; + }, member: () => ({ fields: () => ({ subOptionsId: { From 380fed012f4f0980396ae7104b9a6970414f4e63 Mon Sep 17 00:00:00 2001 From: shreeyash07 Date: Fri, 16 Jun 2023 10:11:57 +0545 Subject: [PATCH 21/72] Add preview screen for scenario form --- .../ScenarioGeoJsonPreview/index.tsx | 52 +++--- .../ScenarioGeoJsonPreview/styles.css | 34 ++++ .../app/components/TutorialList/index.tsx | 2 - manager-dashboard/app/utils/common.ts | 176 +++++++++++++++++- .../app/views/NewProject/utils.ts | 2 + .../NewTutorial/CustomOptionInput/index.tsx | 57 +++--- .../NewTutorial/CustomOptionInput/styles.css | 47 +++-- .../NewTutorial/ScenarioPageInput/index.tsx | 88 ++++++--- .../NewTutorial/ScenarioPageInput/styles.css | 23 ++- .../app/views/NewTutorial/index.tsx | 79 ++++---- .../app/views/NewTutorial/styles.css | 9 +- .../app/views/NewTutorial/utils.ts | 31 +-- 12 files changed, 430 insertions(+), 170 deletions(-) diff --git a/manager-dashboard/app/components/ScenarioGeoJsonPreview/index.tsx b/manager-dashboard/app/components/ScenarioGeoJsonPreview/index.tsx index eb3ff7ebb..ebde4f4d5 100644 --- a/manager-dashboard/app/components/ScenarioGeoJsonPreview/index.tsx +++ b/manager-dashboard/app/components/ScenarioGeoJsonPreview/index.tsx @@ -1,4 +1,4 @@ -import React, { useCallback } from 'react'; +import React from 'react'; import { map as createMap, Map, @@ -6,30 +6,26 @@ import { geoJSON, } from 'leaflet'; import { _cs } from '@togglecorp/fujs'; -import SegmentInput from '#components/SegmentInput'; -import { valueSelector, labelSelector } from '#utils/common'; +import Heading from '#components/Heading'; import styles from './styles.css'; -import Heading from '#components/Heading'; +import { iconMap } from '#utils/common'; interface Props { className?: string; geoJson: GeoJSON.GeoJSON | undefined; + previewPopUp?: { + title?: string; + description?: string; + icon?: string; + } } -const previewOptions: { - value: string; - label: string; -}[] = [ - { value: 'instruction', label: 'Instruction' }, - { value: 'hint', label: 'Hint' }, - { value: 'success', label: 'Success' }, -]; - function ScenarioGeoJsonPreview(props: Props) { const { className, geoJson, + previewPopUp, } = props; const mapRef = React.useRef(); @@ -99,27 +95,35 @@ function ScenarioGeoJsonPreview(props: Props) { [geoJson], ); - const handleSenarioType = useCallback((handleProps) => ( - console.log(handleProps) - ), []); + const Comp = previewPopUp?.icon ? iconMap[previewPopUp.icon] : undefined; return (
Preview +
+
+
+ {previewPopUp?.title ?? 'Title'} +
+
+ {previewPopUp?.description ?? 'Description'} +
+
+ { + Comp + && ( +
+ +
+ ) + } +
-
); } diff --git a/manager-dashboard/app/components/ScenarioGeoJsonPreview/styles.css b/manager-dashboard/app/components/ScenarioGeoJsonPreview/styles.css index 19973949d..a4fc7f495 100644 --- a/manager-dashboard/app/components/ScenarioGeoJsonPreview/styles.css +++ b/manager-dashboard/app/components/ScenarioGeoJsonPreview/styles.css @@ -1,13 +1,47 @@ .geo-json-preview { + display: flex; position: relative; + align-items: center; + flex-direction: column; flex-shrink: 0; + gap: var(--spacing-medium); z-index: 0; width: 100%; max-width: 20rem; height: 30rem; .map-container { + z-index: -1; width: 100%; height: 100%; } + + .map-content { + display: flex; + position: absolute; + top: 60px; + opacity: 0.95; + border-radius: 10px; + background-color: #ffffff; + padding: var(--spacing-medium); + gap: var(--spacing-large); + width: 90%; + + .pop-up-content { + display: flex; + flex-basis: 80%; + flex-direction: column; + + .pop-up-title { + font-weight: var(--font-weight-bold); + } + + } + + .pop-up-icon { + display: flex; + align-items: center; + font-size: var(--font-size-super-large); + } + } } diff --git a/manager-dashboard/app/components/TutorialList/index.tsx b/manager-dashboard/app/components/TutorialList/index.tsx index 9065f87a8..9ab818f41 100644 --- a/manager-dashboard/app/components/TutorialList/index.tsx +++ b/manager-dashboard/app/components/TutorialList/index.tsx @@ -53,8 +53,6 @@ function TutorialList(props: Props) { query: tutorialsQuery, }); - console.info(tutorials); - const tutorialList = React.useMemo( () => (tutorials ? Object.entries(tutorials).reverse() : []), [tutorials], diff --git a/manager-dashboard/app/utils/common.ts b/manager-dashboard/app/utils/common.ts index 90ad219b7..4e6c76388 100644 --- a/manager-dashboard/app/utils/common.ts +++ b/manager-dashboard/app/utils/common.ts @@ -1,4 +1,33 @@ -import { isDefined } from '@togglecorp/fujs'; +import { + IoAddOutline, + IoAlertOutline, + IoBanOutline, + IoCheckmarkOutline, + IoCloseOutline, + IoEggOutline, + IoEllipseOutline, + IoFlagOutline, + IoHandLeftOutline, + IoHandRightOutline, + IoHappyOutline, + IoHeartOutline, + IoHelpOutline, + IoInformationOutline, + IoPrismOutline, + IoRefreshOutline, + IoRemoveOutline, + IoSadOutline, + IoSearchOutline, + IoShapesOutline, + IoSquareOutline, + IoStarOutline, + IoThumbsDownOutline, + IoThumbsUpOutline, + IoTriangleOutline, + IoWarningOutline, +} from 'react-icons/io5'; +import { isDefined, listToMap } from '@togglecorp/fujs'; +import { IconType } from 'react-icons'; export function valueSelector(item: { value: T }) { return item.value; @@ -35,3 +64,148 @@ export const projectTypeLabelMap: { [PROJECT_TYPE_CHANGE_DETECTION]: 'Change Detection', [PROJECT_TYPE_COMPLETENESS]: 'Completeness', }; + +export interface IconList { + key: string; + label: string; + component: IconType +} + +export const iconList: IconList[] = [ + { + key: 'addOutline', + label: 'Add', + component: IoAddOutline, + }, + { + key: 'alertOutline', + label: 'Alert', + component: IoAlertOutline, + }, + { + key: 'banOutline', + label: 'Ban', + component: IoBanOutline, + }, + { + key: 'checkmarkOutline', + label: 'Checkmark', + component: IoCheckmarkOutline, + }, + { + key: 'closeOutline', + label: 'Close', + component: IoCloseOutline, + }, + { + key: 'eggOutline', + label: 'Egg', + component: IoEggOutline, + }, + { + key: 'ellipseOutline', + label: 'Ellipse', + component: IoEllipseOutline, + }, + { + key: 'flagOutline', + label: 'Flag', + component: IoFlagOutline, + }, + { + key: 'handLeftOutline', + label: 'Hand Left', + component: IoHandLeftOutline, + }, + { + key: 'handRightOutline', + label: 'Hand Right', + component: IoHandRightOutline, + }, + { + key: 'happyOutline', + label: 'Happy', + component: IoHappyOutline, + }, + { + key: 'heartOutline', + label: 'Heart', + component: IoHeartOutline, + }, + { + key: 'helpOutline', + label: 'Help', + component: IoHelpOutline, + }, + { + key: 'informationOutline', + label: 'Information', + component: IoInformationOutline, + }, + { + key: 'prismOutline', + label: 'Prism', + component: IoPrismOutline, + }, + { + key: 'refreshOutline', + label: 'Refresh', + component: IoRefreshOutline, + }, + { + key: 'removeOutline', + label: 'Remove', + component: IoRemoveOutline, + }, + { + key: 'sadOutline', + label: 'Sad', + component: IoSadOutline, + }, + { + key: 'searchOutline', + label: 'Search', + component: IoSearchOutline, + }, + { + key: 'shapesOutline', + label: 'Shapes', + component: IoShapesOutline, + }, + { + key: 'squareOutline', + label: 'Square', + component: IoSquareOutline, + }, + { + key: 'starOutline', + label: 'Star', + component: IoStarOutline, + }, + { + key: 'thumbsDownOutline', + label: 'Thumbs Down', + component: IoThumbsDownOutline, + }, + { + key: 'thumbsUpOutline', + label: 'Thumbs Up', + component: IoThumbsUpOutline, + }, + { + key: 'triangleOutline', + label: 'Triangle', + component: IoTriangleOutline, + }, + { + key: 'warningOutline', + label: 'Warning', + component: IoWarningOutline, + }, +]; + +export const iconMap = listToMap( + iconList, + (icon) => icon.key, + (icon) => icon.component, +); diff --git a/manager-dashboard/app/views/NewProject/utils.ts b/manager-dashboard/app/views/NewProject/utils.ts index 350b82c89..b78b76164 100644 --- a/manager-dashboard/app/views/NewProject/utils.ts +++ b/manager-dashboard/app/views/NewProject/utils.ts @@ -196,6 +196,8 @@ export const projectFormSchema: ProjectFormSchema = { required: true, }, lookFor: { + required: true, + requiredValidation: requiredStringCondition, validations: [getNoMoreThanNCharacterCondition(25)], }, projectDetails: { diff --git a/manager-dashboard/app/views/NewTutorial/CustomOptionInput/index.tsx b/manager-dashboard/app/views/NewTutorial/CustomOptionInput/index.tsx index 3935d0f1d..9d8615e96 100644 --- a/manager-dashboard/app/views/NewTutorial/CustomOptionInput/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/CustomOptionInput/index.tsx @@ -11,20 +11,20 @@ import { } from '@togglecorp/toggle-form'; import Button from '#components/Button'; import Heading from '#components/Heading'; -import Tabs, { TabList, TabPanel, Tab } from '#components/Tabs'; +import Tabs, { TabList, Tab } from '#components/Tabs'; import TextInput from '#components/TextInput'; import NumberInput from '#components/NumberInput'; import SelectInput from '#components/SelectInput'; import SubOption from './SubOption'; import { - IconOptions, + ColorOptions, PartialTutorialFormType, iconColorOptions, - iconOptions, } from '../utils'; import styles from './styles.css'; +import { IconList, iconList } from '#utils/common'; export type PartialCustomOptionsType = NonNullable[number] const defaultcustomOptionsValue: PartialCustomOptionsType = { @@ -89,9 +89,7 @@ export default function CustomOptionInput(props: Props) { ); return ( - +
- d.key} - labelSelector={(d: IconOptions) => d.label} + options={iconList} + keySelector={(d: IconList) => d.key} + labelSelector={(d: IconList) => d.label} onChange={onOptionChange} error={error?.icon} />
- d.key} - labelSelector={(d: IconOptions) => d.label} + keySelector={(d: ColorOptions) => d.key} + labelSelector={(d: ColorOptions) => d.label} onChange={onOptionChange} error={error?.iconColor} />
+
)}
-
+
+ + Preview + +
+ {value.title} + {value.description} +
+
+
); } diff --git a/manager-dashboard/app/views/NewTutorial/CustomOptionInput/styles.css b/manager-dashboard/app/views/NewTutorial/CustomOptionInput/styles.css index 0a2844aa9..84e6403b9 100644 --- a/manager-dashboard/app/views/NewTutorial/CustomOptionInput/styles.css +++ b/manager-dashboard/app/views/NewTutorial/CustomOptionInput/styles.css @@ -1,27 +1,46 @@ -.option-form { +.option-container { display: flex; - flex-direction: column; - gap: var(--spacing-medium); + gap: var(--spacing-extra-large); - .option-content { + .option-form { display: flex; + flex-basis: 60%; + flex-direction: column; gap: var(--spacing-medium); - .option-input { - flex-grow: 1; + .option-content { + display: flex; + gap: var(--spacing-medium); + + .option-input { + flex-grow: 1; + } + + .option-icon { + flex-basis: 15%; + } } - .option-icon { - flex-basis: 15%; + .remove-button { + align-self: end; + width: fit-content; } - } - .remove-button { - align-self: end; - width: fit-content; + .add-button { + width: fit-content; + } } - .add-button { - width: fit-content; + .option-preview { + display: flex; + align-items: center; + flex-direction: column; + flex-grow: 1; + + .preview-screen { + background-color: var(--color-primary); + width: 20rem; + height: 30rem; + } } } diff --git a/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/index.tsx b/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/index.tsx index 70aa79ce5..659d769d9 100644 --- a/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/index.tsx @@ -6,18 +6,19 @@ import { Error, getErrorObject, } from '@togglecorp/toggle-form'; +import { + IconList, + iconList, + valueSelector, + labelSelector, +} from '#utils/common'; import TextInput from '#components/TextInput'; import Heading from '#components/Heading'; import SelectInput from '#components/SelectInput'; -import { - TabPanel, -} from '#components/Tabs'; +import ScenarioGeoJsonPreview from '#components/ScenarioGeoJsonPreview'; +import SegmentInput from '#components/SegmentInput'; import styles from './styles.css'; -import { - IconOptions, - iconOptions, -} from '../utils'; type ScenarioType = { scenarioId: string; @@ -26,7 +27,7 @@ type ScenarioType = { icon: string; title: string; }; - instructions: { + instruction: { description: string; icon: string; title: string; @@ -38,6 +39,17 @@ type ScenarioType = { }; }; +interface ScenarioSegmentType { + value: 'instruction' | 'hint' | 'success'; + label: string; +} + +const previewOptions: ScenarioSegmentType[] = [ + { value: 'instruction', label: 'Instruction' }, + { value: 'hint', label: 'Hint' }, + { value: 'success', label: 'Success' }, +]; + type PartialScenarioType = PartialForm; const defaultScenarioTabsValue: PartialScenarioType = { scenarioId: 'xxx', @@ -48,6 +60,7 @@ interface Props { onChange: (value: SetValueArg, index: number) => void; index: number, error: Error | undefined; + geoJson: GeoJSON.GeoJSON | undefined; } export default function ScenarioPageInput(props: Props) { @@ -56,25 +69,32 @@ export default function ScenarioPageInput(props: Props) { onChange, index, error: riskyError, + geoJson, } = props; + const [activeSegmentInput, setActiveInput] = React.useState('instruction'); + const onFieldChange = useFormObject(index, onChange, defaultScenarioTabsValue); - const onInstructionFieldChange = useFormObject<'instructions', PartialScenarioType['instructions']>('instructions', onFieldChange, {}); + const onInstructionFieldChange = useFormObject<'instruction', PartialScenarioType['instruction']>('instruction', onFieldChange, {}); const onHintFieldChange = useFormObject<'hint', PartialScenarioType['hint']>('hint', onFieldChange, {}); const onSuccessFieldChange = useFormObject<'success', PartialScenarioType['success']>('success', onFieldChange, {}); const error = getErrorObject(riskyError); - const instructionsError = getErrorObject(error?.instructions); + const instructionsError = getErrorObject(error?.instruction); const hintError = getErrorObject(error?.hint); const successError = getErrorObject(error?.success); + const handleScenarioType = React.useCallback((scenarioSegment: ScenarioSegmentType['value']) => { + setActiveInput(scenarioSegment); + }, []); + + const previewPopUpData = value[activeSegmentInput]; + return ( - -
+
+
Instructions @@ -82,14 +102,14 @@ export default function ScenarioPageInput(props: Props) {
d.key} - labelSelector={(d: IconOptions) => d.label} + value={value.instruction?.icon} + options={iconList} + keySelector={(d: IconList) => d.key} + labelSelector={(d: IconList) => d.label} onChange={onInstructionFieldChange} error={instructionsError?.icon} /> @@ -130,9 +150,9 @@ export default function ScenarioPageInput(props: Props) { name="icon" label="Icon" value={value.hint?.icon} - options={iconOptions} - keySelector={(d: IconOptions) => d.key} - labelSelector={(d: IconOptions) => d.label} + options={iconList} + keySelector={(d: IconList) => d.key} + labelSelector={(d: IconList) => d.label} onChange={onHintFieldChange} error={hintError?.icon} /> @@ -161,14 +181,28 @@ export default function ScenarioPageInput(props: Props) { name="icon" label="Icon" value={value.success?.icon} - options={iconOptions} - keySelector={(d: IconOptions) => d.key} - labelSelector={(d: IconOptions) => d.label} + options={iconList} + keySelector={(d: IconList) => d.key} + labelSelector={(d: IconList) => d.label} onChange={onSuccessFieldChange} error={successError?.icon} />
- +
+ + +
+
); } diff --git a/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/styles.css b/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/styles.css index 505605b80..154503c03 100644 --- a/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/styles.css +++ b/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/styles.css @@ -1,16 +1,23 @@ .scenario { display: flex; - flex-direction: column; - gap: var(--spacing-large); + gap: var(--spacing-extra-large); - .scenario-form { + .scenario-content { display: flex; - gap: var(--spacing-medium); - - .scenario-form-content { + flex-basis: 65%; + flex-direction: column; + gap: var(--spacing-large); + + .scenario-form { display: flex; - flex-basis: 80%; - flex-direction: column; + gap: var(--spacing-medium); + + .scenario-form-content { + display: flex; + flex-basis: 80%; + flex-direction: column; + gap: var(--spacing-medium); + } } } } \ No newline at end of file diff --git a/manager-dashboard/app/views/NewTutorial/index.tsx b/manager-dashboard/app/views/NewTutorial/index.tsx index dfee13ac9..8a5a07b1b 100644 --- a/manager-dashboard/app/views/NewTutorial/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/index.tsx @@ -45,6 +45,7 @@ import { Tabs, Tab, TabList, + TabPanel, } from '#components/Tabs'; import TileServerInput, { TILE_SERVER_BING, @@ -54,7 +55,6 @@ import InputSection from '#components/InputSection'; import Button from '#components/Button'; import Heading from '#components/Heading'; import SelectInput from '#components/SelectInput'; -import ScenarioGeoJsonPreview from '#components/ScenarioGeoJsonPreview'; import { valueSelector, labelSelector, @@ -159,8 +159,6 @@ function NewTutorial(props: Props) { const [activeOptionsTab, setActiveOptionsTab] = React.useState('1'); // NOTE: Information Page const [activeInformationPages, setActiveInformationPages] = React.useState('1'); - // NOTE: Preview GeoJson - const [activePreviewGeoJson, setActivePreviewGeoJson] = React.useState(); const error = React.useMemo( () => getErrorObject(formError), @@ -181,12 +179,17 @@ function NewTutorial(props: Props) { [error?.informationPages], ); - const handleScenarioChange = React.useCallback((tab) => { - setActiveTab(tab); - const previewGeoJson = value.tutorialTasks?.features.filter( - (screen) => screen.properties.screen === Number(activeTab), - ); - setActivePreviewGeoJson(previewGeoJson); + const previewGeoJson = React.useMemo((): GeoJSON.GeoJSON | undefined => { + const geojson = value.tutorialTasks; + if (!geojson) { + return undefined; + } + return { + ...geojson, + features: geojson.features.filter( + (screen) => screen.properties.screen === Number(activeTab), + ), + }; }, [ value.tutorialTasks, activeTab, @@ -387,6 +390,7 @@ function NewTutorial(props: Props) { const tutorialTasks = geoProps as PartialTutorialFormType['tutorialTasks']; setFieldValue(tutorialTasks, 'tutorialTasks'); + const uniqueArray = tutorialTasks && unique( tutorialTasks.features, ((geo) => geo?.properties.screen), ); @@ -399,7 +403,6 @@ function NewTutorial(props: Props) { success: {}, } )); - setFieldValue(tutorialTaskArray, 'scenarioPages'); }, [setFieldValue]); @@ -550,34 +553,35 @@ function NewTutorial(props: Props) { > {value.scenarioPages?.length ? (
-
- - {value.scenarioPages?.map((task) => ( - - {`Scenario ${task.scenarioId}`} - - ))} - - {value.scenarioPages?.map((task, index) => ( + + {value.scenarioPages?.map((task) => ( + + {`Scenario ${task.scenarioId}`} + + ))} + + {value.scenarioPages?.map((task, index) => ( + - ))} -
- + + ))}
) : (
No Scenarios
@@ -670,14 +674,19 @@ function NewTutorial(props: Props) { ))} {value.customOptions.map((options, index) => ( - + name={String(options.optionId)} + > + + ))}
) : ( diff --git a/manager-dashboard/app/views/NewTutorial/styles.css b/manager-dashboard/app/views/NewTutorial/styles.css index a2ea122ee..b1533cad5 100644 --- a/manager-dashboard/app/views/NewTutorial/styles.css +++ b/manager-dashboard/app/views/NewTutorial/styles.css @@ -42,13 +42,8 @@ .tab-content { display: flex; - gap: var(--spacing-extra-large); - - .tab-list { - display: flex; - flex-basis: 60%; - flex-direction: column; - } + flex-direction: column; + gap: var(--spacing-medium); } } } diff --git a/manager-dashboard/app/views/NewTutorial/utils.ts b/manager-dashboard/app/views/NewTutorial/utils.ts index c33b6c55b..9fa787373 100644 --- a/manager-dashboard/app/views/NewTutorial/utils.ts +++ b/manager-dashboard/app/views/NewTutorial/utils.ts @@ -23,35 +23,12 @@ import { PROJECT_TYPE_FOOTPRINT, } from '#utils/common'; -export interface IconOptions { +export interface ColorOptions { key: string; label: string; } -export const iconOptions: IconOptions[] = [ - { - key: 'addOutline', - label: 'Add Outline', - }, - { - key: 'alertOutline', - label: 'Alert Outline', - }, - { - key: 'banOutline', - label: 'Ban Outline', - }, - { - key: 'checkmarkOutline', - label: 'Checkmark Outline', - }, - { - key: 'closeOutline', - label: 'Close Outline', - }, -]; - -export const iconColorOptions: IconOptions[] = [ +export const iconColorOptions: ColorOptions[] = [ { key: 'green', label: 'Green', @@ -137,7 +114,7 @@ export interface TutorialFormType { icon: string; title: string; }; - instructions: { + instruction: { description: string; icon: string; title: string; @@ -236,7 +213,7 @@ export const tutorialFormSchema: TutorialFormSchema = { icon: { required: true }, }), }, - instructions: { + instruction: { fields: () => ({ title: { required: true, From 152e25fef492b8a729a2c0596db7ef51a9855a96 Mon Sep 17 00:00:00 2001 From: shreeyash07 Date: Fri, 16 Jun 2023 17:48:21 +0545 Subject: [PATCH 22/72] Add information and option preview screen --- .../components/CustomOptionPreview/index.tsx | 48 +++++ .../components/CustomOptionPreview/styles.css | 22 ++ .../app/components/FileInput/index.tsx | 52 +---- .../app/components/FileInput/styles.css | 9 - .../app/components/Preview/index.tsx | 57 +++++ .../app/components/Preview/styles.css | 8 + .../CustomOptionReadOnly/index.tsx | 24 +-- .../CustomOptionReadOnly/styles.css | 23 +- .../NewProject/BasicProjectInfoForm/index.tsx | 12 +- .../CustomOptionInput/SubOption/index.tsx | 12 +- .../CustomOptionInput/SubOption/styles.css | 4 +- .../NewTutorial/CustomOptionInput/index.tsx | 198 +++++++++--------- .../NewTutorial/CustomOptionInput/styles.css | 47 ++--- .../InformationPageInput/Block/index.tsx | 19 +- .../InformationPagePreview/index.tsx | 43 ++++ .../InformationPagePreview/styles.css | 21 ++ .../InformationPageInput/index.tsx | 13 +- .../InformationPageInput/styles.css | 18 +- .../app/views/NewTutorial/index.tsx | 54 +++-- .../app/views/NewTutorial/styles.css | 12 +- .../app/views/NewTutorial/utils.ts | 2 +- 21 files changed, 433 insertions(+), 265 deletions(-) create mode 100644 manager-dashboard/app/components/CustomOptionPreview/index.tsx create mode 100644 manager-dashboard/app/components/CustomOptionPreview/styles.css create mode 100644 manager-dashboard/app/components/Preview/index.tsx create mode 100644 manager-dashboard/app/components/Preview/styles.css create mode 100644 manager-dashboard/app/views/NewTutorial/InformationPageInput/InformationPagePreview/index.tsx create mode 100644 manager-dashboard/app/views/NewTutorial/InformationPageInput/InformationPagePreview/styles.css diff --git a/manager-dashboard/app/components/CustomOptionPreview/index.tsx b/manager-dashboard/app/components/CustomOptionPreview/index.tsx new file mode 100644 index 000000000..1fbfa2abb --- /dev/null +++ b/manager-dashboard/app/components/CustomOptionPreview/index.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import Heading from '#components/Heading'; +import { PartialTutorialFormType } from '#views/NewTutorial/utils'; +import { iconMap } from '#utils/common'; + +import styles from './styles.css'; + +interface Props { + value: PartialTutorialFormType['customOptions'] +} + +export default function CustomOptionPreview(props: Props) { + const { + value, + } = props; + + function Content() { + const innerContent = value?.map((preview) => { + const Icon = preview.icon ? iconMap[preview.icon] : undefined; + return ( +
+ {Icon && } +
+ {preview.title} +
+
+ {preview.description} +
+
+ ); + }); + return <>{innerContent}; + } + + return ( +
+ + Preview + +
+ +
+
+ ); +} diff --git a/manager-dashboard/app/components/CustomOptionPreview/styles.css b/manager-dashboard/app/components/CustomOptionPreview/styles.css new file mode 100644 index 000000000..44ccaa5f9 --- /dev/null +++ b/manager-dashboard/app/components/CustomOptionPreview/styles.css @@ -0,0 +1,22 @@ +.option-preview { + display: flex; + align-items: center; + flex-direction: column; + flex-grow: 1; + + .preview-screen { + display: flex; + flex-direction: column; + justify-content: center; + background-color: var(--color-primary); + width: 20rem; + height: 30rem; + + .preview-content { + display: flex; + gap: var(--spacing-extra-small); + padding: var(--spacing-medium); + color: #ffffff; + } + } +} \ No newline at end of file diff --git a/manager-dashboard/app/components/FileInput/index.tsx b/manager-dashboard/app/components/FileInput/index.tsx index ca4b975bd..461acfbd1 100644 --- a/manager-dashboard/app/components/FileInput/index.tsx +++ b/manager-dashboard/app/components/FileInput/index.tsx @@ -8,60 +8,10 @@ import { MdAttachFile } from 'react-icons/md'; import { useButtonFeatures } from '#components/Button'; import RawInput from '#components/RawInput'; import InputContainer, { Props as InputContainerProps } from '#components/InputContainer'; +import Preview from '#components/Preview'; import styles from './styles.css'; -interface PreviewProps { - file: File | null | undefined; - className?: string; -} - -function Preview(props: PreviewProps) { - const { - file, - className, - } = props; - - const isPreviewable = file?.name?.match(/.(jpg|jpeg|png|gif)$/i) ?? false; - const [imageUrl, setImageUrl] = React.useState(); - - React.useEffect(() => { - if (!file) { - return undefined; - } - - // FIXME: use async methods - const fileReader = new FileReader(); - - const handleFileLoad = () => { - setImageUrl(String(fileReader.result) ?? undefined); - }; - - fileReader.addEventListener('load', handleFileLoad); - fileReader.readAsDataURL(file); - - return () => { - fileReader.removeEventListener('load', handleFileLoad); - }; - }, [file]); - - if (!isPreviewable) { - return ( -
- Preview not available -
- ); - } - - return ( - {file?.name} - ); -} - export interface Props extends Omit { value: File | undefined | null; name: Name; diff --git a/manager-dashboard/app/components/FileInput/styles.css b/manager-dashboard/app/components/FileInput/styles.css index f9102b3b0..56317210e 100644 --- a/manager-dashboard/app/components/FileInput/styles.css +++ b/manager-dashboard/app/components/FileInput/styles.css @@ -42,12 +42,3 @@ color: var(--color-text-watermark); font-size: var(--font-size-large); } - -.preview { - background-color: var(--color-input-background); - width: 100%; - max-width: 30rem; - height: 20rem; - object-fit: contain; - object-position: center center; -} diff --git a/manager-dashboard/app/components/Preview/index.tsx b/manager-dashboard/app/components/Preview/index.tsx new file mode 100644 index 000000000..897104a0a --- /dev/null +++ b/manager-dashboard/app/components/Preview/index.tsx @@ -0,0 +1,57 @@ +import React from 'react'; +import { _cs } from '@togglecorp/fujs'; + +import styles from './styles.css'; + +interface PreviewProps { + file: File | null | undefined; + className?: string; +} + +function Preview(props: PreviewProps) { + const { + file, + className, + } = props; + + const isPreviewable = file?.name?.match(/.(jpg|jpeg|png|gif)$/i) ?? false; + const [imageUrl, setImageUrl] = React.useState(); + + React.useEffect(() => { + if (!file) { + return undefined; + } + + // FIXME: use async methods + const fileReader = new FileReader(); + + const handleFileLoad = () => { + setImageUrl(String(fileReader.result) ?? undefined); + }; + + fileReader.addEventListener('load', handleFileLoad); + fileReader.readAsDataURL(file); + + return () => { + fileReader.removeEventListener('load', handleFileLoad); + }; + }, [file]); + + if (!isPreviewable) { + return ( +
+ Preview not available +
+ ); + } + + return ( + {file?.name} + ); +} + +export default Preview; diff --git a/manager-dashboard/app/components/Preview/styles.css b/manager-dashboard/app/components/Preview/styles.css new file mode 100644 index 000000000..23e80d351 --- /dev/null +++ b/manager-dashboard/app/components/Preview/styles.css @@ -0,0 +1,8 @@ +.preview { + background-color: var(--color-input-background); + width: 100%; + max-width: 30rem; + height: 20rem; + object-fit: contain; + object-position: center center; +} \ No newline at end of file diff --git a/manager-dashboard/app/views/NewProject/BasicProjectInfoForm/CustomOptionReadOnly/index.tsx b/manager-dashboard/app/views/NewProject/BasicProjectInfoForm/CustomOptionReadOnly/index.tsx index 0e86f8183..0ddc97b34 100644 --- a/manager-dashboard/app/views/NewProject/BasicProjectInfoForm/CustomOptionReadOnly/index.tsx +++ b/manager-dashboard/app/views/NewProject/BasicProjectInfoForm/CustomOptionReadOnly/index.tsx @@ -20,9 +20,7 @@ export default function CustomOptionReadOnly(props: Props) { const [activesubOptions, setActiveSubOptions] = React.useState('1'); return ( - +
- {value.subOptions?.map((rea) => ( + {value.subOptions?.map((sub) => ( - {`Sub Options ${rea.subOptionsId}`} + {`Sub Options ${sub.subOptionsId}`} ))} - {value.subOptions.map((rea) => ( + {value.subOptions.map((sub) => ( @@ -100,6 +97,7 @@ export default function CustomOptionReadOnly(props: Props) {
No Sub Options
)}
- +
+ ); } diff --git a/manager-dashboard/app/views/NewProject/BasicProjectInfoForm/CustomOptionReadOnly/styles.css b/manager-dashboard/app/views/NewProject/BasicProjectInfoForm/CustomOptionReadOnly/styles.css index 70f67625f..7ead978b6 100644 --- a/manager-dashboard/app/views/NewProject/BasicProjectInfoForm/CustomOptionReadOnly/styles.css +++ b/manager-dashboard/app/views/NewProject/BasicProjectInfoForm/CustomOptionReadOnly/styles.css @@ -1,3 +1,24 @@ -.custom-option { +.option-container { display: flex; + gap: var(--spacing-extra-large); + + .option-form { + display: flex; + flex-basis: 60%; + flex-direction: column; + gap: var(--spacing-medium); + + .option-content { + display: flex; + gap: var(--spacing-medium); + + .option-input { + flex-grow: 1; + } + + .option-icon { + flex-basis: 15%; + } + } + } } \ No newline at end of file diff --git a/manager-dashboard/app/views/NewProject/BasicProjectInfoForm/index.tsx b/manager-dashboard/app/views/NewProject/BasicProjectInfoForm/index.tsx index 840d048c2..4d8ec2977 100644 --- a/manager-dashboard/app/views/NewProject/BasicProjectInfoForm/index.tsx +++ b/manager-dashboard/app/views/NewProject/BasicProjectInfoForm/index.tsx @@ -15,6 +15,7 @@ import Tabs, { Tab, TabList, + TabPanel, } from '#components/Tabs'; import CustomOptionReadOnly from './CustomOptionReadOnly'; @@ -246,10 +247,15 @@ function BasicProjectInfoForm(props: Props) { ))} {value.customOptions.map((options) => ( - + name={String(options.optionId)} + > + + ))} diff --git a/manager-dashboard/app/views/NewTutorial/CustomOptionInput/SubOption/index.tsx b/manager-dashboard/app/views/NewTutorial/CustomOptionInput/SubOption/index.tsx index 888e25158..dd88859e9 100644 --- a/manager-dashboard/app/views/NewTutorial/CustomOptionInput/SubOption/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/CustomOptionInput/SubOption/index.tsx @@ -5,7 +5,6 @@ import { getErrorObject, useFormObject, } from '@togglecorp/toggle-form'; -import { TabPanel } from '#components/Tabs'; import TextInput from '#components/TextInput'; import Button from '#components/Button'; import NumberInput from '#components/NumberInput'; @@ -35,12 +34,9 @@ export default function SubOption(props: Props) { const error = getErrorObject(riskyError); return ( - +
Delete this Reason - +
); } diff --git a/manager-dashboard/app/views/NewTutorial/CustomOptionInput/SubOption/styles.css b/manager-dashboard/app/views/NewTutorial/CustomOptionInput/SubOption/styles.css index bdd535e6e..db7513432 100644 --- a/manager-dashboard/app/views/NewTutorial/CustomOptionInput/SubOption/styles.css +++ b/manager-dashboard/app/views/NewTutorial/CustomOptionInput/SubOption/styles.css @@ -1,8 +1,8 @@ -.reason-content { +.sub-option-content { display: flex; gap: var(--spacing-medium); - .reason-input { + .sub-option-input { flex-grow: 1; } .remove-button { diff --git a/manager-dashboard/app/views/NewTutorial/CustomOptionInput/index.tsx b/manager-dashboard/app/views/NewTutorial/CustomOptionInput/index.tsx index 9d8615e96..4465d2c10 100644 --- a/manager-dashboard/app/views/NewTutorial/CustomOptionInput/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/CustomOptionInput/index.tsx @@ -11,7 +11,7 @@ import { } from '@togglecorp/toggle-form'; import Button from '#components/Button'; import Heading from '#components/Heading'; -import Tabs, { TabList, Tab } from '#components/Tabs'; +import Tabs, { TabList, Tab, TabPanel } from '#components/Tabs'; import TextInput from '#components/TextInput'; import NumberInput from '#components/NumberInput'; import SelectInput from '#components/SelectInput'; @@ -89,117 +89,111 @@ export default function CustomOptionInput(props: Props) { ); return ( -
-
-
- - d.key} - labelSelector={(d: IconList) => d.label} - onChange={onOptionChange} - error={error?.icon} - /> -
-
- - d.key} - labelSelector={(d: ColorOptions) => d.label} - onChange={onOptionChange} - error={error?.iconColor} - /> -
+
+
- - - Sub Options - -
+
+ + d.key} + labelSelector={(d: ColorOptions) => d.label} + onChange={onOptionChange} + error={error?.iconColor} + /> +
+ + + + Sub Options + + + {subOptionsError && subOptionsError?.[nonFieldError]} + {value.subOptions?.length ? ( + - Add Sub Options - - {subOptionsError && subOptionsError?.[nonFieldError]} - {value.subOptions?.length ? ( - - - {value.subOptions?.map((rea) => ( - - {`Sub Options ${rea.subOptionsId}`} - - ))} - - {value.subOptions.map((rea, i) => ( + + {value.subOptions?.map((sub) => ( + + {`Sub Options ${sub.subOptionsId}`} + + ))} + + {value.subOptions.map((sub, i) => ( + - ))} - - ) : ( -
Add Sub Options
- )} -
-
- - Preview - -
- {value.title} - {value.description} -
-
+ + ))} + + ) : ( +
Add Sub Options
+ )}
); } diff --git a/manager-dashboard/app/views/NewTutorial/CustomOptionInput/styles.css b/manager-dashboard/app/views/NewTutorial/CustomOptionInput/styles.css index 84e6403b9..0a2844aa9 100644 --- a/manager-dashboard/app/views/NewTutorial/CustomOptionInput/styles.css +++ b/manager-dashboard/app/views/NewTutorial/CustomOptionInput/styles.css @@ -1,46 +1,27 @@ -.option-container { +.option-form { display: flex; - gap: var(--spacing-extra-large); + flex-direction: column; + gap: var(--spacing-medium); - .option-form { + .option-content { display: flex; - flex-basis: 60%; - flex-direction: column; gap: var(--spacing-medium); - .option-content { - display: flex; - gap: var(--spacing-medium); - - .option-input { - flex-grow: 1; - } - - .option-icon { - flex-basis: 15%; - } - } - - .remove-button { - align-self: end; - width: fit-content; + .option-input { + flex-grow: 1; } - .add-button { - width: fit-content; + .option-icon { + flex-basis: 15%; } } - .option-preview { - display: flex; - align-items: center; - flex-direction: column; - flex-grow: 1; + .remove-button { + align-self: end; + width: fit-content; + } - .preview-screen { - background-color: var(--color-primary); - width: 20rem; - height: 30rem; - } + .add-button { + width: fit-content; } } diff --git a/manager-dashboard/app/views/NewTutorial/InformationPageInput/Block/index.tsx b/manager-dashboard/app/views/NewTutorial/InformationPageInput/Block/index.tsx index c016dc8b8..c275a0c4c 100644 --- a/manager-dashboard/app/views/NewTutorial/InformationPageInput/Block/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/InformationPageInput/Block/index.tsx @@ -33,7 +33,16 @@ export default function Block(props: Props) { const error = getErrorObject(riskyError); return (
- {value.blockType === 'image' ? ( + {value.blockType === 'text' && ( + + )} + {value.blockType === 'image' && ( - ) : ( - )}
); diff --git a/manager-dashboard/app/views/NewTutorial/InformationPageInput/InformationPagePreview/index.tsx b/manager-dashboard/app/views/NewTutorial/InformationPageInput/InformationPagePreview/index.tsx new file mode 100644 index 000000000..4abf7fd17 --- /dev/null +++ b/manager-dashboard/app/views/NewTutorial/InformationPageInput/InformationPagePreview/index.tsx @@ -0,0 +1,43 @@ +import React from 'react'; + +import { PartialTutorialFormType } from '#views/NewTutorial/utils'; +import Heading from '#components/Heading'; +import Preview from '#components/Preview'; + +import styles from './styles.css'; + +interface Props { + value: NonNullable[number] +} + +export default function InformationPagePreview(props: Props) { + const { + value, + } = props; + + function Content() { + const innerContent = value?.blocks?.map((preview) => { + if (preview.blockType === 'text') { + return
{preview.textDescription}
; + } + return ( + + ); + }); + return <>{innerContent}; + } + return ( +
+ + Preview + +
+
{value?.title}
+ +
+
+ ); +} diff --git a/manager-dashboard/app/views/NewTutorial/InformationPageInput/InformationPagePreview/styles.css b/manager-dashboard/app/views/NewTutorial/InformationPageInput/InformationPagePreview/styles.css new file mode 100644 index 000000000..4cf883449 --- /dev/null +++ b/manager-dashboard/app/views/NewTutorial/InformationPageInput/InformationPagePreview/styles.css @@ -0,0 +1,21 @@ +.information-preview { + display: flex; + align-items: center; + flex-direction: column; + flex-grow: 1; + + .preview-screen { + display: flex; + flex-direction: column; + background-color: var(--color-primary); + width: 20rem; + height: 30rem; + + .preview-content { + display: flex; + gap: var(--spacing-extra-small); + padding: var(--spacing-medium); + color: #ffffff; + } + } +} \ No newline at end of file diff --git a/manager-dashboard/app/views/NewTutorial/InformationPageInput/index.tsx b/manager-dashboard/app/views/NewTutorial/InformationPageInput/index.tsx index 9f1628b71..83cb9fcd2 100644 --- a/manager-dashboard/app/views/NewTutorial/InformationPageInput/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/InformationPageInput/index.tsx @@ -7,12 +7,13 @@ import { useFormArray, } from '@togglecorp/toggle-form'; -import { TabPanel } from '#components/Tabs'; import TextInput from '#components/TextInput'; import Button from '#components/Button'; +import InformationPagePreview from './InformationPagePreview'; import { InformationPagesType } from '../utils'; import Block from './Block'; + import styles from './styles.css'; interface Props { @@ -45,10 +46,9 @@ export default function InformationPageInput(props: Props) { [error?.blocks], ); + console.log('information', value); return ( - +
- + +
); } diff --git a/manager-dashboard/app/views/NewTutorial/InformationPageInput/styles.css b/manager-dashboard/app/views/NewTutorial/InformationPageInput/styles.css index 5ce2c3ee8..88f72a095 100644 --- a/manager-dashboard/app/views/NewTutorial/InformationPageInput/styles.css +++ b/manager-dashboard/app/views/NewTutorial/InformationPageInput/styles.css @@ -1,10 +1,16 @@ -.informationForm { +.information-container { display: flex; - flex-direction: column; - gap: var(--spacing-medium); + gap: var(--spacing-extra-large); - .remove-button { - align-self: end; - width: fit-content; + .informationForm { + display: flex; + flex-basis: 60%; + flex-direction: column; + gap: var(--spacing-medium); + + .remove-button { + align-self: end; + width: fit-content; + } } } \ No newline at end of file diff --git a/manager-dashboard/app/views/NewTutorial/index.tsx b/manager-dashboard/app/views/NewTutorial/index.tsx index 8a5a07b1b..a4f863b26 100644 --- a/manager-dashboard/app/views/NewTutorial/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/index.tsx @@ -55,6 +55,7 @@ import InputSection from '#components/InputSection'; import Button from '#components/Button'; import Heading from '#components/Heading'; import SelectInput from '#components/SelectInput'; +import CustomOptionPreview from '#components/CustomOptionPreview'; import { valueSelector, labelSelector, @@ -623,14 +624,19 @@ function NewTutorial(props: Props) { ))} {value.informationPages?.map((page, i) => ( - + name={String(page.pageNumber)} + > + +
))} ) : ( @@ -673,21 +679,27 @@ function NewTutorial(props: Props) { ))} - {value.customOptions.map((options, index) => ( - - + {value.customOptions.map((options, index) => ( + - - ))} + name={String(options.optionId)} + className={styles.optionTabPanel} + > + + + ))} + +
) : (
Add options
diff --git a/manager-dashboard/app/views/NewTutorial/styles.css b/manager-dashboard/app/views/NewTutorial/styles.css index b1533cad5..97e027ed7 100644 --- a/manager-dashboard/app/views/NewTutorial/styles.css +++ b/manager-dashboard/app/views/NewTutorial/styles.css @@ -21,6 +21,14 @@ .add-button { width: fit-content; } + + .option-content { + display: flex; + + .option-tab-panel { + flex-basis: 60%; + } + } } .input-group { @@ -115,10 +123,12 @@ 0% { transform: rotate(0deg) translateX(0); } + 25% { transform: rotateY(30deg) rotate(-10deg) translateX(-50px); } + 100% { transform: rotate(0deg) translateX(0); } -} +} \ No newline at end of file diff --git a/manager-dashboard/app/views/NewTutorial/utils.ts b/manager-dashboard/app/views/NewTutorial/utils.ts index 9fa787373..65ab7b6dc 100644 --- a/manager-dashboard/app/views/NewTutorial/utils.ts +++ b/manager-dashboard/app/views/NewTutorial/utils.ts @@ -98,8 +98,8 @@ export type InformationPages = { blocks: { blockNumber: number; blockType: 'image' | 'text'; - imageFile?: File; textDescription?: string; + imageFile?: File; }[]; }[]; From 1fbb4324ac2a94998c35456cfa599f173d3f6551 Mon Sep 17 00:00:00 2001 From: shreeyash07 Date: Mon, 19 Jun 2023 14:35:04 +0545 Subject: [PATCH 23/72] Add popup button --- .../app/components/Button/index.tsx | 6 +- .../components/CustomOptionPreview/styles.css | 22 ---- .../app/components/FileInput/index.tsx | 1 - .../app/components/FileInput/styles.css | 11 -- .../app/components/GeoJsonFileInput/index.tsx | 8 +- .../app/components/Heading/index.tsx | 16 ++- .../Heading/{styles.module.css => styles.css} | 0 .../app/components/PopupButton/index.tsx | 105 ++++++++++++++++++ .../app/components/PopupButton/styles.css | 10 ++ .../app/components/Preview/styles.css | 13 +++ .../CustomOptionPreview/index.tsx | 18 ++- .../CustomOptionPreview/styles.css | 38 +++++++ .../InformationPagePreview/index.tsx | 14 ++- .../InformationPagePreview/styles.css | 6 +- .../InformationPageInput/index.tsx | 1 - .../app/views/NewTutorial/index.tsx | 94 +++++++++------- .../app/views/NewTutorial/styles.css | 7 +- .../app/views/NewTutorial/utils.ts | 35 ++++-- 18 files changed, 299 insertions(+), 106 deletions(-) delete mode 100644 manager-dashboard/app/components/CustomOptionPreview/styles.css rename manager-dashboard/app/components/Heading/{styles.module.css => styles.css} (100%) create mode 100644 manager-dashboard/app/components/PopupButton/index.tsx create mode 100644 manager-dashboard/app/components/PopupButton/styles.css rename manager-dashboard/app/{components => views/NewTutorial/CustomOptionInput}/CustomOptionPreview/index.tsx (66%) create mode 100644 manager-dashboard/app/views/NewTutorial/CustomOptionInput/CustomOptionPreview/styles.css diff --git a/manager-dashboard/app/components/Button/index.tsx b/manager-dashboard/app/components/Button/index.tsx index dad831ead..416c24e90 100644 --- a/manager-dashboard/app/components/Button/index.tsx +++ b/manager-dashboard/app/components/Button/index.tsx @@ -13,7 +13,7 @@ export type ButtonVariant = ( | 'transparent' ); -export interface Props extends RawButtonProps { +export interface ButtonProps extends RawButtonProps { /** * Variant of the button */ @@ -57,7 +57,7 @@ export interface Props extends RawButtonProps { type ButtonFeatureKeys = 'variant' | 'className' | 'actionsClassName' | 'iconsClassName' | 'childrenClassName' | 'children' | 'icons' | 'actions' | 'disabled'; export function useButtonFeatures( - props: Pick, ButtonFeatureKeys>, + props: Pick, ButtonFeatureKeys>, ) { const { variant = 'default', @@ -111,7 +111,7 @@ export function useButtonFeatures( /** * Basic button component */ -function Button(props: Props) { +function Button(props: ButtonProps) { const { variant, className, diff --git a/manager-dashboard/app/components/CustomOptionPreview/styles.css b/manager-dashboard/app/components/CustomOptionPreview/styles.css deleted file mode 100644 index 44ccaa5f9..000000000 --- a/manager-dashboard/app/components/CustomOptionPreview/styles.css +++ /dev/null @@ -1,22 +0,0 @@ -.option-preview { - display: flex; - align-items: center; - flex-direction: column; - flex-grow: 1; - - .preview-screen { - display: flex; - flex-direction: column; - justify-content: center; - background-color: var(--color-primary); - width: 20rem; - height: 30rem; - - .preview-content { - display: flex; - gap: var(--spacing-extra-small); - padding: var(--spacing-medium); - color: #ffffff; - } - } -} \ No newline at end of file diff --git a/manager-dashboard/app/components/FileInput/index.tsx b/manager-dashboard/app/components/FileInput/index.tsx index 461acfbd1..6b2413e45 100644 --- a/manager-dashboard/app/components/FileInput/index.tsx +++ b/manager-dashboard/app/components/FileInput/index.tsx @@ -120,7 +120,6 @@ function FileInput(props: Props) { {description} {showPreview && ( )} diff --git a/manager-dashboard/app/components/FileInput/styles.css b/manager-dashboard/app/components/FileInput/styles.css index 56317210e..f590c0854 100644 --- a/manager-dashboard/app/components/FileInput/styles.css +++ b/manager-dashboard/app/components/FileInput/styles.css @@ -31,14 +31,3 @@ } } -.no-preview { - display: flex; - align-items: center; - justify-content: center; - padding: var(--spacing-medium); - width: 100%; - max-width: 30rem; - height: 20rem; - color: var(--color-text-watermark); - font-size: var(--font-size-large); -} diff --git a/manager-dashboard/app/components/GeoJsonFileInput/index.tsx b/manager-dashboard/app/components/GeoJsonFileInput/index.tsx index acc981f40..365c9a882 100644 --- a/manager-dashboard/app/components/GeoJsonFileInput/index.tsx +++ b/manager-dashboard/app/components/GeoJsonFileInput/index.tsx @@ -93,14 +93,18 @@ function GeoJsonFileInput(props: Props) { const file = newValue; + console.log('file', file); + async function handleValidationAndChange() { + const text = await readUploadedFileAsText(file); let fileAsJson; try { - const text = await readUploadedFileAsText(file); if (!mountedRef.current) { return; } + console.log('text', text); + if (!text || typeof text !== 'string') { setInternalErrorMessage('Failed to read the GeoJson file'); setTempValue(newValue); @@ -109,6 +113,8 @@ function GeoJsonFileInput(props: Props) { } const parsedGeoJSON = parseGeoJSON(text); + console.log('Parsed', parsedGeoJSON);k + if (!parsedGeoJSON.errored) { fileAsJson = parsedGeoJSON.value; } else { diff --git a/manager-dashboard/app/components/Heading/index.tsx b/manager-dashboard/app/components/Heading/index.tsx index 2ed4b2c4e..4387ed17e 100644 --- a/manager-dashboard/app/components/Heading/index.tsx +++ b/manager-dashboard/app/components/Heading/index.tsx @@ -1,11 +1,21 @@ import React from 'react'; import { _cs } from '@togglecorp/fujs'; -import styles from './styles.module.css'; +import styles from './styles.css'; + +type HeadingLevel = 1 | 2 | 3 | 4 | 5; + +const headingLevelToClassName: Record = { + 1: styles.level1, + 2: styles.level2, + 3: styles.level3, + 4: styles.level4, + 5: styles.level5, +}; export interface Props { className?: string; - level?: 1 | 2 | 3 | 4 | 5; + level?: HeadingLevel; children: React.ReactNode; } @@ -16,7 +26,7 @@ function Heading(props: Props) { children, } = props; - const levelStyle = styles[`level${level}`]; + const levelStyle = headingLevelToClassName[level]; const HeadingTag = `h${level}` as React.ElementType; return ( diff --git a/manager-dashboard/app/components/Heading/styles.module.css b/manager-dashboard/app/components/Heading/styles.css similarity index 100% rename from manager-dashboard/app/components/Heading/styles.module.css rename to manager-dashboard/app/components/Heading/styles.css diff --git a/manager-dashboard/app/components/PopupButton/index.tsx b/manager-dashboard/app/components/PopupButton/index.tsx new file mode 100644 index 000000000..e4c2e9a12 --- /dev/null +++ b/manager-dashboard/app/components/PopupButton/index.tsx @@ -0,0 +1,105 @@ +import React from 'react'; +import { IoIosArrowDown, IoIosArrowUp } from 'react-icons/io'; + +import { _cs } from '@togglecorp/fujs'; + +import Button, { ButtonProps } from '#components/Button'; + +import useBlurEffect from '../../hooks/useBlurEffect'; + +import Popup from '../Popup'; + +import styles from './styles.css'; + +export interface PopupButtonProps extends Omit, 'label'> { + popupClassName?: string; + popupContentClassName?: string; + label: React.ReactNode; + componentRef?: React.MutableRefObject<{ + setPopupVisibility: React.Dispatch>; + } | null>; + persistent: boolean; + arrowHidden?: boolean; + defaultShown?: boolean; +} + +function PopupButton(props: PopupButtonProps) { + const { + popupClassName, + popupContentClassName, + children, + label, + name, + actions, + componentRef, + arrowHidden, + persistent, + defaultShown, + ...otherProps + } = props; + + const buttonRef = React.useRef(null); + const popupRef = React.useRef(null); + + const [popupShown, setPopupShown] = React.useState(defaultShown ?? false); + + React.useEffect( + () => { + if (componentRef) { + componentRef.current = { + setPopupVisibility: setPopupShown, + }; + } + }, + [componentRef], + ); + + useBlurEffect( + popupShown && !persistent, + setPopupShown, + popupRef, + buttonRef, + ); + + const handleShowPopup = React.useCallback( + () => { + setPopupShown((prevState) => !prevState); + }, + [], + ); + + return ( + <> + + {popupShown && ( + + {children} + + )} + + ); +} + +export default PopupButton; diff --git a/manager-dashboard/app/components/PopupButton/styles.css b/manager-dashboard/app/components/PopupButton/styles.css new file mode 100644 index 000000000..235f00864 --- /dev/null +++ b/manager-dashboard/app/components/PopupButton/styles.css @@ -0,0 +1,10 @@ +.popup { + padding-top: calc(var(--tui-spacing-large) - var(--tui-spacing-medium)); + padding-bottom: calc(var(--tui-spacing-large) - var(--tui-spacing-medium)); + + .popup-content { + display: flex; + flex-direction: column; + max-width: max(50vw, 300px); + } +} \ No newline at end of file diff --git a/manager-dashboard/app/components/Preview/styles.css b/manager-dashboard/app/components/Preview/styles.css index 23e80d351..08ae788b6 100644 --- a/manager-dashboard/app/components/Preview/styles.css +++ b/manager-dashboard/app/components/Preview/styles.css @@ -5,4 +5,17 @@ height: 20rem; object-fit: contain; object-position: center center; +} + +.no-preview { + display: flex; + align-items: center; + justify-content: center; + background-color: var(--color-background); + padding: var(--spacing-medium); + width: 100%; + max-width: 30rem; + height: 10rem; + color: var(--color-text-watermark); + font-size: var(--font-size-large); } \ No newline at end of file diff --git a/manager-dashboard/app/components/CustomOptionPreview/index.tsx b/manager-dashboard/app/views/NewTutorial/CustomOptionInput/CustomOptionPreview/index.tsx similarity index 66% rename from manager-dashboard/app/components/CustomOptionPreview/index.tsx rename to manager-dashboard/app/views/NewTutorial/CustomOptionInput/CustomOptionPreview/index.tsx index 1fbfa2abb..d9cf7b138 100644 --- a/manager-dashboard/app/components/CustomOptionPreview/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/CustomOptionInput/CustomOptionPreview/index.tsx @@ -22,11 +22,19 @@ export default function CustomOptionPreview(props: Props) { className={styles.previewContent} key={preview.optionId} > - {Icon && } -
+ {Icon + && ( +
+ +
+ )} +
{preview.title}
-
+
{preview.description}
@@ -41,7 +49,9 @@ export default function CustomOptionPreview(props: Props) { Preview
- +
+ +
); diff --git a/manager-dashboard/app/views/NewTutorial/CustomOptionInput/CustomOptionPreview/styles.css b/manager-dashboard/app/views/NewTutorial/CustomOptionInput/CustomOptionPreview/styles.css new file mode 100644 index 000000000..aeea11f9e --- /dev/null +++ b/manager-dashboard/app/views/NewTutorial/CustomOptionInput/CustomOptionPreview/styles.css @@ -0,0 +1,38 @@ +.option-preview { + display: flex; + align-items: center; + flex-direction: column; + flex-grow: 1; + + .preview-screen { + display: flex; + flex-direction: column; + background-color: var(--color-primary); + width: 20rem; + height: 30rem; + + .preview-container { + padding-top: var(--spacing-super-large); + + .preview-content { + display: flex; + align-items: center; + gap: var(--spacing-extra-small); + padding: var(--spacing-medium); + color: #ffffff; + + .preview-icon { + display: flex; + align-items: center; + border-radius: 50%; + padding: 3px; + font-size: 30px; + } + + .preview-text { + padding: var(--spacing-small); + } + } + } + } +} \ No newline at end of file diff --git a/manager-dashboard/app/views/NewTutorial/InformationPageInput/InformationPagePreview/index.tsx b/manager-dashboard/app/views/NewTutorial/InformationPageInput/InformationPagePreview/index.tsx index 4abf7fd17..1de8b6903 100644 --- a/manager-dashboard/app/views/NewTutorial/InformationPageInput/InformationPagePreview/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/InformationPageInput/InformationPagePreview/index.tsx @@ -18,7 +18,11 @@ export default function InformationPagePreview(props: Props) { function Content() { const innerContent = value?.blocks?.map((preview) => { if (preview.blockType === 'text') { - return
{preview.textDescription}
; + return ( +
+ {preview.textDescription ?? 'Description of the image'} +
+ ); } return (
-
{value?.title}
- +
+
+ {value?.title ?? 'Title'} +
+ +
); diff --git a/manager-dashboard/app/views/NewTutorial/InformationPageInput/InformationPagePreview/styles.css b/manager-dashboard/app/views/NewTutorial/InformationPageInput/InformationPagePreview/styles.css index 4cf883449..c4fb9c552 100644 --- a/manager-dashboard/app/views/NewTutorial/InformationPageInput/InformationPagePreview/styles.css +++ b/manager-dashboard/app/views/NewTutorial/InformationPageInput/InformationPagePreview/styles.css @@ -10,11 +10,13 @@ background-color: var(--color-primary); width: 20rem; height: 30rem; + overflow: auto; .preview-content { display: flex; - gap: var(--spacing-extra-small); - padding: var(--spacing-medium); + flex-direction: column; + gap: var(--spacing-medium); + padding: var(--spacing-large); color: #ffffff; } } diff --git a/manager-dashboard/app/views/NewTutorial/InformationPageInput/index.tsx b/manager-dashboard/app/views/NewTutorial/InformationPageInput/index.tsx index 83cb9fcd2..0f0337cdb 100644 --- a/manager-dashboard/app/views/NewTutorial/InformationPageInput/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/InformationPageInput/index.tsx @@ -46,7 +46,6 @@ export default function InformationPageInput(props: Props) { [error?.blocks], ); - console.log('information', value); return (
diff --git a/manager-dashboard/app/views/NewTutorial/index.tsx b/manager-dashboard/app/views/NewTutorial/index.tsx index a4f863b26..efb29c2b7 100644 --- a/manager-dashboard/app/views/NewTutorial/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/index.tsx @@ -54,8 +54,8 @@ import TileServerInput, { import InputSection from '#components/InputSection'; import Button from '#components/Button'; import Heading from '#components/Heading'; -import SelectInput from '#components/SelectInput'; -import CustomOptionPreview from '#components/CustomOptionPreview'; +import PopupButton from '#components/PopupButton'; +import CustomOptionPreview from '#views/NewTutorial/CustomOptionInput/CustomOptionPreview'; import { valueSelector, labelSelector, @@ -71,7 +71,6 @@ import { PartialTutorialFormType, ScenarioPagesType, CustomOptionType, - pageOptions, PageTemplateType, InformationPagesType, PartialInformationPagesType, @@ -82,13 +81,6 @@ import ScenarioPageInput from './ScenarioPageInput'; import InformationPageInput from './InformationPageInput'; import styles from './styles.css'; -function pageKeySelector(d: PageTemplateType) { - return d.key; -} -function pageLabelSelector(d: PageTemplateType) { - return d.label; -} - const defaultCustomOptions: PartialTutorialFormType['customOptions'] = [ { optionId: 1, @@ -275,12 +267,6 @@ function NewTutorial(props: Props) { createdBy: userId, }; - // if (uploadData) { - // console.info('upload', uploadData); - // setTutorialSubmissionStatus('failed'); - // return; - // } - const database = getDatabase(); const tutorialDraftsRef = databaseRef(database, 'v2/tutorialDrafts/'); const newTutorialDraftsRef = await pushToDatabase(tutorialDraftsRef); @@ -407,6 +393,10 @@ function NewTutorial(props: Props) { setFieldValue(tutorialTaskArray, 'scenarioPages'); }, [setFieldValue]); + const popupElementRef = React.useRef<{ + setPopupVisibility: React.Dispatch>; + }>(null); + const handleAddInformationPages = React.useCallback( (template: PageTemplateType['key']) => { setFieldValue( @@ -422,19 +412,19 @@ function NewTutorial(props: Props) { blocks = [ { blockNumber: 1, - blockType: 'image', + blockType: 'text', }, { blockNumber: 2, - blockType: 'text', + blockType: 'image', }, { blockNumber: 3, - blockType: 'image', + blockType: 'text', }, { blockNumber: 4, - blockType: 'text', + blockType: 'image', }, ]; } @@ -442,27 +432,27 @@ function NewTutorial(props: Props) { blocks = [ { blockNumber: 1, - blockType: 'image', + blockType: 'text', }, { blockNumber: 2, - blockType: 'text', + blockType: 'image', }, { blockNumber: 3, - blockType: 'image', + blockType: 'text', }, { blockNumber: 4, - blockType: 'text', + blockType: 'image', }, { blockNumber: 5, - blockType: 'image', + blockType: 'text', }, { blockNumber: 6, - blockType: 'text', + blockType: 'image', }, ]; } @@ -470,11 +460,11 @@ function NewTutorial(props: Props) { blocks = [ { blockNumber: 1, - blockType: 'image', + blockType: 'text', }, { blockNumber: 2, - blockType: 'text', + blockType: 'image', }, ]; } @@ -487,10 +477,14 @@ function NewTutorial(props: Props) { }, 'informationPages', ); + popupElementRef.current?.setPopupVisibility(false); }, [setFieldValue], ); + console.log('error', error?.tutorialTasks); + console.log('value', value?.tutorialTasks); + return (
@@ -593,20 +587,36 @@ function NewTutorial(props: Props) { title="Describe information pages" contentClassName={styles.card} > - = 10 - } - nonClearable - /> + + + + + {informationPagesError &&

{informationPagesError?.[nonFieldError]}

} {value.informationPages?.length ? ( { + const isScreen = tutorial?.features.find( + (screen) => isNotDefined(screen.properties.screen), + ); + if (isDefined(isScreen?.properties.screen)) { + return 'There are no screen in this Geojson'; + } + return undefined; + }, + }, exampleImage1: {}, exampleImage2: {}, }; @@ -334,7 +345,7 @@ export const tutorialFormSchema: TutorialFormSchema = { title: { required: true, requiredValidation: requiredStringCondition, - validations: [getNoMoreThanNCharacterCondition(500)], + validations: [getNoMoreThanNCharacterCondition(25)], }, value: { required: true, @@ -343,7 +354,7 @@ export const tutorialFormSchema: TutorialFormSchema = { description: { required: true, requiredValidation: requiredStringCondition, - validations: [getNoMoreThanNCharacterCondition(500)], + validations: [getNoMoreThanNCharacterCondition(100)], }, icon: { required: true, @@ -370,7 +381,7 @@ export const tutorialFormSchema: TutorialFormSchema = { description: { required: true, requiredValidation: requiredStringCondition, - validations: [getNoMoreThanNCharacterCondition(500)], + validations: [getNoMoreThanNCharacterCondition(100)], }, value: { required: true, From e1ffafb538bd1b244a01fff4587c111d7b707da0 Mon Sep 17 00:00:00 2001 From: shreeyash07 Date: Tue, 20 Jun 2023 10:20:19 +0545 Subject: [PATCH 24/72] rebase with dev --- .../app/components/GeoJsonFileInput/index.tsx | 5 ----- manager-dashboard/app/views/NewTutorial/index.tsx | 3 --- manager-dashboard/app/views/NewTutorial/utils.ts | 14 +------------- 3 files changed, 1 insertion(+), 21 deletions(-) diff --git a/manager-dashboard/app/components/GeoJsonFileInput/index.tsx b/manager-dashboard/app/components/GeoJsonFileInput/index.tsx index 365c9a882..1c8d8a50d 100644 --- a/manager-dashboard/app/components/GeoJsonFileInput/index.tsx +++ b/manager-dashboard/app/components/GeoJsonFileInput/index.tsx @@ -93,8 +93,6 @@ function GeoJsonFileInput(props: Props) { const file = newValue; - console.log('file', file); - async function handleValidationAndChange() { const text = await readUploadedFileAsText(file); let fileAsJson; @@ -103,8 +101,6 @@ function GeoJsonFileInput(props: Props) { return; } - console.log('text', text); - if (!text || typeof text !== 'string') { setInternalErrorMessage('Failed to read the GeoJson file'); setTempValue(newValue); @@ -113,7 +109,6 @@ function GeoJsonFileInput(props: Props) { } const parsedGeoJSON = parseGeoJSON(text); - console.log('Parsed', parsedGeoJSON);k if (!parsedGeoJSON.errored) { fileAsJson = parsedGeoJSON.value; diff --git a/manager-dashboard/app/views/NewTutorial/index.tsx b/manager-dashboard/app/views/NewTutorial/index.tsx index efb29c2b7..edc5bdbd3 100644 --- a/manager-dashboard/app/views/NewTutorial/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/index.tsx @@ -482,9 +482,6 @@ function NewTutorial(props: Props) { [setFieldValue], ); - console.log('error', error?.tutorialTasks); - console.log('value', value?.tutorialTasks); - return (
diff --git a/manager-dashboard/app/views/NewTutorial/utils.ts b/manager-dashboard/app/views/NewTutorial/utils.ts index 4921dbf5b..e541bc9a1 100644 --- a/manager-dashboard/app/views/NewTutorial/utils.ts +++ b/manager-dashboard/app/views/NewTutorial/utils.ts @@ -9,7 +9,6 @@ import { ArraySchema, addCondition, } from '@togglecorp/toggle-form'; -import { isDefined, isNotDefined } from '@togglecorp/fujs'; import { TileServer, tileServerFieldsSchema, @@ -304,18 +303,7 @@ export const tutorialFormSchema: TutorialFormSchema = { }), }, // FIXME: we do not send this anymore - tutorialTasks: { - required: true, - validation: (tutorial) => { - const isScreen = tutorial?.features.find( - (screen) => isNotDefined(screen.properties.screen), - ); - if (isDefined(isScreen?.properties.screen)) { - return 'There are no screen in this Geojson'; - } - return undefined; - }, - }, + tutorialTasks: {}, exampleImage1: {}, exampleImage2: {}, }; From d125f7ec511b7baa953fc2e1e6c275c301d8fb01 Mon Sep 17 00:00:00 2001 From: frozenhelium Date: Tue, 20 Jun 2023 16:46:14 +0545 Subject: [PATCH 25/72] Improve UI --- .../app/Base/components/Navbar/styles.css | 6 +- manager-dashboard/app/Base/styles.css | 7 +- .../app/components/Button/styles.css | 1 + .../app/components/Card/styles.css | 5 +- .../app/components/InputSection/index.tsx | 4 +- .../app/components/InputSection/styles.css | 1 - .../app/components/NonFieldError/index.tsx | 39 ++ .../app/components/NonFieldError/styles.css | 3 + .../ScenarioGeoJsonPreview/index.tsx | 101 +---- .../ScenarioGeoJsonPreview/styles.css | 11 +- .../app/components/Tabs/index.tsx | 16 +- .../app/components/Tabs/styles.css | 15 +- manager-dashboard/app/utils/common.ts | 35 +- manager-dashboard/app/views/Login/styles.css | 2 +- .../CustomOptionPreview/index.tsx | 52 +-- .../CustomOptionPreview/styles.css | 50 +-- .../CustomOptionInput/SubOption/index.tsx | 2 +- .../CustomOptionInput/SubOption/styles.css | 5 +- .../NewTutorial/CustomOptionInput/index.tsx | 92 ++-- .../NewTutorial/CustomOptionInput/styles.css | 14 +- .../InformationPagePreview/index.tsx | 54 ++- .../InformationPagePreview/styles.css | 28 +- .../InformationPageInput/index.tsx | 1 - .../InformationPageInput/styles.css | 11 +- .../NewTutorial/ScenarioPageInput/index.tsx | 29 +- .../app/views/NewTutorial/index.tsx | 424 ++++++++---------- .../app/views/NewTutorial/styles.css | 23 +- .../app/views/NewTutorial/utils.ts | 211 ++++++++- 28 files changed, 666 insertions(+), 576 deletions(-) create mode 100644 manager-dashboard/app/components/NonFieldError/index.tsx create mode 100644 manager-dashboard/app/components/NonFieldError/styles.css diff --git a/manager-dashboard/app/Base/components/Navbar/styles.css b/manager-dashboard/app/Base/components/Navbar/styles.css index 30110593c..869c7b47b 100644 --- a/manager-dashboard/app/Base/components/Navbar/styles.css +++ b/manager-dashboard/app/Base/components/Navbar/styles.css @@ -5,7 +5,7 @@ box-shadow: 0 3px 5px -2px var(--color-shadow); background-color: var(--color-primary); padding: var(--spacing-medium) var(--spacing-large); - color: var(--color-text-on-light); + color: var(--color-text-on-dark); .container { display: flex; @@ -42,7 +42,7 @@ .link { opacity: 0.7; - color: var(--color-text-on-light); + color: var(--color-text-on-dark); &.active { opacity: 1; @@ -58,7 +58,7 @@ gap: var(--spacing-medium); .logout-button { - color: var(--color-text-on-light); + color: var(--color-text-on-dark); } } } diff --git a/manager-dashboard/app/Base/styles.css b/manager-dashboard/app/Base/styles.css index 3ffb25fea..f7782f259 100644 --- a/manager-dashboard/app/Base/styles.css +++ b/manager-dashboard/app/Base/styles.css @@ -60,7 +60,7 @@ p { --color-primary: #0d1949; --color-primary-light: #2d5989; - --color-text-on-light: #ffffff; + --color-text-on-dark: #ffffff; --color-text-watermark: rgba(0, 0, 0, .4); --color-text: rgba(0, 0, 0, 0.8); --color-text-dark: rgba(0, 0, 0, 1); @@ -88,11 +88,14 @@ p { --opacity-disabled-element: 0.5; - --width-separator-thin: 1px; + --width-separator-thin: 1pt; --width-scrollbar: 0.75rem; --width-page-content-max: 70rem; --width-navbar-content-max: 76rem; + --width-mobile-preview: 24rem; + --height-mobile-preview: 40rem; + --radius-popup-border: 0.25rem; --radius-scrollbar-border: 0.25rem; --radius-blur-shadow: 5px; diff --git a/manager-dashboard/app/components/Button/styles.css b/manager-dashboard/app/components/Button/styles.css index 5bd4987a7..2670bc56d 100644 --- a/manager-dashboard/app/components/Button/styles.css +++ b/manager-dashboard/app/components/Button/styles.css @@ -4,6 +4,7 @@ border: var(--width-separator-thin) solid rgba(0, 0, 0, 0.3); border-radius: var(--radius-button-border); padding: var(--spacing-small) var(--spacing-medium); + width: fit-content; color: rgba(0, 0, 0, 0.8); gap: var(--spacing-small); diff --git a/manager-dashboard/app/components/Card/styles.css b/manager-dashboard/app/components/Card/styles.css index 56aaac368..75fc13db7 100644 --- a/manager-dashboard/app/components/Card/styles.css +++ b/manager-dashboard/app/components/Card/styles.css @@ -1,7 +1,6 @@ .card { - border: 0.5px; background-color: var(--color-foreground); - padding: var(--spacing); + padding: var(--spacing-medium); .title { padding: var(--spacing-medium); @@ -22,4 +21,4 @@ } } } -} \ No newline at end of file +} diff --git a/manager-dashboard/app/components/InputSection/index.tsx b/manager-dashboard/app/components/InputSection/index.tsx index dd63e0fd4..3cab44e6e 100644 --- a/manager-dashboard/app/components/InputSection/index.tsx +++ b/manager-dashboard/app/components/InputSection/index.tsx @@ -7,6 +7,7 @@ interface Props { className?: string; heading?: React.ReactNode; children?: React.ReactNode; + contentClassName?: string; } function InputSection(props: Props) { @@ -14,6 +15,7 @@ function InputSection(props: Props) { className, heading, children, + contentClassName, } = props; return ( @@ -23,7 +25,7 @@ function InputSection(props: Props) { {heading}
-
+
{children}
diff --git a/manager-dashboard/app/components/InputSection/styles.css b/manager-dashboard/app/components/InputSection/styles.css index fde96fe6f..ca1ec233f 100644 --- a/manager-dashboard/app/components/InputSection/styles.css +++ b/manager-dashboard/app/components/InputSection/styles.css @@ -11,7 +11,6 @@ display: flex; flex-direction: column; border-radius: var(--radius-card); - padding: var(--spacing-large); gap: var(--spacing-extra-large); } } diff --git a/manager-dashboard/app/components/NonFieldError/index.tsx b/manager-dashboard/app/components/NonFieldError/index.tsx new file mode 100644 index 000000000..aa91856d3 --- /dev/null +++ b/manager-dashboard/app/components/NonFieldError/index.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { _cs, isNotDefined } from '@togglecorp/fujs'; +import { + Error, + nonFieldError, + getErrorObject, +} from '@togglecorp/toggle-form'; + +import styles from './styles.css'; + +interface Props { + className?: string; + error: Error; +} + +function NonFieldError(props: Props) { + const { + className, + error, + } = props; + + if (!error) { + return null; + } + + const errorMessage = getErrorObject(error)?.[nonFieldError]; + + if (isNotDefined(errorMessage)) { + return null; + } + + return ( +
+ {errorMessage} +
+ ); +} + +export default NonFieldError; diff --git a/manager-dashboard/app/components/NonFieldError/styles.css b/manager-dashboard/app/components/NonFieldError/styles.css new file mode 100644 index 000000000..cc6d8bdd4 --- /dev/null +++ b/manager-dashboard/app/components/NonFieldError/styles.css @@ -0,0 +1,3 @@ +.non-field-error { + color: var(--color-danger); +} diff --git a/manager-dashboard/app/components/ScenarioGeoJsonPreview/index.tsx b/manager-dashboard/app/components/ScenarioGeoJsonPreview/index.tsx index ebde4f4d5..3f0f6ec49 100644 --- a/manager-dashboard/app/components/ScenarioGeoJsonPreview/index.tsx +++ b/manager-dashboard/app/components/ScenarioGeoJsonPreview/index.tsx @@ -1,15 +1,10 @@ import React from 'react'; -import { - map as createMap, - Map, - tileLayer, - geoJSON, -} from 'leaflet'; import { _cs } from '@togglecorp/fujs'; -import Heading from '#components/Heading'; +import GeoJsonPreview from '#components/GeoJsonPreview'; import styles from './styles.css'; -import { iconMap } from '#utils/common'; + +import { iconMap, IconKey } from '#utils/common'; interface Props { className?: string; @@ -17,7 +12,7 @@ interface Props { previewPopUp?: { title?: string; description?: string; - icon?: string; + icon?: IconKey; } } @@ -28,80 +23,11 @@ function ScenarioGeoJsonPreview(props: Props) { previewPopUp, } = props; - const mapRef = React.useRef(); - const mapContainerRef = React.useRef(null); - - React.useEffect( - () => { - if (mapContainerRef.current && !mapRef.current) { - mapRef.current = createMap(mapContainerRef.current); - } - - if (mapRef.current) { - // NOTE: show whole world by default - mapRef.current.setView( - [0.0, 0.0], - 1, - ); - - const layer = tileLayer( - 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', - { - attribution: '© OpenStreetMap', - subdomains: ['a', 'b', 'c'], - }, - ); - - layer.addTo(mapRef.current); - mapRef.current.invalidateSize(); - } - - return () => { - if (mapRef.current) { - mapRef.current.remove(); - mapRef.current = undefined; - } - }; - }, - [], - ); - - React.useEffect( - () => { - if (!geoJson) { - return undefined; - } - - const map = mapRef.current; - if (!map) { - return undefined; - } - - const newGeoJson = geoJSON(); - newGeoJson.addTo(map); - - newGeoJson.addData(geoJson); - const bounds = newGeoJson.getBounds(); - - if (bounds.isValid()) { - map.fitBounds(bounds); - } - - return () => { - newGeoJson.removeFrom(map); - newGeoJson.remove(); - }; - }, - [geoJson], - ); - const Comp = previewPopUp?.icon ? iconMap[previewPopUp.icon] : undefined; + console.info(geoJson); return (
- - Preview -
@@ -111,18 +37,15 @@ function ScenarioGeoJsonPreview(props: Props) { {previewPopUp?.description ?? 'Description'}
- { - Comp - && ( -
- -
- ) - } + { Comp && ( +
+ +
+ )}
-
); diff --git a/manager-dashboard/app/components/ScenarioGeoJsonPreview/styles.css b/manager-dashboard/app/components/ScenarioGeoJsonPreview/styles.css index a4fc7f495..a7574b8ca 100644 --- a/manager-dashboard/app/components/ScenarioGeoJsonPreview/styles.css +++ b/manager-dashboard/app/components/ScenarioGeoJsonPreview/styles.css @@ -6,23 +6,20 @@ flex-shrink: 0; gap: var(--spacing-medium); z-index: 0; - width: 100%; - max-width: 20rem; - height: 30rem; .map-container { z-index: -1; - width: 100%; - height: 100%; + width: var(--width-mobile-preview); + height: var(--height-mobile-preview); } .map-content { display: flex; position: absolute; - top: 60px; + top: 3rem; opacity: 0.95; border-radius: 10px; - background-color: #ffffff; + background-color: var(--color-foreground); padding: var(--spacing-medium); gap: var(--spacing-large); width: 90%; diff --git a/manager-dashboard/app/components/Tabs/index.tsx b/manager-dashboard/app/components/Tabs/index.tsx index 98bc6552a..0404419f1 100644 --- a/manager-dashboard/app/components/Tabs/index.tsx +++ b/manager-dashboard/app/components/Tabs/index.tsx @@ -4,7 +4,7 @@ import RawButton, { Props as RawButtonProps } from '../RawButton'; import styles from './styles.css'; -type TabKey = string; +type TabKey = string | number; export interface TabContextProps { activeTab: TabKey | undefined; @@ -12,7 +12,7 @@ export interface TabContextProps { } const TabContext = React.createContext({ - activeTab: '', + activeTab: undefined, setActiveTab: () => { // eslint-disable-next-line no-console console.warn('setActiveTab called before it was initialized'); @@ -75,7 +75,7 @@ export function TabList(props: TabListProps) { ); } -export interface TabPanelProps extends React.HTMLProps { +export interface TabPanelProps extends Omit, 'name'> { name: TabKey; elementRef?: React.Ref; className?: string; @@ -105,13 +105,13 @@ export function TabPanel(props: TabPanelProps) { ); } -export interface Props { +export interface Props { children: React.ReactNode; - value: TabKey; - onChange: (key: TabKey) => void; + value: VALUE; + onChange: (key: VALUE) => void; } -export function Tabs(props: Props) { +export function Tabs(props: Props) { const { children, value, @@ -121,7 +121,7 @@ export function Tabs(props: Props) { const contextValue = React.useMemo(() => ({ // Note: following cast is required since we do not have any other method // to provide template in the context type - activeTab: value, + activeTab: value as TabKey, setActiveTab: onChange as (key: TabKey | undefined) => void, }), [value, onChange]); diff --git a/manager-dashboard/app/components/Tabs/styles.css b/manager-dashboard/app/components/Tabs/styles.css index 28e354241..c6a4bab5a 100644 --- a/manager-dashboard/app/components/Tabs/styles.css +++ b/manager-dashboard/app/components/Tabs/styles.css @@ -1,19 +1,18 @@ .tab { - border: 1px solid rgba(0,0,0,.1); - border-bottom: none; - border-radius: 5px 5px 0 0; - background-color: #FFFFFF; + border: var(--width-separator-thin) solid var(--color-separator); + border-radius: 0.25rem 0.25rem 0 0; + border-bottom-color: transparent; + background-color: var(--color-foreground); padding: var(--spacing-small); text-align: left; - + &.active { - bottom: -1px; - background-color: #FBFBFF; + background-color: var(--color-background-accent-hint); } } .tab-list { display: flex; gap: var(--spacing-small); - border-bottom: 1px solid rgba(0,0,0,.1); + border-bottom: var(--width-separator-thin) solid var(--color-separator); } diff --git a/manager-dashboard/app/utils/common.ts b/manager-dashboard/app/utils/common.ts index 4e6c76388..d141c0cd7 100644 --- a/manager-dashboard/app/utils/common.ts +++ b/manager-dashboard/app/utils/common.ts @@ -65,13 +65,40 @@ export const projectTypeLabelMap: { [PROJECT_TYPE_COMPLETENESS]: 'Completeness', }; -export interface IconList { - key: string; +export type IconKey = 'addOutline' + | 'alertOutline' + | 'banOutline' + | 'checkmarkOutline' + | 'closeOutline' + | 'eggOutline' + | 'ellipseOutline' + | 'flagOutline' + | 'handLeftOutline' + | 'handRightOutline' + | 'happyOutline' + | 'heartOutline' + | 'helpOutline' + | 'informationOutline' + | 'prismOutline' + | 'refreshOutline' + | 'removeOutline' + | 'sadOutline' + | 'searchOutline' + | 'shapesOutline' + | 'squareOutline' + | 'starOutline' + | 'thumbsDownOutline' + | 'thumbsUpOutline' + | 'triangleOutline' + | 'warningOutline'; + +export interface IconItem { + key: IconKey; label: string; - component: IconType + component: IconType; } -export const iconList: IconList[] = [ +export const iconList: IconItem[] = [ { key: 'addOutline', label: 'Add', diff --git a/manager-dashboard/app/views/Login/styles.css b/manager-dashboard/app/views/Login/styles.css index 82d1d718a..a9726a911 100644 --- a/manager-dashboard/app/views/Login/styles.css +++ b/manager-dashboard/app/views/Login/styles.css @@ -25,7 +25,7 @@ } .text { - color: var(--color-text-on-light); + color: var(--color-text-on-dark); } } diff --git a/manager-dashboard/app/views/NewTutorial/CustomOptionInput/CustomOptionPreview/index.tsx b/manager-dashboard/app/views/NewTutorial/CustomOptionInput/CustomOptionPreview/index.tsx index d9cf7b138..50c0589b1 100644 --- a/manager-dashboard/app/views/NewTutorial/CustomOptionInput/CustomOptionPreview/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/CustomOptionInput/CustomOptionPreview/index.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import Heading from '#components/Heading'; import { PartialTutorialFormType } from '#views/NewTutorial/utils'; import { iconMap } from '#utils/common'; @@ -14,16 +13,21 @@ export default function CustomOptionPreview(props: Props) { value, } = props; - function Content() { - const innerContent = value?.map((preview) => { - const Icon = preview.icon ? iconMap[preview.icon] : undefined; - return ( -
- {Icon - && ( + return ( +
+ {value?.map((preview) => { + const Icon = preview.icon ? iconMap[preview.icon] : undefined; + const previewText = [ + preview.title, + preview.description, + ].filter(Boolean).join(' - '); + + return ( +
+ {Icon && (
)} -
- {preview.title} -
-
- {preview.description} +
+ {previewText} +
-
- ); - }); - return <>{innerContent}; - } - - return ( -
- - Preview - -
-
- -
-
+ ); + })}
); } diff --git a/manager-dashboard/app/views/NewTutorial/CustomOptionInput/CustomOptionPreview/styles.css b/manager-dashboard/app/views/NewTutorial/CustomOptionInput/CustomOptionPreview/styles.css index aeea11f9e..321b2617c 100644 --- a/manager-dashboard/app/views/NewTutorial/CustomOptionInput/CustomOptionPreview/styles.css +++ b/manager-dashboard/app/views/NewTutorial/CustomOptionInput/CustomOptionPreview/styles.css @@ -1,38 +1,28 @@ .option-preview { display: flex; - align-items: center; flex-direction: column; - flex-grow: 1; + background-color: var(--color-primary); + padding: var(--spacing-large); + gap: var(--spacing-large); + width: var(--width-mobile-preview); + height: var(--height-mobile-preview); - .preview-screen { + .preview-content { display: flex; - flex-direction: column; - background-color: var(--color-primary); - width: 20rem; - height: 30rem; + align-items: center; + gap: var(--spacing-medium); + color: var(--color-text-on-dark); - .preview-container { - padding-top: var(--spacing-super-large); - - .preview-content { - display: flex; - align-items: center; - gap: var(--spacing-extra-small); - padding: var(--spacing-medium); - color: #ffffff; - - .preview-icon { - display: flex; - align-items: center; - border-radius: 50%; - padding: 3px; - font-size: 30px; - } - - .preview-text { - padding: var(--spacing-small); - } - } + .preview-icon { + display: flex; + align-items: center; + border-radius: 50%; + padding: 3px; + font-size: 30px; + } + + .preview-text { + font-size: var(--font-size-small); } } -} \ No newline at end of file +} diff --git a/manager-dashboard/app/views/NewTutorial/CustomOptionInput/SubOption/index.tsx b/manager-dashboard/app/views/NewTutorial/CustomOptionInput/SubOption/index.tsx index dd88859e9..3492ffe49 100644 --- a/manager-dashboard/app/views/NewTutorial/CustomOptionInput/SubOption/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/CustomOptionInput/SubOption/index.tsx @@ -56,7 +56,7 @@ export default function SubOption(props: Props) { onClick={onRemove} className={styles.removeButton} > - Delete this Reason + Remove
); diff --git a/manager-dashboard/app/views/NewTutorial/CustomOptionInput/SubOption/styles.css b/manager-dashboard/app/views/NewTutorial/CustomOptionInput/SubOption/styles.css index db7513432..4577244ee 100644 --- a/manager-dashboard/app/views/NewTutorial/CustomOptionInput/SubOption/styles.css +++ b/manager-dashboard/app/views/NewTutorial/CustomOptionInput/SubOption/styles.css @@ -1,12 +1,13 @@ .sub-option-content { display: flex; + align-items: flex-end; gap: var(--spacing-medium); .sub-option-input { flex-grow: 1; } + .remove-button { width: fit-content; - height: fit-content; } -} \ No newline at end of file +} diff --git a/manager-dashboard/app/views/NewTutorial/CustomOptionInput/index.tsx b/manager-dashboard/app/views/NewTutorial/CustomOptionInput/index.tsx index 4465d2c10..0877abf2f 100644 --- a/manager-dashboard/app/views/NewTutorial/CustomOptionInput/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/CustomOptionInput/index.tsx @@ -7,14 +7,13 @@ import { useFormObject, getErrorObject, useFormArray, - nonFieldError, } from '@togglecorp/toggle-form'; import Button from '#components/Button'; import Heading from '#components/Heading'; -import Tabs, { TabList, Tab, TabPanel } from '#components/Tabs'; import TextInput from '#components/TextInput'; import NumberInput from '#components/NumberInput'; import SelectInput from '#components/SelectInput'; +import NonFieldError from '#components/NonFieldError'; import SubOption from './SubOption'; import { @@ -24,7 +23,7 @@ import { } from '../utils'; import styles from './styles.css'; -import { IconList, iconList } from '#utils/common'; +import { IconItem, iconList } from '#utils/common'; export type PartialCustomOptionsType = NonNullable[number] const defaultcustomOptionsValue: PartialCustomOptionsType = { @@ -50,8 +49,6 @@ export default function CustomOptionInput(props: Props) { error: riskyError, } = props; - const [activesubOptions, setActiveSubOptions] = React.useState('1'); - const onOptionChange = useFormObject(index, onChange, defaultcustomOptionsValue); const { @@ -89,7 +86,7 @@ export default function CustomOptionInput(props: Props) { ); return ( -
+
d.key} - labelSelector={(d: IconList) => d.label} + keySelector={(d: IconItem) => d.key} + labelSelector={(d: IconItem) => d.label} onChange={onOptionChange} error={error?.icon} /> @@ -126,7 +123,7 @@ export default function CustomOptionInput(props: Props) { label="Icon Color" value={value?.iconColor} options={iconColorOptions} - keySelector={(d: ColorOptions) => d.key} + keySelector={(d: ColorOptions) => d.color} labelSelector={(d: ColorOptions) => d.label} onChange={onOptionChange} error={error?.iconColor} @@ -145,55 +142,38 @@ export default function CustomOptionInput(props: Props) { onClick={onRemove} className={styles.removeButton} > - Delete this option - - - Sub Options - - - {subOptionsError && subOptionsError?.[nonFieldError]} - {value.subOptions?.length ? ( - + + Sub Options + + + {value.subOptions?.map((sub, i) => ( + + ))} + {!value.subOptions?.length && ( +
No sub options at the moment
+ )} + +
); } diff --git a/manager-dashboard/app/views/NewTutorial/CustomOptionInput/styles.css b/manager-dashboard/app/views/NewTutorial/CustomOptionInput/styles.css index 0a2844aa9..a7159de73 100644 --- a/manager-dashboard/app/views/NewTutorial/CustomOptionInput/styles.css +++ b/manager-dashboard/app/views/NewTutorial/CustomOptionInput/styles.css @@ -1,7 +1,7 @@ -.option-form { +.custom-option { display: flex; flex-direction: column; - gap: var(--spacing-medium); + gap: var(--spacing-large); .option-content { display: flex; @@ -12,7 +12,7 @@ } .option-icon { - flex-basis: 15%; + flex-basis: 10rem; } } @@ -24,4 +24,12 @@ .add-button { width: fit-content; } + + .sub-options { + display: flex; + flex-direction: column; + gap: var(--spacing-large); + background-color: var(--color-background); + padding: var(--spacing-large); + } } diff --git a/manager-dashboard/app/views/NewTutorial/InformationPageInput/InformationPagePreview/index.tsx b/manager-dashboard/app/views/NewTutorial/InformationPageInput/InformationPagePreview/index.tsx index 1de8b6903..7f485ae27 100644 --- a/manager-dashboard/app/views/NewTutorial/InformationPageInput/InformationPagePreview/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/InformationPageInput/InformationPagePreview/index.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { PartialTutorialFormType } from '#views/NewTutorial/utils'; -import Heading from '#components/Heading'; import Preview from '#components/Preview'; import styles from './styles.css'; @@ -15,37 +14,34 @@ export default function InformationPagePreview(props: Props) { value, } = props; - function Content() { - const innerContent = value?.blocks?.map((preview) => { - if (preview.blockType === 'text') { - return ( -
- {preview.textDescription ?? 'Description of the image'} -
- ); - } - return ( - - ); - }); - return <>{innerContent}; - } return (
- - Preview - -
-
-
- {value?.title ?? 'Title'} -
- + {value?.title && ( +
+ {value?.title}
-
+ )} + {value?.blocks?.map((preview) => { + if (preview.blockType === 'text' && preview.textDescription) { + return ( +
+ {preview.textDescription} +
+ ); + } + + if (preview.blockType === 'image') { + return ( + + ); + } + + return null; + })}
); } diff --git a/manager-dashboard/app/views/NewTutorial/InformationPageInput/InformationPagePreview/styles.css b/manager-dashboard/app/views/NewTutorial/InformationPageInput/InformationPagePreview/styles.css index c4fb9c552..a18a8b9d7 100644 --- a/manager-dashboard/app/views/NewTutorial/InformationPageInput/InformationPagePreview/styles.css +++ b/manager-dashboard/app/views/NewTutorial/InformationPageInput/InformationPagePreview/styles.css @@ -1,23 +1,15 @@ .information-preview { display: flex; - align-items: center; flex-direction: column; - flex-grow: 1; + gap: var(--spacing-large); + background-color: var(--color-primary); + padding: var(--spacing-large); + width: var(--width-mobile-preview); + height: var(--height-mobile-preview); + overflow: auto; + color: var(--color-text-on-dark); - .preview-screen { - display: flex; - flex-direction: column; - background-color: var(--color-primary); - width: 20rem; - height: 30rem; - overflow: auto; - - .preview-content { - display: flex; - flex-direction: column; - gap: var(--spacing-medium); - padding: var(--spacing-large); - color: #ffffff; - } + .image-preview { + height: 10rem; } -} \ No newline at end of file +} diff --git a/manager-dashboard/app/views/NewTutorial/InformationPageInput/index.tsx b/manager-dashboard/app/views/NewTutorial/InformationPageInput/index.tsx index 0f0337cdb..194ea201c 100644 --- a/manager-dashboard/app/views/NewTutorial/InformationPageInput/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/InformationPageInput/index.tsx @@ -68,7 +68,6 @@ export default function InformationPageInput(props: Props) { diff --git a/manager-dashboard/app/views/NewTutorial/InformationPageInput/styles.css b/manager-dashboard/app/views/NewTutorial/InformationPageInput/styles.css index 88f72a095..6e4a55005 100644 --- a/manager-dashboard/app/views/NewTutorial/InformationPageInput/styles.css +++ b/manager-dashboard/app/views/NewTutorial/InformationPageInput/styles.css @@ -4,13 +4,8 @@ .informationForm { display: flex; - flex-basis: 60%; flex-direction: column; - gap: var(--spacing-medium); - - .remove-button { - align-self: end; - width: fit-content; - } + flex-grow: 1; + gap: var(--spacing-large); } -} \ No newline at end of file +} diff --git a/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/index.tsx b/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/index.tsx index 659d769d9..f025ab709 100644 --- a/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/index.tsx @@ -7,10 +7,11 @@ import { getErrorObject, } from '@togglecorp/toggle-form'; import { - IconList, + IconItem, iconList, valueSelector, labelSelector, + IconKey, } from '#utils/common'; import TextInput from '#components/TextInput'; import Heading from '#components/Heading'; @@ -21,20 +22,20 @@ import SegmentInput from '#components/SegmentInput'; import styles from './styles.css'; type ScenarioType = { - scenarioId: string; + scenarioId: number; hint: { description: string; - icon: string; + icon: IconKey; title: string; }; instruction: { description: string; - icon: string; + icon: IconKey; title: string; }; success: { description: string; - icon: string; + icon: IconKey; title: string; }; }; @@ -52,7 +53,7 @@ const previewOptions: ScenarioSegmentType[] = [ type PartialScenarioType = PartialForm; const defaultScenarioTabsValue: PartialScenarioType = { - scenarioId: 'xxx', + scenarioId: 0, }; interface Props { @@ -120,8 +121,8 @@ export default function ScenarioPageInput(props: Props) { label="Icon" value={value.instruction?.icon} options={iconList} - keySelector={(d: IconList) => d.key} - labelSelector={(d: IconList) => d.label} + keySelector={(d: IconItem) => d.key} + labelSelector={(d: IconItem) => d.label} onChange={onInstructionFieldChange} error={instructionsError?.icon} /> @@ -151,8 +152,8 @@ export default function ScenarioPageInput(props: Props) { label="Icon" value={value.hint?.icon} options={iconList} - keySelector={(d: IconList) => d.key} - labelSelector={(d: IconList) => d.label} + keySelector={(d: IconItem) => d.key} + labelSelector={(d: IconItem) => d.label} onChange={onHintFieldChange} error={hintError?.icon} /> @@ -163,14 +164,14 @@ export default function ScenarioPageInput(props: Props) {
d.key} - labelSelector={(d: IconList) => d.label} + keySelector={(d: IconItem) => d.key} + labelSelector={(d: IconItem) => d.label} onChange={onSuccessFieldChange} error={successError?.icon} /> diff --git a/manager-dashboard/app/views/NewTutorial/index.tsx b/manager-dashboard/app/views/NewTutorial/index.tsx index edc5bdbd3..448a289c6 100644 --- a/manager-dashboard/app/views/NewTutorial/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/index.tsx @@ -3,6 +3,7 @@ import { _cs, isDefined, unique, + getElementAround, } from '@togglecorp/fujs'; import { useForm, @@ -10,7 +11,6 @@ import { createSubmitHandler, analyzeErrors, useFormArray, - nonFieldError, } from '@togglecorp/toggle-form'; import { getStorage, @@ -35,6 +35,7 @@ import { Link } from 'react-router-dom'; import UserContext from '#base/context/UserContext'; import projectTypeOptions from '#base/configs/projectTypes'; import useMountedRef from '#hooks/useMountedRef'; +import useInputState from '#hooks/useInputState'; import Card from '#components/Card'; import Modal from '#components/Modal'; import TextInput from '#components/TextInput'; @@ -53,8 +54,6 @@ import TileServerInput, { } from '#components/TileServerInput'; import InputSection from '#components/InputSection'; import Button from '#components/Button'; -import Heading from '#components/Heading'; -import PopupButton from '#components/PopupButton'; import CustomOptionPreview from '#views/NewTutorial/CustomOptionInput/CustomOptionPreview'; import { valueSelector, @@ -71,37 +70,44 @@ import { PartialTutorialFormType, ScenarioPagesType, CustomOptionType, - PageTemplateType, InformationPagesType, PartialInformationPagesType, - PartialBlocksType, + colorKeyToColorMap, + InformationPageTemplateKey, + infoPageTemplateoptions, + infoPageBlocksMap, } from './utils'; import CustomOptionInput from './CustomOptionInput'; import ScenarioPageInput from './ScenarioPageInput'; import InformationPageInput from './InformationPageInput'; import styles from './styles.css'; +import NonFieldError from '#components/NonFieldError'; +import SelectInput from '#components/SelectInput'; const defaultCustomOptions: PartialTutorialFormType['customOptions'] = [ { optionId: 1, value: 1, title: 'Yes', - icon: 'addOutline', - iconColor: 'green', + icon: 'checkmarkOutline', + iconColor: colorKeyToColorMap.green, + description: 'the shape does outline a building in the image', }, { - value: 0, optionId: 2, + value: 0, title: 'No', - icon: 'alertOutline', - iconColor: 'red', + icon: 'closeOutline', + iconColor: colorKeyToColorMap.red, + description: 'the shape doesn\'t match a building in the image', }, { - value: 2, optionId: 3, + value: 2, title: 'Not Sure', - icon: 'banOutline', - iconColor: 'yellow', + icon: 'removeOutline', + iconColor: colorKeyToColorMap.orange, + description: 'if you\'re not sure or there is cloud cover / bad imagery', }, ]; const defaultTutorialFormValue: PartialTutorialFormType = { @@ -146,12 +152,10 @@ function NewTutorial(props: Props) { setTutorialSubmissionStatus, ] = React.useState<'started' | 'imageUpload' | 'tutorialSubmit' | 'success' | 'failed' | undefined>(); - // NOTE: scenario - const [activeTab, setActiveTab] = React.useState('1'); - // NOTE: options - const [activeOptionsTab, setActiveOptionsTab] = React.useState('1'); - // NOTE: Information Page - const [activeInformationPages, setActiveInformationPages] = React.useState('1'); + const [activeScenarioTab, setActiveScenarioTab] = React.useState(1); + const [activeOptionsTab, setActiveOptionsTab] = React.useState(1); + const [activeInformationPage, setActiveInformationPage] = React.useState(1); + const [selectedInfoPageTemplate, setSelectedInfoPageTemplate] = useInputState('1-picture'); const error = React.useMemo( () => getErrorObject(formError), @@ -174,18 +178,22 @@ function NewTutorial(props: Props) { const previewGeoJson = React.useMemo((): GeoJSON.GeoJSON | undefined => { const geojson = value.tutorialTasks; + if (!geojson) { return undefined; } + + console.info(activeScenarioTab, typeof activeScenarioTab, geojson); + return { ...geojson, features: geojson.features.filter( - (screen) => screen.properties.screen === Number(activeTab), + (screen) => screen.properties.screen === activeScenarioTab, ), }; }, [ value.tutorialTasks, - activeTab, + activeScenarioTab, ]); const handleFormSubmission = React.useCallback(( @@ -318,21 +326,45 @@ function NewTutorial(props: Props) { ); const { - setValue: onOptionAdd, + setValue: setOptionValue, removeValue: onOptionRemove, } = useFormArray< 'customOptions', CustomOptionType >('customOptions', setFieldValue); + const handleOptionRemove = React.useCallback( + (index: number) => { + const nextOption = getElementAround(value?.customOptions ?? [], index); + onOptionRemove(index); + + if (nextOption) { + setActiveOptionsTab(nextOption.optionId); + } + }, + [onOptionRemove, value?.customOptions], + ); + const { - setValue: onInformationPagesAdd, - removeValue: onInformationPagesRemove, + setValue: setInformationPageValue, + removeValue: onInformationPageRemove, } = useFormArray< 'informationPages', InformationPagesType >('informationPages', setFieldValue); + const handleInformationPageRemove = React.useCallback( + (index: number) => { + const nextInfoPage = getElementAround(value?.informationPages ?? [], index); + onInformationPageRemove(index); + + if (nextInfoPage) { + setActiveInformationPage(nextInfoPage.pageNumber); + } + }, + [onInformationPageRemove, value?.informationPages], + ); + const handleAddDefineOptions = React.useCallback( () => { setFieldValue( @@ -349,6 +381,9 @@ function NewTutorial(props: Props) { const newDefineOption: CustomOptionType = { optionId: newOptionId, value: newValue, + icon: 'starOutline', + title: 'Untitled', + iconColor: colorKeyToColorMap.gray, }; return [...safeOldValues, newDefineOption]; @@ -384,7 +419,7 @@ function NewTutorial(props: Props) { const sorted = uniqueArray?.sort((a, b) => a.properties?.screen - b.properties.screen); const tutorialTaskArray = sorted?.map((geo) => ( { - scenarioId: String(geo.properties.screen), + scenarioId: geo.properties.screen, hint: {}, instructions: {}, success: {}, @@ -397,80 +432,22 @@ function NewTutorial(props: Props) { setPopupVisibility: React.Dispatch>; }>(null); - const handleAddInformationPages = React.useCallback( - (template: PageTemplateType['key']) => { + const handleAddInformationPage = React.useCallback( + (template: InformationPageTemplateKey) => { setFieldValue( (oldValue: PartialInformationPagesType) => { const newOldValue = oldValue ?? []; - let blocks: PartialBlocksType = []; const newPage = newOldValue.length > 0 ? Math.max(...newOldValue.map((info) => info.pageNumber)) + 1 : 1; - if (template === '2-picture') { - blocks = [ - { - blockNumber: 1, - blockType: 'text', - }, - { - blockNumber: 2, - blockType: 'image', - }, - { - blockNumber: 3, - blockType: 'text', - }, - { - blockNumber: 4, - blockType: 'image', - }, - ]; - } - if (template === '3-picture') { - blocks = [ - { - blockNumber: 1, - blockType: 'text', - }, - { - blockNumber: 2, - blockType: 'image', - }, - { - blockNumber: 3, - blockType: 'text', - }, - { - blockNumber: 4, - blockType: 'image', - }, - { - blockNumber: 5, - blockType: 'text', - }, - { - blockNumber: 6, - blockType: 'image', - }, - ]; - } - if (template === '1-picture') { - blocks = [ - { - blockNumber: 1, - blockType: 'text', - }, - { - blockNumber: 2, - blockType: 'image', - }, - ]; - } + setActiveInformationPage(newPage); + const blocks = infoPageBlocksMap[template]; const newPageInformation: InformationPagesType = { pageNumber: newPage, + title: `Untitled page ${newPage}`, blocks, }; return [...newOldValue, newPageInformation]; @@ -526,140 +503,16 @@ function NewTutorial(props: Props) { />
- - - - - - {value.scenarioPages?.length ? ( -
- - {value.scenarioPages?.map((task) => ( - - {`Scenario ${task.scenarioId}`} - - ))} - - {value.scenarioPages?.map((task, index) => ( - - - - ))} -
- ) : ( -
No Scenarios
- )} -
-
- + {value.projectType === PROJECT_TYPE_FOOTPRINT && ( + - - - - - - {informationPagesError &&

{informationPagesError?.[nonFieldError]}

} - {value.informationPages?.length ? ( - - - {value.informationPages.map((info) => ( - - {`Intro ${info.pageNumber}`} - - ))} - - {value.informationPages?.map((page, i) => ( - - - - ))} - - ) : ( -
Add Page
- )} -
- {value.projectType === PROJECT_TYPE_FOOTPRINT && ( - - Option Instructions - - {optionsError &&

{optionsError?.[nonFieldError]}

} + {value.customOptions?.length ? ( ( {`Option ${opt.optionId}`} @@ -690,15 +545,15 @@ function NewTutorial(props: Props) { {value.customOptions.map((options, index) => ( @@ -709,10 +564,129 @@ function NewTutorial(props: Props) {
) : ( -
Add options
+
No sub-options at the moment
)} - )} + + )} + + +
+ infoPageTemplate.key} + labelSelector={(infoPageTemplate) => infoPageTemplate.label} + className={styles.instructionPopup} + /> + +
+ + {value.informationPages && value.informationPages.length > 0 && ( + + + {value.informationPages.map((info) => ( + + {`Intro ${info.pageNumber}`} + + ))} + + {value.informationPages?.map((page, i) => ( + + + + ))} + + )} + {!(value.informationPages?.length) && ( +
+ No information pages at the moment +
+ )} +
+
+ + + + + + + {value.scenarioPages?.length ? ( +
+ + {value.scenarioPages?.map((task) => ( + + {`Scenario ${task.scenarioId}`} + + ))} + + {value.scenarioPages?.map((task, index) => ( + + + + ))} +
+ ) : ( +
No Scenarios at the moment
+ )} +
+
{ (value?.projectType === PROJECT_TYPE_BUILD_AREA diff --git a/manager-dashboard/app/views/NewTutorial/styles.css b/manager-dashboard/app/views/NewTutorial/styles.css index 3cd69990c..ef08fdc77 100644 --- a/manager-dashboard/app/views/NewTutorial/styles.css +++ b/manager-dashboard/app/views/NewTutorial/styles.css @@ -11,7 +11,11 @@ flex-direction: column; width: 100%; max-width: 70rem; - gap: var(--spacing-large); + gap: var(--spacing-extra-large); + + .instruction-card { + display: flex; + } .card { display: flex; @@ -21,12 +25,13 @@ .add-button { width: fit-content; } - + .option-content { display: flex; + gap: var(--spacing-extra-large); .option-tab-panel { - flex-basis: 60%; + flex-grow: 1; } } } @@ -43,17 +48,21 @@ } } - .card-scenarios { + .info-page-card-content { display: flex; flex-direction: column; gap: var(--spacing-large); - .tab-content { + .add-new-section { display: flex; - flex-direction: column; gap: var(--spacing-medium); + align-items: flex-end; } } + + .scenario-content { + gap: var(--spacing-small); + } } .error-message { @@ -136,4 +145,4 @@ 100% { transform: rotate(0deg) translateX(0); } -} \ No newline at end of file +} diff --git a/manager-dashboard/app/views/NewTutorial/utils.ts b/manager-dashboard/app/views/NewTutorial/utils.ts index e541bc9a1..442471886 100644 --- a/manager-dashboard/app/views/NewTutorial/utils.ts +++ b/manager-dashboard/app/views/NewTutorial/utils.ts @@ -21,49 +21,202 @@ import { PROJECT_TYPE_CHANGE_DETECTION, PROJECT_TYPE_COMPLETENESS, PROJECT_TYPE_FOOTPRINT, + IconKey, } from '#utils/common'; +export type ColorKey = 'red' +| 'pink' +| 'purple' +| 'deepPurple' +| 'indigo' +| 'blue' +| 'cyan' +| 'teal' +| 'green' +| 'lime' +| 'yellow' +| 'orange' +| 'brown' +| 'gray'; + +export const colorKeyToColorMap: Record = { + red: '#D32F2F', + pink: '#D81B60', + purple: '#9C27B0', + deepPurple: '#673AB7', + indigo: '#3F51B5', + blue: '#1976D2', + cyan: '#0097A7', + teal: '#00695C', + green: '#2E7D32', + lime: '#9E9D24', + yellow: '#FFD600', + orange: '#FF9100', + brown: '#795548', + gray: '#757575', +}; + export interface ColorOptions { - key: string; + key: ColorKey; label: string; + color: string; } export const iconColorOptions: ColorOptions[] = [ + { + key: 'red', + label: 'Red', + color: colorKeyToColorMap.red, + }, + { + key: 'pink', + label: 'Pink', + color: colorKeyToColorMap.pink, + }, + { + key: 'purple', + label: 'Purple', + color: colorKeyToColorMap.purple, + }, + { + key: 'deepPurple', + label: 'Deep Purple', + color: colorKeyToColorMap.deepPurple, + }, + { + key: 'indigo', + label: 'Indigo', + color: colorKeyToColorMap.indigo, + }, + { + key: 'blue', + label: 'Blue', + color: colorKeyToColorMap.blue, + }, + { + key: 'cyan', + label: 'Cyan', + color: colorKeyToColorMap.cyan, + }, + { + key: 'teal', + label: 'Teal', + color: colorKeyToColorMap.teal, + }, { key: 'green', label: 'Green', + color: colorKeyToColorMap.green, }, { - key: 'red', - label: 'Red', + key: 'lime', + label: 'Lime', + color: colorKeyToColorMap.lime, }, { key: 'yellow', label: 'Yellow', + color: colorKeyToColorMap.yellow, + }, + { + key: 'orange', + label: 'Orange', + color: colorKeyToColorMap.orange, + }, + { + key: 'brown', + label: 'Brown', + color: colorKeyToColorMap.brown, + }, + { + key: 'gray', + label: 'Gray', + color: colorKeyToColorMap.gray, }, ]; -export interface PageTemplateType { - key: '1-picture' | '2-picture' | '3-picture'; +export type InformationPageTemplateKey = '1-picture' | '2-picture' | '3-picture'; +export interface InformationPageOption { + key: InformationPageTemplateKey; label: string; } -export const pageOptions: PageTemplateType[] = [ +export const infoPageTemplateoptions: InformationPageOption[] = [ { key: '1-picture', - label: '1 picture template', + label: 'With 1 picture', }, { key: '2-picture', - label: '2 picture template', + label: 'With 2 picture', }, { key: '3-picture', - label: '3 picture template', + label: 'With 3 picture', }, ]; -// FIXME: include here +interface Block { + blockNumber: number, + blockType: 'text' | 'image', +} + +export const infoPageBlocksMap: Record = { + '1-picture': [ + { + blockNumber: 1, + blockType: 'text', + }, + { + blockNumber: 2, + blockType: 'image', + }, + ], + '2-picture': [ + { + blockNumber: 1, + blockType: 'text', + }, + { + blockNumber: 2, + blockType: 'image', + }, + { + blockNumber: 3, + blockType: 'text', + }, + { + blockNumber: 4, + blockType: 'image', + }, + ], + '3-picture': [ + { + blockNumber: 1, + blockType: 'text', + }, + { + blockNumber: 2, + blockType: 'image', + }, + { + blockNumber: 3, + blockType: 'text', + }, + { + blockNumber: 4, + blockType: 'image', + }, + { + blockNumber: 5, + blockType: 'text', + }, + { + blockNumber: 6, + blockType: 'image', + }, + ], +}; export type TutorialTasks = GeoJSON.FeatureCollection { const customOptionField: CustomOptionFormSchema = { validation: (option) => { - if (option && option.length >= 6) { - return 'Options cannot be more than 6'; + if (!option) { + return undefined; } - if (option && option.length <= 2) { - return 'Options cannot be less than 2'; + + if (option.length < 2) { + return 'There should be at least 2 options'; + } + + if (option.length > 6) { + return 'There shouldn\'t be more than 6 options'; } + return undefined; }, keySelector: (key) => key.optionId, @@ -353,12 +512,18 @@ export const tutorialFormSchema: TutorialFormSchema = { subOptions: { keySelector: (key) => key.subOptionsId, validation: (sub) => { - if (sub && sub?.length >= 6) { - return 'Sub options cannot be more than 6'; + if (!sub || sub.length === 0) { + return undefined; } - if (sub && sub.length <= 2) { - return 'Sub options cannot be less than 2'; + + if (sub.length < 2) { + return 'There should be at least 2 sub options'; } + + if (sub.length > 6) { + return 'There shouldn\'t be more than 6 sub options'; + } + return undefined; }, member: () => ({ From 244d9c15cf9ea8f37251628dc0f0d63994bfdc78 Mon Sep 17 00:00:00 2001 From: shreeyash07 Date: Tue, 20 Jun 2023 17:39:25 +0545 Subject: [PATCH 26/72] fix css --- .../app/components/ScenarioGeoJsonPreview/index.tsx | 1 - manager-dashboard/app/views/NewTutorial/index.tsx | 2 -- manager-dashboard/app/views/NewTutorial/styles.css | 10 ++++++---- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/manager-dashboard/app/components/ScenarioGeoJsonPreview/index.tsx b/manager-dashboard/app/components/ScenarioGeoJsonPreview/index.tsx index 3f0f6ec49..ef0c0f6ee 100644 --- a/manager-dashboard/app/components/ScenarioGeoJsonPreview/index.tsx +++ b/manager-dashboard/app/components/ScenarioGeoJsonPreview/index.tsx @@ -24,7 +24,6 @@ function ScenarioGeoJsonPreview(props: Props) { } = props; const Comp = previewPopUp?.icon ? iconMap[previewPopUp.icon] : undefined; - console.info(geoJson); return (
diff --git a/manager-dashboard/app/views/NewTutorial/index.tsx b/manager-dashboard/app/views/NewTutorial/index.tsx index 448a289c6..a0f57edac 100644 --- a/manager-dashboard/app/views/NewTutorial/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/index.tsx @@ -183,8 +183,6 @@ function NewTutorial(props: Props) { return undefined; } - console.info(activeScenarioTab, typeof activeScenarioTab, geojson); - return { ...geojson, features: geojson.features.filter( diff --git a/manager-dashboard/app/views/NewTutorial/styles.css b/manager-dashboard/app/views/NewTutorial/styles.css index ef08fdc77..8dce17926 100644 --- a/manager-dashboard/app/views/NewTutorial/styles.css +++ b/manager-dashboard/app/views/NewTutorial/styles.css @@ -13,10 +13,6 @@ max-width: 70rem; gap: var(--spacing-extra-large); - .instruction-card { - display: flex; - } - .card { display: flex; flex-direction: column; @@ -62,6 +58,12 @@ .scenario-content { gap: var(--spacing-small); + + .tab-content { + display: flex; + flex-direction: column; + gap: var(--spacing-large); + } } } From 4d5013a9cf8b27b4b54e26114e1ba4e4abab3e24 Mon Sep 17 00:00:00 2001 From: shreeyash07 Date: Wed, 21 Jun 2023 21:58:19 +0545 Subject: [PATCH 27/72] Add titleserver --- docker-compose.yaml | 6 ++ .../app/components/GeoJsonPreview/index.tsx | 8 ++- .../ScenarioGeoJsonPreview/index.tsx | 3 + .../NewTutorial/ScenarioPageInput/index.tsx | 11 +-- .../app/views/NewTutorial/index.tsx | 68 ++++++++++++------- .../app/views/NewTutorial/utils.ts | 23 +++++++ 6 files changed, 88 insertions(+), 31 deletions(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index fa6fd06c7..f3f4fa211 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -138,6 +138,12 @@ services: REACT_APP_SENTRY_DSN: ${MANAGER_DASHBOARD_SENTRY_DSN} REACT_APP_SENTRY_TRACES_SAMPLE_RATE: ${MANAGER_DASHBOARD_SENTRY_TRACES_SAMPLE_RATE} REACT_APP_COMMUNITY_DASHBOARD_URL: ${MANAGER_DASHBOARD_COMMUNITY_DASHBOARD_URL} + REACT_IMAGE_BING_API_KEY: '${IMAGE_BING_API_KEY}' + REACT_IMAGE_ESRI_API_KEY: '${IMAGE_ESRI_API_KEY}' + REACT_IMAGE_ESRI_BETA_API_KEY: '${IMAGE_ESRI_BETA_API_KEY}' + REACT_IMAGE_MAPBOX_API_KEY: '${IMAGE_MAPBOX_API_KEY}' + REACT_IMAGE_MAXAR_PREMIUM_API_KEY: '${IMAGE_MAXAR_PREMIUM_API_KEY}' + REACT_IMAGE_MAXAR_STANDARD_API_KEY: '${IMAGE_MAXAR_STANDARD_API_KEY}' volumes: - manager-dashboard-static:/code/build/ command: bash -c 'yarn build' diff --git a/manager-dashboard/app/components/GeoJsonPreview/index.tsx b/manager-dashboard/app/components/GeoJsonPreview/index.tsx index 59d7a3599..e3014e588 100644 --- a/manager-dashboard/app/components/GeoJsonPreview/index.tsx +++ b/manager-dashboard/app/components/GeoJsonPreview/index.tsx @@ -12,12 +12,14 @@ import styles from './styles.css'; interface Props { className?: string; geoJson: GeoJSON.GeoJSON | undefined; + url: string | undefined; } function GeoJsonPreview(props: Props) { const { className, geoJson, + url, } = props; const mapRef = React.useRef(); @@ -37,10 +39,10 @@ function GeoJsonPreview(props: Props) { ); const layer = tileLayer( - 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', + url || 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap', - subdomains: ['a', 'b', 'c'], + // subdomains: ['a', 'b', 'c'], }, ); @@ -55,7 +57,7 @@ function GeoJsonPreview(props: Props) { } }; }, - [], + [url], ); React.useEffect( diff --git a/manager-dashboard/app/components/ScenarioGeoJsonPreview/index.tsx b/manager-dashboard/app/components/ScenarioGeoJsonPreview/index.tsx index ef0c0f6ee..63e0864a5 100644 --- a/manager-dashboard/app/components/ScenarioGeoJsonPreview/index.tsx +++ b/manager-dashboard/app/components/ScenarioGeoJsonPreview/index.tsx @@ -14,6 +14,7 @@ interface Props { description?: string; icon?: IconKey; } + url: string | undefined; } function ScenarioGeoJsonPreview(props: Props) { @@ -21,6 +22,7 @@ function ScenarioGeoJsonPreview(props: Props) { className, geoJson, previewPopUp, + url, } = props; const Comp = previewPopUp?.icon ? iconMap[previewPopUp.icon] : undefined; @@ -45,6 +47,7 @@ function ScenarioGeoJsonPreview(props: Props) {
); diff --git a/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/index.tsx b/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/index.tsx index f025ab709..e145ba5ee 100644 --- a/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/index.tsx @@ -62,6 +62,7 @@ interface Props { index: number, error: Error | undefined; geoJson: GeoJSON.GeoJSON | undefined; + url: string | undefined; } export default function ScenarioPageInput(props: Props) { @@ -71,6 +72,7 @@ export default function ScenarioPageInput(props: Props) { index, error: riskyError, geoJson, + url, } = props; const [activeSegmentInput, setActiveInput] = React.useState('instruction'); @@ -102,14 +104,14 @@ export default function ScenarioPageInput(props: Props) {
{ + const tileServerName = value.tileServer?.name; + if (isNotDefined(tileServerName)) { + return undefined; + } + if (tileServerName === 'custom') { + return value.tileServer?.url; + } + return tileServerUrls[tileServerName]; + }, [value.tileServer?.name, value.tileServer?.url]); + return (
@@ -502,6 +521,30 @@ function NewTutorial(props: Props) {
+ + + + {tileServerBVisible && ( + + + + )} {value.projectType === PROJECT_TYPE_FOOTPRINT && ( ))} @@ -706,30 +750,6 @@ function NewTutorial(props: Props) { ) } - - - - {tileServerBVisible && ( - - - - )} {hasErrors && (
Please correct all the errors above before submission! diff --git a/manager-dashboard/app/views/NewTutorial/utils.ts b/manager-dashboard/app/views/NewTutorial/utils.ts index 442471886..61d6dffd8 100644 --- a/manager-dashboard/app/views/NewTutorial/utils.ts +++ b/manager-dashboard/app/views/NewTutorial/utils.ts @@ -11,6 +11,7 @@ import { } from '@togglecorp/toggle-form'; import { TileServer, + TileServerType, tileServerFieldsSchema, } from '#components/TileServerInput'; @@ -24,6 +25,11 @@ import { IconKey, } from '#utils/common'; +const BING_KEY = process.env.REACT_IMAGE_BING_API_KEY; +const MAPBOX_KEY = process.env.REACT_IMAGE_MAPBOX_API_KEY; +const MAXAR_PREMIUM = process.env.REACT_IMAGE_MAXAR_PREMIUM_API_KEY; +const MAXAR_STANDARD = process.env.REACT_IMAGE_MAXAR_STANDARD_API_KEY; + export type ColorKey = 'red' | 'pink' | 'purple' @@ -135,6 +141,23 @@ export const iconColorOptions: ColorOptions[] = [ }, ]; +export const tileServerUrls: { + [key in Exclude]: string; +} = { + bing: + `https://ecn.t0.tiles.virtualearth.net/tiles/a{quad_key}.jpeg?g=1&token=${BING_KEY}`, + mapbox: + `https://d.tiles.mapbox.com/v4/mapbox.satellite/{z}/{x}/{y}.jpg?access_token=${MAPBOX_KEY}`, + maxar_premium: + `https://services.digitalglobe.com/earthservice/tmsaccess/tms/1.0.0/DigitalGlobe%3AImageryTileService@EPSG%3A3857@jpg/{z}/{x}/{y}.jpg?connectId=${MAXAR_PREMIUM}`, + maxar_standard: + `https://services.digitalglobe.com/earthservice/tmsaccess/tms/1.0.0/DigitalGlobe%3AImageryTileService@EPSG%3A3857@jpg/{z}/{x}/{y}.jpg?connectId=${MAXAR_STANDARD}`, + esri: + 'https://services.arcgisonline.com/arcgis/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', + esri_beta: + 'https://clarity.maptiles.arcgis.com/arcgis/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', +}; + export type InformationPageTemplateKey = '1-picture' | '2-picture' | '3-picture'; export interface InformationPageOption { key: InformationPageTemplateKey; From 2638bc38d06647a18c11396c188158a943d75a01 Mon Sep 17 00:00:00 2001 From: shreeyash07 Date: Thu, 22 Jun 2023 09:51:29 +0545 Subject: [PATCH 28/72] fix css --- manager-dashboard/app/Base/styles.css | 1 + .../app/components/EmptyMessage/index.tsx | 28 ++++ .../app/components/EmptyMessage/styles.css | 19 +++ .../app/components/GeoJsonFileInput/index.tsx | 10 +- .../app/components/GeoJsonPreview/index.tsx | 4 +- .../ScenarioGeoJsonPreview/index.tsx | 3 + .../NewTutorial/ScenarioPageInput/index.tsx | 5 +- .../NewTutorial/ScenarioPageInput/styles.css | 6 + .../app/views/NewTutorial/index.tsx | 129 ++++++++++-------- .../app/views/NewTutorial/styles.css | 1 - 10 files changed, 144 insertions(+), 62 deletions(-) create mode 100644 manager-dashboard/app/components/EmptyMessage/index.tsx create mode 100644 manager-dashboard/app/components/EmptyMessage/styles.css diff --git a/manager-dashboard/app/Base/styles.css b/manager-dashboard/app/Base/styles.css index f7782f259..0aa35fe8c 100644 --- a/manager-dashboard/app/Base/styles.css +++ b/manager-dashboard/app/Base/styles.css @@ -65,6 +65,7 @@ p { --color-text: rgba(0, 0, 0, 0.8); --color-text-dark: rgba(0, 0, 0, 1); --color-text-light: rgba(0, 0, 0, 0.6); + --color-text-gray: #666666; --color-accent: #4746f6; --color-danger: #e04656; --color-success: #53b391; diff --git a/manager-dashboard/app/components/EmptyMessage/index.tsx b/manager-dashboard/app/components/EmptyMessage/index.tsx new file mode 100644 index 000000000..2c45d633b --- /dev/null +++ b/manager-dashboard/app/components/EmptyMessage/index.tsx @@ -0,0 +1,28 @@ +import React from 'react'; + +import styles from './styles.css'; + +interface Props { + title: string; + description: string; +} + +function EmptyMessage(props: Props) { + const { + title, + description, + } = props; + + return ( +
+
+ {title} +
+
+ {description} +
+
+ ); +} + +export default EmptyMessage; diff --git a/manager-dashboard/app/components/EmptyMessage/styles.css b/manager-dashboard/app/components/EmptyMessage/styles.css new file mode 100644 index 000000000..0b0e979e0 --- /dev/null +++ b/manager-dashboard/app/components/EmptyMessage/styles.css @@ -0,0 +1,19 @@ +.empty-message { + display: flex; + align-items: center; + flex-direction: column; + justify-content: center; + gap: var(--spacing-small); + padding: var(--spacing-extra-large); + color: var(--color-text-gray); + + .empty-message-title { + font-size: var(--font-size-large); + font-weight: var(--font-weight-bold); + } + + .empty-message-description { + font-size: var(--font-size-medium); + font-weight: var(--font-weight-medium); + } +} \ No newline at end of file diff --git a/manager-dashboard/app/components/GeoJsonFileInput/index.tsx b/manager-dashboard/app/components/GeoJsonFileInput/index.tsx index 1c8d8a50d..c5408a984 100644 --- a/manager-dashboard/app/components/GeoJsonFileInput/index.tsx +++ b/manager-dashboard/app/components/GeoJsonFileInput/index.tsx @@ -53,6 +53,7 @@ interface Props extends Omit, 'value' | 'onChange' | 'accep maxFileSize?: number; value: GeoJSON.GeoJSON | undefined | null; onChange: (newValue: GeoJSON.GeoJSON | undefined, name: N) => void; + preview?: boolean; } function GeoJsonFileInput(props: Props) { @@ -63,6 +64,7 @@ function GeoJsonFileInput(props: Props) { maxFileSize = DEFAULT_MAX_FILE_SIZE, onChange, name, + preview = false, ...otherProps } = props; @@ -144,9 +146,11 @@ function GeoJsonFileInput(props: Props) { description={( <> {description} - + {preview && ( + + )} )} onChange={handleChange} diff --git a/manager-dashboard/app/components/GeoJsonPreview/index.tsx b/manager-dashboard/app/components/GeoJsonPreview/index.tsx index e3014e588..393d550e5 100644 --- a/manager-dashboard/app/components/GeoJsonPreview/index.tsx +++ b/manager-dashboard/app/components/GeoJsonPreview/index.tsx @@ -12,7 +12,8 @@ import styles from './styles.css'; interface Props { className?: string; geoJson: GeoJSON.GeoJSON | undefined; - url: string | undefined; + url?: string | undefined; + urlB?: string | undefined; } function GeoJsonPreview(props: Props) { @@ -20,6 +21,7 @@ function GeoJsonPreview(props: Props) { className, geoJson, url, + urlB, } = props; const mapRef = React.useRef(); diff --git a/manager-dashboard/app/components/ScenarioGeoJsonPreview/index.tsx b/manager-dashboard/app/components/ScenarioGeoJsonPreview/index.tsx index 63e0864a5..b46278237 100644 --- a/manager-dashboard/app/components/ScenarioGeoJsonPreview/index.tsx +++ b/manager-dashboard/app/components/ScenarioGeoJsonPreview/index.tsx @@ -15,6 +15,7 @@ interface Props { icon?: IconKey; } url: string | undefined; + urlB: string | undefined; } function ScenarioGeoJsonPreview(props: Props) { @@ -23,6 +24,7 @@ function ScenarioGeoJsonPreview(props: Props) { geoJson, previewPopUp, url, + urlB, } = props; const Comp = previewPopUp?.icon ? iconMap[previewPopUp.icon] : undefined; @@ -48,6 +50,7 @@ function ScenarioGeoJsonPreview(props: Props) { className={styles.mapContainer} geoJson={geoJson} url={url} + urlB={urlB} />
); diff --git a/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/index.tsx b/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/index.tsx index e145ba5ee..1f3bab426 100644 --- a/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/index.tsx @@ -63,6 +63,7 @@ interface Props { error: Error | undefined; geoJson: GeoJSON.GeoJSON | undefined; url: string | undefined; + urlB: string | undefined; } export default function ScenarioPageInput(props: Props) { @@ -73,6 +74,7 @@ export default function ScenarioPageInput(props: Props) { error: riskyError, geoJson, url, + urlB, } = props; const [activeSegmentInput, setActiveInput] = React.useState('instruction'); @@ -192,11 +194,12 @@ export default function ScenarioPageInput(props: Props) { />
-
+
{ + const tileServerName = value.tileServerB?.name; + if (isNotDefined(tileServerName)) { + return undefined; + } + if (tileServerName === 'custom') { + return value.tileServerB?.url; + } + return tileServerUrls[tileServerName]; + }, [value.tileServerB?.name, value.tileServerB?.url]); + return (
@@ -521,30 +527,6 @@ function NewTutorial(props: Props) {
- - - - {tileServerBVisible && ( - - - - )} {value.projectType === PROJECT_TYPE_FOOTPRINT && ( )} {!(value.informationPages?.length) && ( -
- No information pages at the moment -
+ )}
+ + + + + + {tileServerBVisible && ( + + + + + + )} + { + (value?.projectType === PROJECT_TYPE_BUILD_AREA + || value?.projectType === PROJECT_TYPE_CHANGE_DETECTION + || value?.projectType === PROJECT_TYPE_COMPLETENESS) + && ( + + + + + + ) + } ))}
) : ( -
No Scenarios at the moment
+ )} - { - (value?.projectType === PROJECT_TYPE_BUILD_AREA - || value?.projectType === PROJECT_TYPE_CHANGE_DETECTION - || value?.projectType === PROJECT_TYPE_COMPLETENESS) - && ( - - - - ) - } {hasErrors && (
Please correct all the errors above before submission! diff --git a/manager-dashboard/app/views/NewTutorial/styles.css b/manager-dashboard/app/views/NewTutorial/styles.css index 8dce17926..3907a38a4 100644 --- a/manager-dashboard/app/views/NewTutorial/styles.css +++ b/manager-dashboard/app/views/NewTutorial/styles.css @@ -131,7 +131,6 @@ } .instruction-popup { - padding: var(--spacing-small); gap: var(--spacing-medium); } From f0af826637971557cf91bfe496ff9d494ab83245 Mon Sep 17 00:00:00 2001 From: shreeyash07 Date: Thu, 22 Jun 2023 16:50:55 +0545 Subject: [PATCH 29/72] Add footprint preview page --- .../app/components/GeoJsonPreview/index.tsx | 4 +- .../app/components/TileServerInput/index.tsx | 4 +- .../ChangeDetectionGeoJsonPreview/index.tsx | 61 +++++++++++++++++++ .../ChangeDetectionGeoJsonPreview/styles.css | 42 +++++++++++++ .../FootprintGeoJsonPreview/index.tsx | 56 +++++++++++++++++ .../FootprintGeoJsonPreview/styles.css | 51 ++++++++++++++++ .../ScenarioGeoJsonPreview/index.tsx | 7 +-- .../ScenarioGeoJsonPreview/styles.css | 0 .../NewTutorial/ScenarioPageInput/index.tsx | 38 +++++++++--- .../app/views/NewTutorial/index.tsx | 11 ++-- 10 files changed, 253 insertions(+), 21 deletions(-) create mode 100644 manager-dashboard/app/views/NewTutorial/ScenarioPageInput/ChangeDetectionGeoJsonPreview/index.tsx create mode 100644 manager-dashboard/app/views/NewTutorial/ScenarioPageInput/ChangeDetectionGeoJsonPreview/styles.css create mode 100644 manager-dashboard/app/views/NewTutorial/ScenarioPageInput/FootprintGeoJsonPreview/index.tsx create mode 100644 manager-dashboard/app/views/NewTutorial/ScenarioPageInput/FootprintGeoJsonPreview/styles.css rename manager-dashboard/app/{components => views/NewTutorial/ScenarioPageInput}/ScenarioGeoJsonPreview/index.tsx (95%) rename manager-dashboard/app/{components => views/NewTutorial/ScenarioPageInput}/ScenarioGeoJsonPreview/styles.css (100%) diff --git a/manager-dashboard/app/components/GeoJsonPreview/index.tsx b/manager-dashboard/app/components/GeoJsonPreview/index.tsx index 393d550e5..c42c4eec5 100644 --- a/manager-dashboard/app/components/GeoJsonPreview/index.tsx +++ b/manager-dashboard/app/components/GeoJsonPreview/index.tsx @@ -13,7 +13,6 @@ interface Props { className?: string; geoJson: GeoJSON.GeoJSON | undefined; url?: string | undefined; - urlB?: string | undefined; } function GeoJsonPreview(props: Props) { @@ -21,7 +20,6 @@ function GeoJsonPreview(props: Props) { className, geoJson, url, - urlB, } = props; const mapRef = React.useRef(); @@ -49,12 +47,14 @@ function GeoJsonPreview(props: Props) { ); layer.addTo(mapRef.current); + mapRef.current.zoomControl.remove(); mapRef.current.invalidateSize(); } return () => { if (mapRef.current) { mapRef.current.remove(); + mapRef.current.zoomControl.remove(); mapRef.current = undefined; } }; diff --git a/manager-dashboard/app/components/TileServerInput/index.tsx b/manager-dashboard/app/components/TileServerInput/index.tsx index c74b78f9e..89ab50277 100644 --- a/manager-dashboard/app/components/TileServerInput/index.tsx +++ b/manager-dashboard/app/components/TileServerInput/index.tsx @@ -29,7 +29,7 @@ export const TILE_SERVER_BING = 'bing'; const TILE_SERVER_MAPBOX = 'mapbox'; const TILE_SERVER_MAXAR_STANDARD = 'maxar_standard'; const TILE_SERVER_MAXAR_PREMIUM = 'maxar_premium'; -const TILE_SERVER_ESRI = 'esri'; +export const TILE_SERVER_ESRI = 'esri'; const TILE_SERVER_ESRI_BETA = 'esri_beta'; const TILE_SERVER_CUSTOM = 'custom'; @@ -116,7 +116,7 @@ export function tileServerFieldsSchema(value: TileServerInputType | undefined): type TileServerInputValue = TileServerInputType | undefined; const defaultValue: NonNullable = { - name: TILE_SERVER_BING, + name: TILE_SERVER_ESRI, }; interface Props { diff --git a/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/ChangeDetectionGeoJsonPreview/index.tsx b/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/ChangeDetectionGeoJsonPreview/index.tsx new file mode 100644 index 000000000..96e5547a2 --- /dev/null +++ b/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/ChangeDetectionGeoJsonPreview/index.tsx @@ -0,0 +1,61 @@ +import React from 'react'; +import { _cs } from '@togglecorp/fujs'; + +import GeoJsonPreview from '#components/GeoJsonPreview'; +import { iconMap, IconKey } from '#utils/common'; + +import styles from './styles.css'; + +interface Props { + className?: string; + geoJson: GeoJSON.GeoJSON | undefined; + previewPopUp?: { + title?: string; + description?: string; + icon?: IconKey; + } + url: string | undefined; + urlB: string | undefined; +} + +export default function ChangeDetectionGeoJsonPreview(props: Props) { + const { + className, + geoJson, + previewPopUp, + url, + urlB, + } = props; + + const Comp = previewPopUp?.icon ? iconMap[previewPopUp.icon] : undefined; + + return ( +
+ +
+
+
+ {previewPopUp?.title ?? 'Title'} +
+
+ {previewPopUp?.description ?? 'Description'} +
+
+ { Comp && ( +
+ +
+ )} +
+ +
+ ); +} diff --git a/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/ChangeDetectionGeoJsonPreview/styles.css b/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/ChangeDetectionGeoJsonPreview/styles.css new file mode 100644 index 000000000..f07a45450 --- /dev/null +++ b/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/ChangeDetectionGeoJsonPreview/styles.css @@ -0,0 +1,42 @@ +.preview-screen { + display: flex; + align-items: center; + flex-direction: column; + gap: var(--spacing-medium); + flex-shrink: 0; + background-color: var(--color-primary); + width: 100%; + + .map-preview { + padding: var(--spacing-small); + width: 15rem; + height: 15rem; + } + + .map-content { + display: flex; + opacity: 0.95; + border-radius: 10px; + background-color: var(--color-foreground); + padding: var(--spacing-medium); + gap: var(--spacing-large); + width: 90%; + + .pop-up-content { + display: flex; + flex-basis: 80%; + flex-direction: column; + + .pop-up-title { + font-weight: var(--font-weight-bold); + } + + } + + .pop-up-icon { + display: flex; + align-items: center; + font-size: var(--font-size-super-large); + } + } +} \ No newline at end of file diff --git a/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/FootprintGeoJsonPreview/index.tsx b/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/FootprintGeoJsonPreview/index.tsx new file mode 100644 index 000000000..e309bdafb --- /dev/null +++ b/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/FootprintGeoJsonPreview/index.tsx @@ -0,0 +1,56 @@ +import React from 'react'; +import { _cs } from '@togglecorp/fujs'; + +import GeoJsonPreview from '#components/GeoJsonPreview'; +import { IconKey, iconMap } from '#utils/common'; + +import styles from './styles.css'; + +interface Props { + className?: string; + geoJson: GeoJSON.GeoJSON | undefined; + previewPopUp?: { + title?: string; + description?: string; + icon?: IconKey; + } + url: string | undefined; +} +export default function FootprintGeoJsonPreview(props: Props) { + const { + className, + geoJson, + url, + previewPopUp, + } = props; + + const Comp = previewPopUp?.icon ? iconMap[previewPopUp.icon] : undefined; + + return ( +
+
+
+
+ {previewPopUp?.title ?? 'Title'} +
+
+ {previewPopUp?.description ?? 'Description'} +
+
+ { Comp && ( +
+ +
+ )} +
+ +
+ Footer Option +
+
+ ); +} diff --git a/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/FootprintGeoJsonPreview/styles.css b/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/FootprintGeoJsonPreview/styles.css new file mode 100644 index 000000000..2c8c5e3dd --- /dev/null +++ b/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/FootprintGeoJsonPreview/styles.css @@ -0,0 +1,51 @@ +.preview-screen { + display: flex; + position: relative; + align-items: center; + flex-direction: column; + gap: var(--spacing-medium); + flex-shrink: 0; + z-index: 0; + background-color: var(--color-primary); + width: var(--width-mobile-preview); + height: var(--height-mobile-preview); + + .map-preview { + z-index: -1; + padding: var(--spacing-medium); + height: 80%; + } + + .map-content { + display: flex; + position: absolute; + top: 3rem; + opacity: 0.95; + border-radius: 10px; + background-color: var(--color-foreground); + padding: var(--spacing-medium); + gap: var(--spacing-large); + width: 90%; + + .pop-up-content { + display: flex; + flex-basis: 80%; + flex-direction: column; + + .pop-up-title { + font-weight: var(--font-weight-bold); + } + + } + + .pop-up-icon { + display: flex; + align-items: center; + font-size: var(--font-size-super-large); + } + } + + .footer-option { + display: flex; + } +} \ No newline at end of file diff --git a/manager-dashboard/app/components/ScenarioGeoJsonPreview/index.tsx b/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/ScenarioGeoJsonPreview/index.tsx similarity index 95% rename from manager-dashboard/app/components/ScenarioGeoJsonPreview/index.tsx rename to manager-dashboard/app/views/NewTutorial/ScenarioPageInput/ScenarioGeoJsonPreview/index.tsx index b46278237..857f4254b 100644 --- a/manager-dashboard/app/components/ScenarioGeoJsonPreview/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/ScenarioGeoJsonPreview/index.tsx @@ -2,10 +2,10 @@ import React from 'react'; import { _cs } from '@togglecorp/fujs'; import GeoJsonPreview from '#components/GeoJsonPreview'; -import styles from './styles.css'; - import { iconMap, IconKey } from '#utils/common'; +import styles from './styles.css'; + interface Props { className?: string; geoJson: GeoJSON.GeoJSON | undefined; @@ -15,7 +15,6 @@ interface Props { icon?: IconKey; } url: string | undefined; - urlB: string | undefined; } function ScenarioGeoJsonPreview(props: Props) { @@ -24,7 +23,6 @@ function ScenarioGeoJsonPreview(props: Props) { geoJson, previewPopUp, url, - urlB, } = props; const Comp = previewPopUp?.icon ? iconMap[previewPopUp.icon] : undefined; @@ -50,7 +48,6 @@ function ScenarioGeoJsonPreview(props: Props) { className={styles.mapContainer} geoJson={geoJson} url={url} - urlB={urlB} />
); diff --git a/manager-dashboard/app/components/ScenarioGeoJsonPreview/styles.css b/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/ScenarioGeoJsonPreview/styles.css similarity index 100% rename from manager-dashboard/app/components/ScenarioGeoJsonPreview/styles.css rename to manager-dashboard/app/views/NewTutorial/ScenarioPageInput/ScenarioGeoJsonPreview/styles.css diff --git a/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/index.tsx b/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/index.tsx index 1f3bab426..c34fe46a7 100644 --- a/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/index.tsx @@ -12,12 +12,19 @@ import { valueSelector, labelSelector, IconKey, + ProjectType, + PROJECT_TYPE_BUILD_AREA, + PROJECT_TYPE_FOOTPRINT, + PROJECT_TYPE_CHANGE_DETECTION, + PROJECT_TYPE_COMPLETENESS, } from '#utils/common'; import TextInput from '#components/TextInput'; import Heading from '#components/Heading'; import SelectInput from '#components/SelectInput'; -import ScenarioGeoJsonPreview from '#components/ScenarioGeoJsonPreview'; +import ScenarioGeoJsonPreview from './ScenarioGeoJsonPreview'; import SegmentInput from '#components/SegmentInput'; +import FootprintGeoJsonPreview from './FootprintGeoJsonPreview'; +import ChangeDetectionGeoJsonPreview from './ChangeDetectionGeoJsonPreview'; import styles from './styles.css'; @@ -62,6 +69,7 @@ interface Props { index: number, error: Error | undefined; geoJson: GeoJSON.GeoJSON | undefined; + projectType: ProjectType | undefined; url: string | undefined; urlB: string | undefined; } @@ -74,6 +82,7 @@ export default function ScenarioPageInput(props: Props) { error: riskyError, geoJson, url, + projectType, urlB, } = props; @@ -195,12 +204,27 @@ export default function ScenarioPageInput(props: Props) {
- + {projectType === PROJECT_TYPE_CHANGE_DETECTION && ( + + )} + {projectType === (PROJECT_TYPE_BUILD_AREA || PROJECT_TYPE_COMPLETENESS) && ( + + )} + {projectType === PROJECT_TYPE_FOOTPRINT && ( + + )} Date: Thu, 22 Jun 2023 17:53:22 +0545 Subject: [PATCH 30/72] Support quad_key on Leaflet - Fix env names - Add env names on .env.example - Remove unused env names on docker-compose --- docker-compose.yaml | 10 ++-- manager-dashboard/.env.example | 7 +++ .../app/components/GeoJsonPreview/index.tsx | 50 +++++++++++++++++-- .../app/views/NewTutorial/utils.ts | 8 +-- 4 files changed, 62 insertions(+), 13 deletions(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index f3f4fa211..77e574093 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -138,12 +138,10 @@ services: REACT_APP_SENTRY_DSN: ${MANAGER_DASHBOARD_SENTRY_DSN} REACT_APP_SENTRY_TRACES_SAMPLE_RATE: ${MANAGER_DASHBOARD_SENTRY_TRACES_SAMPLE_RATE} REACT_APP_COMMUNITY_DASHBOARD_URL: ${MANAGER_DASHBOARD_COMMUNITY_DASHBOARD_URL} - REACT_IMAGE_BING_API_KEY: '${IMAGE_BING_API_KEY}' - REACT_IMAGE_ESRI_API_KEY: '${IMAGE_ESRI_API_KEY}' - REACT_IMAGE_ESRI_BETA_API_KEY: '${IMAGE_ESRI_BETA_API_KEY}' - REACT_IMAGE_MAPBOX_API_KEY: '${IMAGE_MAPBOX_API_KEY}' - REACT_IMAGE_MAXAR_PREMIUM_API_KEY: '${IMAGE_MAXAR_PREMIUM_API_KEY}' - REACT_IMAGE_MAXAR_STANDARD_API_KEY: '${IMAGE_MAXAR_STANDARD_API_KEY}' + REACT_APP_IMAGE_BING_API_KEY: '${IMAGE_BING_API_KEY}' + REACT_APP_IMAGE_MAPBOX_API_KEY: '${IMAGE_MAPBOX_API_KEY}' + REACT_APP_IMAGE_MAXAR_PREMIUM_API_KEY: '${IMAGE_MAXAR_PREMIUM_API_KEY}' + REACT_APP_IMAGE_MAXAR_STANDARD_API_KEY: '${IMAGE_MAXAR_STANDARD_API_KEY}' volumes: - manager-dashboard-static:/code/build/ command: bash -c 'yarn build' diff --git a/manager-dashboard/.env.example b/manager-dashboard/.env.example index 453a97527..1bb43b081 100644 --- a/manager-dashboard/.env.example +++ b/manager-dashboard/.env.example @@ -10,3 +10,10 @@ REACT_APP_FIREBASE_STORAGE_BUCKET= REACT_APP_FIREBASE_MESSAGING_SENDER_ID= REACT_APP_FIREBASE_APP_ID= REACT_APP_COMMUNITY_DASHBOARD_URL= + +REACT_APP_IMAGE_BING_API_KEY= +REACT_APP_IMAGE_MAPBOX_API_KEY= +REACT_APP_IMAGE_MAXAR_PREMIUM_API_KEY= +REACT_APP_IMAGE_SINERGISE_API_KEY= +# -- NOTE: not used and seems to be discontinued +REACT_APP_IMAGE_MAXAR_STANDARD_API_KEY= diff --git a/manager-dashboard/app/components/GeoJsonPreview/index.tsx b/manager-dashboard/app/components/GeoJsonPreview/index.tsx index c42c4eec5..6c2dfdd7f 100644 --- a/manager-dashboard/app/components/GeoJsonPreview/index.tsx +++ b/manager-dashboard/app/components/GeoJsonPreview/index.tsx @@ -2,13 +2,51 @@ import React from 'react'; import { map as createMap, Map, - tileLayer, geoJSON, + + TileLayer, + Coords, + Util, } from 'leaflet'; import { _cs } from '@togglecorp/fujs'; import styles from './styles.css'; +const toQuadKey = (x: number, y: number, z: number) => { + let index = ''; + for (let i = z; i > 0; i -= 1) { + let b = 0; + // eslint-disable-next-line no-bitwise + const mask = 1 << (i - 1); + // eslint-disable-next-line no-bitwise + if ((x & mask) !== 0) { + b += 1; + } + // eslint-disable-next-line no-bitwise + if ((y & mask) !== 0) { + b += 2; + } + index += b.toString(); + } + return index; +}; + +const BingTileLayer = TileLayer.extend({ + getTileUrl(coords: Coords) { + const quadkey = toQuadKey(coords.x, coords.y, coords.z); + const subdomains = this.options.subdomains; + + // eslint-disable-next-line no-underscore-dangle + const url = this._url + .replace('{subdomain}', subdomains[(coords.x + coords.y) % subdomains.length]) + .replace('{quad_key}', quadkey); + + console.log(url); + return url; + }, + toQuadKey, +}); + interface Props { className?: string; geoJson: GeoJSON.GeoJSON | undefined; @@ -38,8 +76,14 @@ function GeoJsonPreview(props: Props) { 1, ); - const layer = tileLayer( - url || 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', + const finalUrl = url || 'https://tile.openstreetmap.org/{z}/{x}/{y}.png'; + const quadKeyUrl = finalUrl.indexOf('{quad_key}') !== -1; + const Layer = quadKeyUrl + ? BingTileLayer + : TileLayer; + + const layer = new Layer( + finalUrl, { attribution: '© OpenStreetMap', // subdomains: ['a', 'b', 'c'], diff --git a/manager-dashboard/app/views/NewTutorial/utils.ts b/manager-dashboard/app/views/NewTutorial/utils.ts index 61d6dffd8..ff1564209 100644 --- a/manager-dashboard/app/views/NewTutorial/utils.ts +++ b/manager-dashboard/app/views/NewTutorial/utils.ts @@ -25,10 +25,10 @@ import { IconKey, } from '#utils/common'; -const BING_KEY = process.env.REACT_IMAGE_BING_API_KEY; -const MAPBOX_KEY = process.env.REACT_IMAGE_MAPBOX_API_KEY; -const MAXAR_PREMIUM = process.env.REACT_IMAGE_MAXAR_PREMIUM_API_KEY; -const MAXAR_STANDARD = process.env.REACT_IMAGE_MAXAR_STANDARD_API_KEY; +const BING_KEY = process.env.REACT_APP_IMAGE_BING_API_KEY; +const MAPBOX_KEY = process.env.REACT_APP_IMAGE_MAPBOX_API_KEY; +const MAXAR_PREMIUM = process.env.REACT_APP_IMAGE_MAXAR_PREMIUM_API_KEY; +const MAXAR_STANDARD = process.env.REACT_APP_IMAGE_MAXAR_STANDARD_API_KEY; export type ColorKey = 'red' | 'pink' From d0cf9f1f007d619cf85318c6794f293edba24e36 Mon Sep 17 00:00:00 2001 From: frozenhelium Date: Thu, 22 Jun 2023 18:19:52 +0545 Subject: [PATCH 31/72] Add png icons --- .../app/resources/icons/1_Tap_Black.png | Bin 0 -> 2124 bytes .../app/resources/icons/2_Tap_Black.png | Bin 0 -> 2308 bytes .../app/resources/icons/3_Tap_Black.png | Bin 0 -> 2322 bytes .../app/resources/icons/tap_icon.png | Bin 0 -> 1436 bytes .../app/resources/icons/tap_icon_angular.png | Bin 0 -> 1813 bytes .../app/utils/{common.ts => common.tsx} | 55 +++++++++++++++++- 6 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 manager-dashboard/app/resources/icons/1_Tap_Black.png create mode 100644 manager-dashboard/app/resources/icons/2_Tap_Black.png create mode 100644 manager-dashboard/app/resources/icons/3_Tap_Black.png create mode 100644 manager-dashboard/app/resources/icons/tap_icon.png create mode 100644 manager-dashboard/app/resources/icons/tap_icon_angular.png rename manager-dashboard/app/utils/{common.ts => common.tsx} (80%) diff --git a/manager-dashboard/app/resources/icons/1_Tap_Black.png b/manager-dashboard/app/resources/icons/1_Tap_Black.png new file mode 100644 index 0000000000000000000000000000000000000000..471cc49c61bbe4e7fb8fe51d37abf7d56b25148a GIT binary patch literal 2124 zcmV-S2($NzP)2t=5IBB98nNdSRz2Sh?N*%NJwOL+*CJu12!6tp*2QlMNqIy+TFfpXAM zozo#f5+fl*cbf(znjBI-MDFZio_CknvUo$E`3MAVV+t%wzq-^|ye$NlVF0q? zLvgqo=Z{T_OO{HO(oERz%W_4QE(|dc)>+ys>INo}Bya=eIAN!k%0@iBl&|4+?E- zBi+v8iNld=D8nt#uXPQ{E`VU^?|E21uC9*~{Bam+wUC1^R=&jgxlab$cmMtw zpY45$ObD$0vi@6#Hj7%Wi@@qQ#>Ju@ltf*@b72wIrO$50d>=#LCJMQV1!p1GT|k!V zzV;!CK%ri=omX6jbQf?#mb0?#BpmM}a0e?$b`|8eGmAu4o5vlz`|3q}v)aO`7f+)I zbZ%+`Z7(7lkqh7n`#T73>kjlFmq9;*hd@ZC?C0S=ZC4@P!8K^Ie(qyjW@|Y0Qbzwe zxeYY>YiG4w@~b@N^T-9PLSNWn+TpQjuHp^Rp79N}!pICC%KVS`z* zi{HMDA_yV2qwTq~T!PlHgo#jZ0-}$RRHwNVm*_oR?hOvC>UgcZHj?uI`yLd+_zk6`$^f$8LXb?|)XoEZv-=%S4Z?1=6_ z*qC&tyl$b5U}$U7ig|br9|af){B&u>gbyYkd-UY-YEs^Ak!Zn(S$vd4n1#40m%O`< z<7_!_7z#jl0!1WuKMImRl9D!{FE_mzijq{cKu({@RYh7bH?1pxi(OX zYY!<8HhKF4_M*fiZp z!x(G#eT)wS(;y*L>*X-qNW&OuH+5)gz|AHc69weMB>nTpv4>&Y5?zhs_LO62=Bc*a z7&ylL=p(YN7hk`x?i%sKwPH&X4LW+^H_5Doj;ef+Krq z=Asr?i)PG8b*#6IT3jvGl#{kJjv00-RbeLLHj#bKf#9gc&2?g(sl~{pRE3#|+eY@7 zm+(*x7z~AwIvF=z<-P8MacxusS9$+EN+OTA7cgI3Ro=HTMe=rPRe-^QYs=HARRMLF z-@*)NBVVUxJF3yRC=uNh(!8CT&A1qytM)OY|0HuUKF?5ffyo7A9oicqZkDX-0;|`< zFhg972isK@*hBW!7UOuB4}nR{;cM_<2_C8~FvEEJtqLtYj5lx})p%IWg9WkeBs^48 zU^T1Lg{63?mcZ)Dw)I1Kj_L?ZF2H)1pu$lNfvHWyZLCa`!(q3;E=Jp?lZ#;$9QF#V zx&#jugu_08)j8>z4|j{eF@7<-m>nKwFx)lu)H=ubLn$v~b};3AY3pQZnEXos4tqJk z6sZ4h&>*$A2C2n0NG+~GYHO`P(e-B!@RZKddI)UKW5k7_#=%HugCWIyU%aln;EZx6U@I` z%dF>_t3A`W3~-CX=!=j0!R|u?aJeX+#~+MMZ~M$(-$elooIsAhxoX2SZis)aBF71_ z_PNLJEq%B;lkq^c_PnBTuYR7P@{m2;P1@fTg!b9=DK+7 z*H9w{jJqa|H;lplVe)>xkpH?PiXHjPg)i_BGl2Z+X)JAV@MY-1J;Hb;$sg+!mn@Ym zrJu6j6=fjGJ~ZJA+uNzZ4djt1unuu8Q8uA|LqlkA6}~Iz?ae^g3Dnt{Lg1*2Fi{^& zl%1fkg#Ft{7SI-D55A?>hVNsG32VG|4#wHH{c`H{B8Ng;YuUrn)IHQ;x%AlIM!E~2 z$Np1MM!~id)K9AGq6U8)gj$=b^^HrPz%OW9*!pG zMKvs>y8wDTNT|CB`>4Jn$_*qlLXk zw-WY`L|K9FA`TVKM=pcDgquhf*$J*?LlMI?ZmZTc&%gg_v4_7YK5iR7o099Q07J3mSeQgiss&eIsump8nGE?GCj(HQR9Q|hu zmQW2!0=56)t>%w?t7F3-lNo*OeF-n+@u>LQ60SM{3v9jNPtQ87%F0b9|rSa#g2RDGkVX$w6H=< z64m?tD^!JZKC0U-%#|^`Z4XbAaDhs+MM*b+iC{(w{7xxJ!fGp@@4BIO4qiP%jUtMk+QEp+@bmD5Hsp4Ajm-{ocn5KyvzC%HSE^EPG8yyp>bZ9gk|5W-c zx{qa?tB;mjxe8UpRVifE(b?T_odidN)<5W?PI1$$I23U;;d&|}TkyXShL%x>xY5-* z4$?)=E3(L;KUe({8b=jzNyMZ#92xw9B98JR-$C}1z|aD!i0c$Qw`_q9%6|b=HqmgA zXDRMeyjq$X!k}j5SysG;hB8ZWSw`Juq>E<-bsVINUkhzcf8C6IhY8y>RYbEWqu?_K z{w#;uI)@S@4LMS8owrhuM)j+{V4@Sb{ozVW85jN&bR8P}tt_q$wO5VSk^U}k6R5nx zybffyunsl)sDH`*!Vwjh#9~m#M#0~RnIVm^+z^I%k7*~=8%lL{r2R#rg(ji3-J8f3 zErcN+Bd*S>ks8s~ZicwkvW)r;;<002cOx~Ut=)Twr-50Jkh1kU(A-GPXlXZfXmY^K za*hcD@+fJ){Bh!8h+CqiaNLe`42?W>mYaZsxF5~KLjHhAThJ3k>p2JChlaH(iUbaF5#efXl9`kSBZYYMOCbKh)!H3R;7z>DjXAaDQ#gc z;`UK|%z<#wiCgNzDnpB*OKA&p6L*N>F)v}G9WZDLAyrn~>`U7!>l%n5+JPl)e~X$Z zBkpA^7B{5rA!bO~POS|vG~nLKbZTutPV+Z02ZkuusWlzhXe`u->Jrkjom!KzFg;f7 zqTKJTSH?nooT2RklM5)C+G`RH*g_btr4Lm?QUY4`bfY>$<8|@TWUe#%#q1b4b!1By?<5PJD?Gczy9668#6j}}mdB*$p6*t`LHuU6F+Xg~ zA>GyW*1CiEODShDKbW*#+dNqoI)4(tL32684CsDtP$QkV8tKH}pQ4>$P<={aN zupacH$qsrDPrF{kxDZMr!5gc1G1=Yve$#fDdHplL*Y?e}v;8D5Go8LpU%&I~@BM!7 zy^e(qOmIjD1Y0Js+1iI-)dsAN{J0bCUV;T5fh?=I4r_VVy5alKz`_P3IJDP+6`Yc9 zZJpz!S_@bWE?YNld%Wr!h~Us3TZ%d(FI%7#0a^69VIq%8Tw9j2viP!`i(W@YV2cj^ z+d}7`LbXg|>+(6P>o2A4G%YUquS>ExeV_RV1@ag3a^hg^i%j?y=>g z{Vrk?szb;!7Zs7Re-`NiIJ81)cvzc3J0v#oxgufOHSH zy7acJxHhs~0NoxW)O5-|s_)8j3h8(#;VOc|yYH~9mc;z@%g|Eu`21s8J^}OWzV-9% zUG%P~TM)>10heW=6HI3u?;~^v2axS5q(J(vqGz?R1gr8}r<20o{}XulaPYIYPlxV; zg(ZSRyB&I`D=yv2bis7tcFO*)EJqQ#h$Y7Nk;|YTLEe+8BD>MK9B{FMGpu@#KLNby zJlbBo3A}s|c>QSj`t8-h`mdY-8_Vk5k-FlNXl}~qBqaG&61EF{yy(~~DfTz!WWC!gd^;_UwdK6LKXl1J!oNB`^s7_ zM=T=t(^R-==-<#spbQk}QaN@aj3Q#lT~NfJLjb$1Q|8}Y4F!$IaH+$3@ukq$ zKD8qm?^0*Sk98NQE#qamL||QE)g!m?g_3nq%1hR&3QMBuWN1M^ojAr$T7S;%?Njb+d$Er*2feLkMWVuvGNV04S#n__8 z=kCSxJ;qX8n!}P)J@VGH_@N&8+If^@aH-Sy`P`VTD=rMKiAW+{QFzWZbj9^B(gv?V zx7uBu1A6NRg5~3E|67V^QHZjvNmpDSVfvai=T$XbJW8Fl>8*f~7j0BvS`3o0oT;c= z@DQf9Cau_w&f%j3y^G$ua)A6HDo1`Nt+L5jw_Bz|^I;Yr6%pO8Q*zpU0oJPiN}L4b z2CPZhd3Ez@jc!|+1sfEq6}#)YGC`Q6?}%5`myfNPu4$wY=qrmqIk`eP1+i&or1C*}+-#xA8PtedzV^kZJaK{F6A6hd-V+$<>D zoOKO^hh|_a+uxufiio=pKT)&UR2I<>l^8jXz# z;VvO9+NsqU8-rujK8pR{dTDGV#~GR~Fu4HT)ZPebqhw7NSiK&Bb;L$8*lw!84)kYR zjN@QD1ST>2ufai0aL{al1(NNrRcPrT*}yw!#=~+BY7pBF!$C6zR#$b}s3{JbC9pcP zUHeqtMRNou7f^ebpyHw#0#lp%+t`>W$3?vbwlUbY(tqeWYFyM;VAUl!Xdo`?C$Ksu zz3%C58C)bUW)|y*gLO!EO+B^lBKc6tlUP55vR&CcSso_;62L`$S;7ct|8CGAt+)nh s#WhGPu0dLH4bqBhkXBrSwBj1%f4nGPdCwb|0000007*qoM6N<$f+zG~SpWb4 literal 0 HcmV?d00001 diff --git a/manager-dashboard/app/resources/icons/tap_icon.png b/manager-dashboard/app/resources/icons/tap_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..d332bc489e6f00e5fb6a0bd64e41408fec639bef GIT binary patch literal 1436 zcmV;N1!MY&P)Px)R!KxbR7ef2R&Q)mRTMwx^>yt!LN&u=CMFXYjIKi>0VO69A>bbnS-UN$5H)Iy zXb?a6L1TlTbKxUD+@mM7%t0H z77^rB6)+R87%Xd550LJ*#S`f_m8LFt$C~%L?K8Ev9U2T=$mZVFH9XQ4i}aPk=ADQ@ zy#p3}*V2{VUTT8d-fkFG7qgQIAoZT5)N}5!!gd}Jg8noh{0*RX#uLW2La@6pB)JTA z5z%@8Oalhqc8?df^N5nIi+cGAoycst%>v+COSY!Dlt3IUG$2tVS}3`&v;3HxX_BO>hl z73Pq(mD@XFNn;}@+ytoH8=eI8f0~Lnp1!#vfDRUICxB&cBe0j1M#tDT$RvvaaWolk zdMTdDtOT<9*msL%oRn=Mp+shmPeifg zz@*^VZb$2^S+#B1?4L`-=7_SioG-rcgwuDl-nn-E4etEx7#!`s3&&Qdzj>@~jBQ18 z3vtrV%Ofwq|7E)^A`XZ~t{fd)8`pObm9<7*QLSM2%Q&GqfCx?{(|h8H%-4iwgYLuL z&W+Juz1AsfH-!_qL=9_*Z*XNe$Ir(Lip(RVV3(N#g99dBh?jmvL_SRdjs>YF*&6+V z8~f}o*E>#uPx!_1bNGK|V+#H%BC^y}#&AL_n}FX3dp*P*-L28LxHR??!96Vr<9sY( zoMj)r=I~WS1x^^v_^35GuXtNKi?%T6!?zjtRM{3cD+7f3QxnehS z5k0Xa-n9I5B0cPyG-Gp5Z0>ai$PaQ^vr07Y+umr=iBLE1vH<`mJCI*0fJ^cdTP2ds z=4u4bL190TMJG}g&XLl>KfYlGYJrt{kh7yg;gGMLT4w5mMMV6l*=fmFXFKs$UpJ-r zIm7Cvzi9ptOwrW^w1E9+k)q|L7AeQhS8%V5)8L}$BBFgQ4HvkgUFO_bD&KE5_%{Cd zW+F0;bw+d%kr>%Czi{LQ`-cXeoa*1kKY{&S;YZ&Yne9!a!7dq8NFU0lue2)>xe3n0 zhdAU$qv1sZGS`!+I~G06S=PhgW-9|bD?vM>|%bDK<|c83^dXm)TF3 z_$A%SW&eG#l`EF1rVA?k;LIsWBtbzwjeUe8oZcP$vG{>AX(s*3G4gW`JMnIcZXkeu;B^LaaclcUsk48|aHwWUj#BUXIHmy;#c+$(hju#Q4yj9OCwK?2yL#mr9oJ<^U@=3X zYTIaPF9BZ=&&Z&Nr@~)_I|$djjgBnA!CI{WS5<-#b5SjF!;cVRpSKd$GkQBiP4As2 zs;8WQpzmu2R^_yuU@6>Zl(aQ^&Kcggw%vlGOZ69!7Of&0oV>Q5CC|-rsy=t^@qgUg z(7ez<`=?Z^%36$!^|5my;?)c7$AeFwN`w0PVZcLExU7q~COeI{?A>j0@`cKBZDv&5 z!|@Cq>7wV8dR*o$qCn$}KD(|!ViO0yuXJ5DlA9T+7I5E%bli(>kti;cr4=rh% z>^*pMjVLgW$gZUK_t_%-(H+MEI|?}{`L2jE`-|m+gX0ig%&C7GSB7hsYj$)ge|{)Q zG5ZBa%ip?$yy#a|7>v6US;ubtwJrt%4kl_gV>kgh%>T!r2VR z@zW!FoKH2PM<)&bPsmx&$Y@(SAD)gHB^005wAcnc^-`O-QGm_0ukpAXk<6Kg!&YBr zBso)SxJ+7g@S`9TFP5NZotdzKru_Q)*c?!uxMxw&nvsLd>|ZQ4-Osnxujh^X#S8Rn zHY{@j2Os8#@M-so`LWHBBkG_(UqSMqB#K*H|M`Fx3d6A}wux_}tQcw9x>~yL4nB$a zHgj{LYL}+YLwm=c8wt6s*+5q~F`v!G*3>;U4ne+~KWq04tGa|0M;4@byO+M{j^igG z&6i*c$6I_bJM{~gIqtv(V`^BI=n5U=RfOtV`y93R{f^GB6q9+fdZA>%rqLdnzkVX& z=PR#EyW4)ML8ol7T4==F66=)7h8~lR3_>5Z5Y&lr=Q>ow`G;+(1R*n*ZS$#~W&cwA zm=O4Z^{|w)s;^g-Y5)^><6U6&gCiDwW9H0N$&aOidh3on=Yq#^JWUlCfArov%?7i* z_M5(anAqF(ec_OVc88hvvgTB2@!X3acezxX8+`h%E##wxsQxF3>2<4es-Pspi(a%5 zr}M|l*zxkqC}tpPPCQ`0t0z_w>IFtdswuPDCJnEPJFj^d=W7~Dg5rjk6;_`~PKua^{xhj_q#5!g3IH#mm&)^c`w+blXkiPdi3(1Ioe9uh*0@nqnm{)9>$F_;UJ zf1+c~B+9W7R2@OODfkW*El9tKK}M$vq?DEoFoCdY1&Go_!cc_J5o#A6FC8W?-OflK z@MNv-jOdy-9T_q8#8cuR1ZeYorpJ^Wt~=c+?UsJ-5ADdb9GyS=si=5`Gz*2_i zq<4i|G?iyXG--t8=FIn?c>&QYFU^!c8!)X5AE3aAA`??!w$LV%O)fz&vpQp@6Vvd| zDYr&?^@39yZ!Y^Za3lofv$@lqTW~%kxTo1}!Y`_y?H}CN>;tt zCMdtx-cmsmbolwpZz7x9B-r(6RuIbVXgBo6ZZlMueC_<8sv57^&ahTNDD5tf@Nt+y zKNvS3Ezv+dH@EdBbRxp?KlZ`~fG z!Q7*W5qW}wPfwi;>xz!Fa)r|lNH}C|biPBQ5jC`DRhaW(SpDtz9d~KI%l5nXZze=# lf7q(item: { value: T }) { return item.value; } @@ -90,7 +97,12 @@ export type IconKey = 'addOutline' | 'thumbsDownOutline' | 'thumbsUpOutline' | 'triangleOutline' - | 'warningOutline'; + | 'warningOutline' + | 'generalTap' + | 'tap' + | 'oneTap' + | 'twoTap' + | 'threeTap'; export interface IconItem { key: IconKey; @@ -98,6 +110,22 @@ export interface IconItem { component: IconType; } +function getPngIcon(src: string, alt: string) { + const element = () => ( + {alt} + ); + + return element; +} + export const iconList: IconItem[] = [ { key: 'addOutline', @@ -229,6 +257,31 @@ export const iconList: IconItem[] = [ label: 'Warning', component: IoWarningOutline, }, + { + key: 'generalTap', + label: 'General Tap', + component: getPngIcon(tapIcon, 'general tap'), + }, + { + key: 'tap', + label: 'Tap', + component: getPngIcon(angularTapIcon, 'tap'), + }, + { + key: 'oneTap', + label: '1-Tap', + component: getPngIcon(oneTapIcon, 'one tap'), + }, + { + key: 'twoTap', + label: '2-Tap', + component: getPngIcon(twoTapIcon, 'two tap'), + }, + { + key: 'threeTap', + label: '3-Tap', + component: getPngIcon(threeTapIcon, 'three tap'), + }, ]; export const iconMap = listToMap( From 1e57685578af3505343ccac4f3f3bb6d5a9e4d3b Mon Sep 17 00:00:00 2001 From: shreeyash07 Date: Thu, 22 Jun 2023 18:22:28 +0545 Subject: [PATCH 32/72] finalize scenario preview --- .../FootprintGeoJsonPreview/index.tsx | 16 +++++++++++++- .../FootprintGeoJsonPreview/styles.css | 10 +++++++++ .../NewTutorial/ScenarioPageInput/index.tsx | 4 ++++ .../app/views/NewTutorial/index.tsx | 21 ++++++++++++++++++- .../app/views/NewTutorial/utils.ts | 6 ++++++ 5 files changed, 55 insertions(+), 2 deletions(-) diff --git a/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/FootprintGeoJsonPreview/index.tsx b/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/FootprintGeoJsonPreview/index.tsx index e309bdafb..441f426df 100644 --- a/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/FootprintGeoJsonPreview/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/FootprintGeoJsonPreview/index.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { _cs } from '@togglecorp/fujs'; import GeoJsonPreview from '#components/GeoJsonPreview'; +import { CustomOptionPreviewType } from '#views/NewTutorial/utils'; import { IconKey, iconMap } from '#utils/common'; import styles from './styles.css'; @@ -15,6 +16,7 @@ interface Props { icon?: IconKey; } url: string | undefined; + customOptionsPreview?: CustomOptionPreviewType[] | undefined; } export default function FootprintGeoJsonPreview(props: Props) { const { @@ -22,6 +24,7 @@ export default function FootprintGeoJsonPreview(props: Props) { geoJson, url, previewPopUp, + customOptionsPreview, } = props; const Comp = previewPopUp?.icon ? iconMap[previewPopUp.icon] : undefined; @@ -49,7 +52,18 @@ export default function FootprintGeoJsonPreview(props: Props) { geoJson={geoJson} />
- Footer Option + {customOptionsPreview?.map((option) => { + const Icon = iconMap[option.icon]; + return ( +
+ +
+ ); + })}
); diff --git a/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/FootprintGeoJsonPreview/styles.css b/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/FootprintGeoJsonPreview/styles.css index 2c8c5e3dd..628b192aa 100644 --- a/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/FootprintGeoJsonPreview/styles.css +++ b/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/FootprintGeoJsonPreview/styles.css @@ -47,5 +47,15 @@ .footer-option { display: flex; + flex-direction: column; + gap: var(--spacing-large); + + .preview-icon { + display: flex; + align-items: center; + border-radius: 50%; + padding: 3px; + font-size: 30px; + } } } \ No newline at end of file diff --git a/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/index.tsx b/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/index.tsx index c34fe46a7..65f722e4f 100644 --- a/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/ScenarioPageInput/index.tsx @@ -23,6 +23,7 @@ import Heading from '#components/Heading'; import SelectInput from '#components/SelectInput'; import ScenarioGeoJsonPreview from './ScenarioGeoJsonPreview'; import SegmentInput from '#components/SegmentInput'; +import { CustomOptionPreviewType } from '../utils'; import FootprintGeoJsonPreview from './FootprintGeoJsonPreview'; import ChangeDetectionGeoJsonPreview from './ChangeDetectionGeoJsonPreview'; @@ -72,6 +73,7 @@ interface Props { projectType: ProjectType | undefined; url: string | undefined; urlB: string | undefined; + customOptionsPreview?: CustomOptionPreviewType[] | undefined; } export default function ScenarioPageInput(props: Props) { @@ -84,6 +86,7 @@ export default function ScenarioPageInput(props: Props) { url, projectType, urlB, + customOptionsPreview, } = props; const [activeSegmentInput, setActiveInput] = React.useState('instruction'); @@ -223,6 +226,7 @@ export default function ScenarioPageInput(props: Props) { )} { + const customOptions = value?.customOptions; + if (isNotDefined(customOptions)) { + return undefined; + } + const finalValue = customOptions.map((custom) => ( + { + id: randomString(), + icon: custom.icon ?? 'removeOutline', + iconColor: custom.iconColor as ColorKey ?? 'gray', + } + )); + return finalValue; + }, [value.customOptions]); + return (
@@ -598,13 +616,13 @@ function NewTutorial(props: Props) { infoPageTemplate.key} labelSelector={(infoPageTemplate) => infoPageTemplate.label} - className={styles.instructionPopup} /> +
+
+ {isExpanded && ( +
+ {children} +
+ )} +
+ ); +} + +export default ExpandableContainer; diff --git a/manager-dashboard/app/components/ExpandableContainer/styles.css b/manager-dashboard/app/components/ExpandableContainer/styles.css new file mode 100644 index 000000000..96483f361 --- /dev/null +++ b/manager-dashboard/app/components/ExpandableContainer/styles.css @@ -0,0 +1,38 @@ +.expandable-container { + border-radius: var(--radius-card-border); + color: inherit; + + .header-container { + display: flex; + border: var(--width-separator-thin) solid var(--color-separator); + border-top-left-radius: var(--radius-card-border); + border-top-right-radius: var(--radius-card-border); + padding: var(--spacing-medium); + + .header { + flex-grow: 1; + } + + .actions { + flex-shrink: 0; + + .icon { + font-size: 1.2rem; + } + } + } + + .children { + border: var(--width-separator-thin) solid var(--color-separator); + border-bottom-left-radius: var(--radius-card-border); + border-bottom-right-radius: var(--radius-card-border); + padding: var(--spacing-medium); + } + + &:not(.expanded) { + .header-container { + border-bottom-left-radius: var(--radius-card-border); + border-bottom-right-radius: var(--radius-card-border); + } + } +} diff --git a/manager-dashboard/app/components/GeoJsonPreview/index.tsx b/manager-dashboard/app/components/GeoJsonPreview/index.tsx index 752dd8b4a..e9d09b920 100644 --- a/manager-dashboard/app/components/GeoJsonPreview/index.tsx +++ b/manager-dashboard/app/components/GeoJsonPreview/index.tsx @@ -3,7 +3,6 @@ import { map as createMap, Map, geoJSON, - TileLayer, Coords, } from 'leaflet'; @@ -64,7 +63,9 @@ function GeoJsonPreview(props: Props) { React.useEffect( () => { if (mapContainerRef.current && !mapRef.current) { - mapRef.current = createMap(mapContainerRef.current); + mapRef.current = createMap(mapContainerRef.current, { + zoomSnap: 0, + }); } if (mapRef.current) { @@ -122,7 +123,7 @@ function GeoJsonPreview(props: Props) { const bounds = newGeoJson.getBounds(); if (bounds.isValid()) { - map.fitBounds(bounds); + map.fitBounds(bounds, { padding: [0, 0] }); } return () => { diff --git a/manager-dashboard/app/components/InputSection/index.tsx b/manager-dashboard/app/components/InputSection/index.tsx index 3cab44e6e..56f874ed8 100644 --- a/manager-dashboard/app/components/InputSection/index.tsx +++ b/manager-dashboard/app/components/InputSection/index.tsx @@ -4,6 +4,7 @@ import { _cs } from '@togglecorp/fujs'; import styles from './styles.css'; interface Props { + actions?: React.ReactNode; className?: string; heading?: React.ReactNode; children?: React.ReactNode; @@ -16,14 +17,20 @@ function InputSection(props: Props) { heading, children, contentClassName, + actions, } = props; return (
-

+

{heading}

+ {actions && ( +
+ {actions} +
+ )}
{children} diff --git a/manager-dashboard/app/components/InputSection/styles.css b/manager-dashboard/app/components/InputSection/styles.css index ca1ec233f..0c0012c77 100644 --- a/manager-dashboard/app/components/InputSection/styles.css +++ b/manager-dashboard/app/components/InputSection/styles.css @@ -1,16 +1,32 @@ .input-section { display: flex; flex-direction: column; - gap: var(--spacing-medium); + gap: var(--spacing-small); .header { + display: flex; + gap: var(--spacing-medium); + align-items: flex-end; padding: 0; + + .heading { + flex-grow: 1; + } + + .actions { + display: flex; + gap: var(--spacing-small); + align-items: flex-end; + } } .content { display: flex; flex-direction: column; - border-radius: var(--radius-card); + border-radius: var(--radius-card-border); gap: var(--spacing-extra-large); + background-color: var(--color-foreground); + padding: var(--spacing-large); + min-height: 14rem; } } diff --git a/manager-dashboard/app/components/MobilePreview/index.tsx b/manager-dashboard/app/components/MobilePreview/index.tsx new file mode 100644 index 000000000..dc98308cd --- /dev/null +++ b/manager-dashboard/app/components/MobilePreview/index.tsx @@ -0,0 +1,44 @@ +import React from 'react; +import { IoArrowBack } from 'react-icons/io5'; +import { _cs } from '@togglecorp/fujs'; + +import Heading from '#components/Heading'; + +import styles from './styles.css'; + +interface Props { + className?: string; + heading?: React.ReactNode; + actions?: React.ReactNode; + children?: React.ReactNode; +} + +function MobilePreview(props: Props) { + const { + className, + heading, + actions, + children, + } = props; + + return ( +
+
+
+ +
+ + {heading} + + {actions && ( +
+ {actions} +
+ )} +
+
+ {children} +
+
+ ); +} diff --git a/manager-dashboard/app/components/MobilePreview/styles.css b/manager-dashboard/app/components/MobilePreview/styles.css new file mode 100644 index 000000000..b0dfd6e6d --- /dev/null +++ b/manager-dashboard/app/components/MobilePreview/styles.css @@ -0,0 +1,3 @@ +.mobile-preview { + color: inherit; +} diff --git a/manager-dashboard/app/components/PopupButton/index.tsx b/manager-dashboard/app/components/PopupButton/index.tsx index e4c2e9a12..2ed1c13e8 100644 --- a/manager-dashboard/app/components/PopupButton/index.tsx +++ b/manager-dashboard/app/components/PopupButton/index.tsx @@ -18,7 +18,7 @@ export interface PopupButtonProps extends componentRef?: React.MutableRefObject<{ setPopupVisibility: React.Dispatch>; } | null>; - persistent: boolean; + persistent?: boolean; arrowHidden?: boolean; defaultShown?: boolean; } @@ -33,7 +33,7 @@ function PopupButton(props: PopupButtonPr actions, componentRef, arrowHidden, - persistent, + persistent = false, defaultShown, ...otherProps } = props; diff --git a/manager-dashboard/app/views/NewTutorial/CustomOptionInput/SubOption/index.tsx b/manager-dashboard/app/views/NewTutorial/CustomOptionInput/SubOption/index.tsx index 3492ffe49..44c41d833 100644 --- a/manager-dashboard/app/views/NewTutorial/CustomOptionInput/SubOption/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/CustomOptionInput/SubOption/index.tsx @@ -54,7 +54,6 @@ export default function SubOption(props: Props) { diff --git a/manager-dashboard/app/views/NewTutorial/CustomOptionInput/SubOption/styles.css b/manager-dashboard/app/views/NewTutorial/CustomOptionInput/SubOption/styles.css index 4577244ee..2602d2e38 100644 --- a/manager-dashboard/app/views/NewTutorial/CustomOptionInput/SubOption/styles.css +++ b/manager-dashboard/app/views/NewTutorial/CustomOptionInput/SubOption/styles.css @@ -6,8 +6,4 @@ .sub-option-input { flex-grow: 1; } - - .remove-button { - width: fit-content; - } } diff --git a/manager-dashboard/app/views/NewTutorial/CustomOptionInput/index.tsx b/manager-dashboard/app/views/NewTutorial/CustomOptionInput/index.tsx index 0877abf2f..42fba473b 100644 --- a/manager-dashboard/app/views/NewTutorial/CustomOptionInput/index.tsx +++ b/manager-dashboard/app/views/NewTutorial/CustomOptionInput/index.tsx @@ -145,7 +145,7 @@ export default function CustomOptionInput(props: Props) { Remove Option
- + Sub Options + + Create New Tutorial +
- - +
+ -
- - +
+ + } + label="New Information Page" + popupContentClassName={styles.newInfoButtonPopup} + > + {infoPageTemplateoptions.map((infoPageTemplate) => ( + + ))} + + )} + > + +
+ {value.informationPages?.map((page, i) => ( + + + + ))} + {!(value.informationPages?.length) && ( + -
- + )} +
{value.projectType === PROJECT_TYPE_FOOTPRINT && ( - + actions={( - - {value.customOptions?.length ? ( - - - {value.customOptions.map((opt) => ( - - {`Option ${opt.optionId}`} - - ))} - -
- {value.customOptions.map((options, index) => ( - - - - ))} - -
-
- ) : ( -
No sub-options at the moment
- )} -
-
- )} - - -
- infoPageTemplate.key} - labelSelector={(infoPageTemplate) => infoPageTemplate.label} - /> - -
+ )} + > - {value.informationPages && value.informationPages.length > 0 && ( - - - {value.informationPages.map((info) => ( - +
+ {value.customOptions.map((option, index) => ( + - {`Intro ${info.pageNumber}`} - + + ))} - - {value.informationPages?.map((page, i) => ( - - - - ))} - - )} - {!(value.informationPages?.length) && ( - +
+ +
+ ) : ( +
No sub-options at the moment
)} - - + + )} - - - + {tileServerBVisible && ( - - - + )} { @@ -721,17 +681,15 @@ function NewTutorial(props: Props) { - - - + ) } @@ -739,64 +697,46 @@ function NewTutorial(props: Props) { heading="Scenarios" contentClassName={styles.scenarioContent} > - - - - - - {value.scenarioPages?.length ? ( -
- - {value.scenarioPages?.map((task) => ( - - {`Scenario ${task.scenarioId}`} - - ))} - - {value.scenarioPages?.map((task, index) => ( - - - - ))} -
- ) : ( + +
+ + Describe Scenarios + +
+ {value.scenarioPages?.map((task, index) => ( + + + + ))} + {(value.scenarioPages?.length ?? 0) === 0 && ( )} - - +
+
{hasErrors && (
diff --git a/manager-dashboard/app/views/NewTutorial/styles.css b/manager-dashboard/app/views/NewTutorial/styles.css index 3907a38a4..fdae0d25c 100644 --- a/manager-dashboard/app/views/NewTutorial/styles.css +++ b/manager-dashboard/app/views/NewTutorial/styles.css @@ -4,6 +4,7 @@ flex-direction: column; background-color: var(--color-background); padding: var(--spacing-large); + gap: var(--spacing-extra-large); overflow: auto; .container { @@ -13,25 +14,6 @@ max-width: 70rem; gap: var(--spacing-extra-large); - .card { - display: flex; - flex-direction: column; - gap: var(--spacing-large); - - .add-button { - width: fit-content; - } - - .option-content { - display: flex; - gap: var(--spacing-extra-large); - - .option-tab-panel { - flex-grow: 1; - } - } - } - .input-group { display: flex; flex-direction: column; @@ -44,25 +26,21 @@ } } - .info-page-card-content { + .information-page-list { display: flex; flex-direction: column; - gap: var(--spacing-large); - - .add-new-section { - display: flex; - gap: var(--spacing-medium); - align-items: flex-end; - } + gap: var(--spacing-medium); } - .scenario-content { - gap: var(--spacing-small); + .custom-option-container { + display: flex; + gap: var(--spacing-large); - .tab-content { + .custom-option-list { display: flex; flex-direction: column; - gap: var(--spacing-large); + flex-grow: 1; + gap: var(--spacing-medium); } } } @@ -129,11 +107,25 @@ justify-content: flex-end; } } - + .instruction-popup { gap: var(--spacing-medium); } +.new-info-button-popup { + padding: var(--spacing-small) 0; + + .popup-item { + border-radius: unset; + padding: var(--spacing-small) var(--spacing-large); + width: 100%; + + &:hover { + background-color: var(--color-separator); + } + } +} + @keyframes wiggle { 0% { transform: rotate(0deg) translateX(0); From 8d74ec6e3b53597099a413702661caef1c21d691 Mon Sep 17 00:00:00 2001 From: tnagorra Date: Thu, 22 Jun 2023 21:44:31 +0545 Subject: [PATCH 36/72] Do not select project type by default --- .../NewProject/BasicProjectInfoForm/index.tsx | 28 +++--- .../app/views/NewProject/index.tsx | 93 ++++++++++--------- .../app/views/NewTutorial/index.tsx | 55 ++++++----- 3 files changed, 94 insertions(+), 82 deletions(-) diff --git a/manager-dashboard/app/views/NewProject/BasicProjectInfoForm/index.tsx b/manager-dashboard/app/views/NewProject/BasicProjectInfoForm/index.tsx index 4d8ec2977..69fd62e0a 100644 --- a/manager-dashboard/app/views/NewProject/BasicProjectInfoForm/index.tsx +++ b/manager-dashboard/app/views/NewProject/BasicProjectInfoForm/index.tsx @@ -22,7 +22,7 @@ import CustomOptionReadOnly from './CustomOptionReadOnly'; export interface Props { className?: string; - submissionPending: boolean; + disabled: boolean; value: T; setValue: (value: SetBaseValueArg, doNotReset?: boolean) => void; setFieldValue: (...entries: EntriesAsList) => void; @@ -31,7 +31,7 @@ export interface Props { function BasicProjectInfoForm(props: Props) { const { - submissionPending, + disabled, value, setValue, setFieldValue, @@ -99,7 +99,7 @@ function BasicProjectInfoForm(props: Props) { error={error?.projectTopic} label="Project Topic" hint="Enter the topic of your project (50 char max)." - disabled={submissionPending} + disabled={disabled} autoFocus /> ) { label="Project Region" hint="Enter name of your project Region (50 chars max)" error={error?.projectRegion} - disabled={submissionPending} + disabled={disabled} />
@@ -120,7 +120,7 @@ function BasicProjectInfoForm(props: Props) { label="Project Number" hint="Is this project part of a bigger campaign with multiple projects?" error={error?.projectNumber} - disabled={submissionPending} + disabled={disabled} /> ) { error={error?.requestingOrganisation} label="Requesting Organisation" hint="Which group, institution or community is requesting this project?" - disabled={submissionPending || organisationsPending} + disabled={disabled || organisationsPending} keySelector={valueSelector} labelSelector={labelSelector} /> @@ -143,7 +143,7 @@ function BasicProjectInfoForm(props: Props) { readOnly placeholder="[Project Topic] - [Project Region] ([Task Number]) [Requesting Organisation]" // error={error?.name} - disabled={submissionPending} + disabled={disabled} />
) { label="Visibility" hint="Choose either 'public' or select the team for which this project should be displayed" error={error?.visibility} - disabled={submissionPending || teamsPending} + disabled={disabled || teamsPending} /> ) { error={error?.lookFor} label="Look For" hint="What should the users look for (e.g. buildings, cars, trees)? (25 chars max)" - disabled={submissionPending} + disabled={disabled} />