From 9f5d3cb7954a0c89a586819b16c25ebbf6b28a6b Mon Sep 17 00:00:00 2001 From: shmck Date: Sun, 28 Jun 2020 21:43:25 -0700 Subject: [PATCH 1/6] hint state progress Signed-off-by: shmck --- .../Tutorial/{ => components}/ContentMenu.tsx | 2 +- .../containers/Tutorial/components/Hints.tsx | 14 +++-- .../containers/Tutorial/components/Level.tsx | 58 ++++++++++++++----- .../containers/Tutorial/components/Step.tsx | 8 ++- web-app/src/containers/Tutorial/index.tsx | 48 ++++++--------- 5 files changed, 80 insertions(+), 50 deletions(-) rename web-app/src/containers/Tutorial/{ => components}/ContentMenu.tsx (97%) diff --git a/web-app/src/containers/Tutorial/ContentMenu.tsx b/web-app/src/containers/Tutorial/components/ContentMenu.tsx similarity index 97% rename from web-app/src/containers/Tutorial/ContentMenu.tsx rename to web-app/src/containers/Tutorial/components/ContentMenu.tsx index 69e3329e..3399ed60 100644 --- a/web-app/src/containers/Tutorial/ContentMenu.tsx +++ b/web-app/src/containers/Tutorial/components/ContentMenu.tsx @@ -2,7 +2,7 @@ import * as React from 'react' import * as T from 'typings' import * as TT from 'typings/tutorial' import { Menu } from '@alifd/next' -import Icon from '../../components/Icon' +import Icon from '../../../components/Icon' interface Props { tutorial: TT.Tutorial diff --git a/web-app/src/containers/Tutorial/components/Hints.tsx b/web-app/src/containers/Tutorial/components/Hints.tsx index 7e45fe35..9de203ab 100644 --- a/web-app/src/containers/Tutorial/components/Hints.tsx +++ b/web-app/src/containers/Tutorial/components/Hints.tsx @@ -19,22 +19,26 @@ const styles = { interface Props { hints: string[] + hintIndex: number + setHintIndex(value: number): void } const Hints = (props: Props) => { - const [hintIndex, setHintIndex] = React.useState(-1) - const isFinalHint = props.hints.length - 1 === hintIndex + const isFinalHint = props.hints.length - 1 === props.hintIndex const nextHint = () => { - if (!isFinalHint) { - setHintIndex((currentHintIndex) => currentHintIndex + 1) + console.log('hintIndex') + console.log(props.hintIndex) + if (isFinalHint) { + return } + props.setHintIndex(props.hintIndex + 1) } return (
{/* only show revealed hints */} {props.hints.map((h, i) => { - return i <= hintIndex ? ( + return i <= props.hintIndex ? (
{h}
diff --git a/web-app/src/containers/Tutorial/components/Level.tsx b/web-app/src/containers/Tutorial/components/Level.tsx index b28382dd..b47419b7 100644 --- a/web-app/src/containers/Tutorial/components/Level.tsx +++ b/web-app/src/containers/Tutorial/components/Level.tsx @@ -8,6 +8,7 @@ import Button from '../../../components/Button' import Markdown from '../../../components/Markdown' import ProcessMessages from '../../../components/ProcessMessages' import NuxTutorial from '../../../components/NewUserExperience/NuxTutorial' +import ContentMenu from './ContentMenu' import Step from './Step' import { DISPLAY_RUN_TEST_BUTTON } from '../../../environment' @@ -87,42 +88,69 @@ const styles = { } interface Props { - menu: any - steps: Array - title: string + tutorial: TT.Tutorial index: number - content: string status: 'COMPLETE' | 'ACTIVE' | 'INCOMPLETE' + progress: T.Progress + position: T.Position processes: T.ProcessEvent[] testStatus: T.TestStatus | null onContinue(): void onRunTest(): void onLoadSolution(): void onOpenLogs(channel: string): void + displayHintsIndex: number[] + setHintsIndex(index: number, value: number): void } const Level = ({ - menu, - steps, - title, - content, + tutorial, index, status, + progress, + position, onContinue, onRunTest, onLoadSolution, onOpenLogs, processes, testStatus, + displayHintsIndex, + setHintsIndex, }: Props) => { - // @ts-ignore + const level = tutorial.levels[index] + + const [title, setTitle] = React.useState(level.title) + const [content, setContent] = React.useState(level.content) + + const menu = ( + + ) + + const steps: Array = level.steps.map((step: TT.Step) => { + // label step status for step component + let status: T.ProgressStatus = 'INCOMPLETE' + if (progress.steps[step.id]) { + status = 'COMPLETE' + } else if (step.id === position.stepId) { + status = 'ACTIVE' + } + return { ...step, status } + }) + + // current let currentStep = steps.findIndex((s) => s.status === 'ACTIVE') if (currentStep === -1) { currentStep = steps.length } const pageBottomRef = React.useRef(null) - const scrollToBottom = () => { // @ts-ignore pageBottomRef.current.scrollIntoView({ behavior: 'smooth' }) @@ -144,6 +172,7 @@ const Level = ({ {menu}
+

{title}

{content || ''} @@ -153,27 +182,30 @@ const Level = ({
Tasks
- {steps.map((step: (TT.Step & { status: T.ProgressStatus }) | null, index: number) => { + {steps.map((step: (TT.Step & { status: T.ProgressStatus }) | null, stepIndex: number) => { if (!step) { return null } let subtasks = null - if (step.setup.subtasks && testStatus?.summary) { + if (step?.setup?.subtasks && testStatus?.summary) { subtasks = Object.keys(testStatus.summary).map((testName: string) => ({ name: testName, // @ts-ignore typescript is wrong here pass: testStatus.summary[testName], })) } + const hints = step.hints return ( setHintsIndex(stepIndex, value)} /> ) })} diff --git a/web-app/src/containers/Tutorial/components/Step.tsx b/web-app/src/containers/Tutorial/components/Step.tsx index 5065685c..43782cd4 100644 --- a/web-app/src/containers/Tutorial/components/Step.tsx +++ b/web-app/src/containers/Tutorial/components/Step.tsx @@ -6,11 +6,13 @@ import Hints from './Hints' import Markdown from '../../../components/Markdown' interface Props { - order: number + index: number content: string status: T.ProgressStatus subtasks: { name: string; pass: boolean }[] | null hints?: string[] + hintIndex: number + setHintIndex(value: number): void onLoadSolution(): void } @@ -73,7 +75,9 @@ const Step = (props: Props) => { ) : null} {/* hints */} - {props.hints && props.hints.length ? : null} + {props.hints && props.hints.length ? ( + + ) : null}
diff --git a/web-app/src/containers/Tutorial/index.tsx b/web-app/src/containers/Tutorial/index.tsx index 6d6c31c3..ae4012b1 100644 --- a/web-app/src/containers/Tutorial/index.tsx +++ b/web-app/src/containers/Tutorial/index.tsx @@ -2,7 +2,6 @@ import * as React from 'react' import * as T from 'typings' import * as TT from 'typings/tutorial' import * as selectors from '../../services/selectors' -import ContentMenu from './ContentMenu' import Level from './components/Level' interface PageProps { @@ -14,10 +13,6 @@ const TutorialPage = (props: PageProps) => { const { position, progress, processes, testStatus } = props.context const tutorial = selectors.currentTutorial(props.context) - const levelData: TT.Level = selectors.currentLevel(props.context) - - const [title, setTitle] = React.useState(levelData.title) - const [content, setContent] = React.useState(levelData.content) const onContinue = (): void => { props.send({ @@ -40,39 +35,34 @@ const TutorialPage = (props: PageProps) => { props.send({ type: 'OPEN_LOGS', payload: { channel } }) } - const steps = levelData.steps.map((step: TT.Step) => { - // label step status for step component - let status: T.ProgressStatus = 'INCOMPLETE' - if (progress.steps[step.id]) { - status = 'COMPLETE' - } else if (step.id === position.stepId) { - status = 'ACTIVE' - } - return { ...step, status } - }) + const levelIndex = tutorial.levels.findIndex((l: TT.Level) => l.id === position.levelId) + const levelStatus = progress.levels[position.levelId] ? 'COMPLETE' : 'ACTIVE' + const { steps } = tutorial.levels[levelIndex] + const [displayHintsIndex, setDisplayHintsIndex] = React.useState(steps.map((s) => -1)) + + const setHintsIndex = (index: number, value: number) => { + return setDisplayHintsIndex((displayHintsIndex) => { + const next = [...displayHintsIndex] + next[index] = value + return next + }) + } return ( - } - index={tutorial.levels.findIndex((l: TT.Level) => l.id === position.levelId)} - steps={steps} - status={progress.levels[position.levelId] ? 'COMPLETE' : 'ACTIVE'} + tutorial={tutorial} + index={levelIndex} + status={levelStatus} + progress={progress} + position={position} onContinue={onContinue} onRunTest={onRunTest} onLoadSolution={onLoadSolution} onOpenLogs={onOpenLogs} processes={processes} testStatus={testStatus} + displayHintsIndex={displayHintsIndex} + setHintsIndex={setHintsIndex} /> ) } From 83a55b87e0634b89fb161065f6e2e1710f46762d Mon Sep 17 00:00:00 2001 From: shmck Date: Wed, 1 Jul 2020 08:51:50 -0700 Subject: [PATCH 2/6] route change progress Signed-off-by: shmck --- typings/index.d.ts | 2 +- web-app/src/Routes.tsx | 5 +-- web-app/src/components/Router/index.tsx | 2 +- .../containers/Tutorial/components/Hints.tsx | 2 -- .../containers/Tutorial/components/Level.tsx | 23 ++++++++++--- web-app/src/containers/Tutorial/index.tsx | 12 ------- web-app/src/services/state/machine.ts | 32 +++++++++---------- 7 files changed, 40 insertions(+), 38 deletions(-) diff --git a/typings/index.d.ts b/typings/index.d.ts index f7cf5095..3e2cc4b4 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -76,7 +76,6 @@ export interface MachineStateSchema { } Tutorial: { states: { - LoadNext: {} Level: { states: { Load: {} @@ -86,6 +85,7 @@ export interface MachineStateSchema { TestFail: {} StepNext: {} LevelComplete: {} + LoadNext: {} } } Completed: {} diff --git a/web-app/src/Routes.tsx b/web-app/src/Routes.tsx index 26460463..6d503ccb 100644 --- a/web-app/src/Routes.tsx +++ b/web-app/src/Routes.tsx @@ -20,6 +20,7 @@ const Routes = () => { ) } + console.log('RENDER') return ( @@ -37,10 +38,10 @@ const Routes = () => { {/* Tutorial */} - + - + {/* Completed */} diff --git a/web-app/src/components/Router/index.tsx b/web-app/src/components/Router/index.tsx index 8dda5bce..f641fb6b 100644 --- a/web-app/src/components/Router/index.tsx +++ b/web-app/src/components/Router/index.tsx @@ -30,7 +30,7 @@ const useRouter = (): Output => { send(action) } - logger(`STATE: ${JSON.stringify(state.value)}`) + console.log(`STATE: ${JSON.stringify(state.value)}`) // event bus listener React.useEffect(() => { diff --git a/web-app/src/containers/Tutorial/components/Hints.tsx b/web-app/src/containers/Tutorial/components/Hints.tsx index 9de203ab..3d66bc0e 100644 --- a/web-app/src/containers/Tutorial/components/Hints.tsx +++ b/web-app/src/containers/Tutorial/components/Hints.tsx @@ -26,8 +26,6 @@ interface Props { const Hints = (props: Props) => { const isFinalHint = props.hints.length - 1 === props.hintIndex const nextHint = () => { - console.log('hintIndex') - console.log(props.hintIndex) if (isFinalHint) { return } diff --git a/web-app/src/containers/Tutorial/components/Level.tsx b/web-app/src/containers/Tutorial/components/Level.tsx index b47419b7..2f7d2016 100644 --- a/web-app/src/containers/Tutorial/components/Level.tsx +++ b/web-app/src/containers/Tutorial/components/Level.tsx @@ -99,8 +99,6 @@ interface Props { onRunTest(): void onLoadSolution(): void onOpenLogs(channel: string): void - displayHintsIndex: number[] - setHintsIndex(index: number, value: number): void } const Level = ({ @@ -115,14 +113,31 @@ const Level = ({ onOpenLogs, processes, testStatus, - displayHintsIndex, - setHintsIndex, }: Props) => { const level = tutorial.levels[index] const [title, setTitle] = React.useState(level.title) const [content, setContent] = React.useState(level.content) + // hold state for hints for the level + const [displayHintsIndex, setDisplayHintsIndex] = React.useState([]) + const setHintsIndex = (index: number, value: number) => { + return setDisplayHintsIndex((displayHintsIndex) => { + const next = [...displayHintsIndex] + next[index] = value + return next + }) + } + React.useEffect(() => { + console.log(position.levelId) + console.log(JSON.stringify(position)) + // set the hints to empty on level starts + setDisplayHintsIndex(steps.map((s) => -1)) + return () => { + console.log('LEVEL UNMOUNTED') + } + }, [position.levelId]) + const menu = ( { const levelIndex = tutorial.levels.findIndex((l: TT.Level) => l.id === position.levelId) const levelStatus = progress.levels[position.levelId] ? 'COMPLETE' : 'ACTIVE' - const { steps } = tutorial.levels[levelIndex] - const [displayHintsIndex, setDisplayHintsIndex] = React.useState(steps.map((s) => -1)) - - const setHintsIndex = (index: number, value: number) => { - return setDisplayHintsIndex((displayHintsIndex) => { - const next = [...displayHintsIndex] - next[index] = value - return next - }) - } return ( { onOpenLogs={onOpenLogs} processes={processes} testStatus={testStatus} - displayHintsIndex={displayHintsIndex} - setHintsIndex={setHintsIndex} /> ) } diff --git a/web-app/src/services/state/machine.ts b/web-app/src/services/state/machine.ts index 26b4229f..4a7d1059 100644 --- a/web-app/src/services/state/machine.ts +++ b/web-app/src/services/state/machine.ts @@ -142,21 +142,6 @@ export const createMachine = (options: any) => { id: 'tutorial', initial: 'Level', states: { - LoadNext: { - id: 'tutorial-load-next', - onEntry: ['loadNext'], - on: { - NEXT_STEP: { - target: 'Level', - actions: ['updatePosition'], - }, - NEXT_LEVEL: { - target: 'Level', - actions: ['updatePosition'], - }, - COMPLETED: '#completed-tutorial', - }, - }, Level: { initial: 'Load', states: { @@ -228,11 +213,26 @@ export const createMachine = (options: any) => { onExit: ['syncLevelProgress'], on: { NEXT_LEVEL: { - target: '#tutorial-load-next', + target: 'LoadNext', actions: ['testClear', 'updatePosition'], }, }, }, + LoadNext: { + id: 'tutorial-load-next', + onEntry: ['loadNext'], + on: { + NEXT_STEP: { + target: 'Load', + actions: ['updatePosition'], + }, + NEXT_LEVEL: { + target: 'Load', + actions: ['updatePosition'], + }, + COMPLETED: '#completed-tutorial', + }, + }, }, }, Completed: { From 130757890804e22be9da752ca1320b91e6a887ee Mon Sep 17 00:00:00 2001 From: shmck Date: Wed, 1 Jul 2020 15:17:57 -0700 Subject: [PATCH 3/6] setup routes using objects and route strings Signed-off-by: shmck --- web-app/src/App.tsx | 5 +- web-app/src/Routes.tsx | 67 +++++------ web-app/src/components/Router/Route.tsx | 8 -- web-app/src/components/Router/index.tsx | 111 ++++++------------ .../src/services/state/useStateMachine.tsx | 64 ++++++++++ 5 files changed, 135 insertions(+), 120 deletions(-) delete mode 100644 web-app/src/components/Router/Route.tsx create mode 100644 web-app/src/services/state/useStateMachine.tsx diff --git a/web-app/src/App.tsx b/web-app/src/App.tsx index 3c296c87..9f490dec 100644 --- a/web-app/src/App.tsx +++ b/web-app/src/App.tsx @@ -2,12 +2,15 @@ import * as React from 'react' import { ConfigProvider } from '@alifd/next' import enUS from '@alifd/next/lib/locale/en-us' import ErrorBoundary from './components/ErrorBoundary' +import Workspace from './components/Workspace' import Routes from './Routes' const App = () => ( - + + + ) diff --git a/web-app/src/Routes.tsx b/web-app/src/Routes.tsx index 6d503ccb..384f5811 100644 --- a/web-app/src/Routes.tsx +++ b/web-app/src/Routes.tsx @@ -1,6 +1,6 @@ import * as React from 'react' -import useRouter from './components/Router' -import Workspace from './components/Workspace' +import useStateMachine from './services/state/useStateMachine' +import { Router, Route } from './components/Router' import ErrorView from './components/Error' import LoadingPage from './containers/Loading' import StartPage from './containers/Start' @@ -9,47 +9,40 @@ import CompletedPage from './containers/Tutorial/CompletedPage' import TutorialPage from './containers/Tutorial' const Routes = () => { - const { context, send, Router, Route } = useRouter() + const { context, route, send } = useStateMachine() // TODO: handle only full page errors if (context.error) { - return ( - - - - ) + return } - console.log('RENDER') return ( - - - {/* Setup */} - - - - - - - - - - - - - {/* Tutorial */} - - - - - - - {/* Completed */} - - - - - + + {/* Setup */} + + + + + + + + + + + + + {/* Tutorial */} + + + + + + + {/* Completed */} + + + + ) } diff --git a/web-app/src/components/Router/Route.tsx b/web-app/src/components/Router/Route.tsx deleted file mode 100644 index d73001df..00000000 --- a/web-app/src/components/Router/Route.tsx +++ /dev/null @@ -1,8 +0,0 @@ -interface Props { - children: any - path: string | string[] -} - -const Route = ({ children }: Props) => children - -export default Route diff --git a/web-app/src/components/Router/index.tsx b/web-app/src/components/Router/index.tsx index f641fb6b..1999b77c 100644 --- a/web-app/src/components/Router/index.tsx +++ b/web-app/src/components/Router/index.tsx @@ -1,87 +1,50 @@ import * as React from 'react' -import * as T from 'typings' -import { createMachine } from '../../services/state/machine' -import { useMachine } from '../../services/xstate-react' -import Route from './Route' import onError from '../../services/sentry/onError' -import logger from '../../services/logger' -interface Output { - context: T.MachineContext - send: (action: any) => void - Router: any - Route: any +interface RouterProps { + children: React.ReactChildren | React.ReactChildren[] + route: string } -declare let acquireVsCodeApi: any - -const editor = acquireVsCodeApi() -const editorSend = (action: T.Action) => { - logger(`TO EXT: "${action.type}"`) - return editor.postMessage(action) -} - -// router finds first state match of -const useRouter = (): Output => { - const [state, send] = useMachine(createMachine({ editorSend })) - - const sendWithLog = (action: T.Action): void => { - logger(`SEND: ${action.type}`, action) - send(action) +// check if a route string (eg. 'a.b.c') +// matches a paths object ({ a: { b: { c: true }}}) +const matches = (route: string, paths: object): boolean => { + const keys: string[] = route.split('.') + let current: any = paths || {} + // if the key throws, there is no match + for (const key of keys) { + const next = current[key] + if (next) { + current = next + continue + } else { + return false + } } + return true +} - console.log(`STATE: ${JSON.stringify(state.value)}`) - - // event bus listener - React.useEffect(() => { - const listener = 'message' - // propograte channel event to state machine - const handler = (event: any) => { - // NOTE: must call event.data, cannot destructure. VSCode acts odd - const action = event.data - // ignore browser events from plugins - if (action.source) { - return - } - sendWithLog(action) - } - window.addEventListener(listener, handler) - return () => { - window.removeEventListener(listener, handler) - } - }, []) +export const Router = ({ children, route }: RouterProps) => { + // @ts-ignore may accept string as well as element + const childArray: React.ReactElement[] = React.Children.toArray(children) + for (const child of childArray) { + // match path + const { paths } = child.props + let pathMatch = matches(route, paths) - const Router = ({ children }: any) => { - const childArray = React.Children.toArray(children) - for (const child of childArray) { - // match path - // @ts-ignore - const { path } = child.props - let pathMatch - if (typeof path === 'string') { - pathMatch = state.matches(path) - } else if (Array.isArray(path)) { - pathMatch = path.some((p) => state.matches(p)) - } else { - throw new Error(`Invalid route path ${JSON.stringify(path)}`) - } - if (pathMatch) { - // @ts-ignore - return child.props.children - } + if (pathMatch) { + return child.props.children } - const message = `No Route matches for ${JSON.stringify(state)}` - onError(new Error(message)) - console.warn(message) - return null } + const message = `No Route matches for "${JSON.stringify(route)}"` + onError(new Error(message)) + console.warn(message) + return null +} - return { - context: state.context, - send: sendWithLog, - Router, - Route, - } +interface RouteProps { + children: any + paths: object } -export default useRouter +export const Route = ({ children }: RouteProps) => children diff --git a/web-app/src/services/state/useStateMachine.tsx b/web-app/src/services/state/useStateMachine.tsx new file mode 100644 index 00000000..f71f5bf7 --- /dev/null +++ b/web-app/src/services/state/useStateMachine.tsx @@ -0,0 +1,64 @@ +import * as React from 'react' +import * as T from 'typings' +import { State } from 'xstate' +import { createMachine } from './machine' +import { useMachine } from '../xstate-react' +import logger from '../logger' + +interface Output { + context: T.MachineContext + route: string + send: (action: any) => void +} + +declare let acquireVsCodeApi: any + +const createRouteString = (routeObject: object) => { + let paths = [] + const key = Object.keys(routeObject)[0] +} + +const editor = acquireVsCodeApi() +const editorSend = (action: T.Action) => { + logger(`TO EXT: "${action.type}"`) + return editor.postMessage(action) +} + +// router finds first state match of +const useStateMachine = (): Output => { + const [state, send] = useMachine(createMachine({ editorSend })) + + const sendWithLog = (action: T.Action): void => { + logger(`SEND: ${action.type}`, action) + send(action) + } + + console.log(`STATE: ${JSON.stringify(state.value)}`) + + // event bus listener + React.useEffect(() => { + const listener = 'message' + // propograte channel event to state machine + const handler = (event: any) => { + // NOTE: must call event.data, cannot destructure. VSCode acts odd + const action = event.data + // ignore browser events from other extensions + if (action.source) { + return + } + sendWithLog(action) + } + window.addEventListener(listener, handler) + return () => { + window.removeEventListener(listener, handler) + } + }, []) + + return { + context: state.context, + route: '', + send: sendWithLog, + } +} + +export default useStateMachine From 4de3c42588e6000ce82c39ce1a45784da0c5331f Mon Sep 17 00:00:00 2001 From: shmck Date: Wed, 1 Jul 2020 15:36:46 -0700 Subject: [PATCH 4/6] router progress Signed-off-by: shmck --- .../src/services/state/routeString.test.ts | 16 +++++++++ .../src/services/state/useStateMachine.tsx | 33 +++++++++++++++---- 2 files changed, 42 insertions(+), 7 deletions(-) create mode 100644 web-app/src/services/state/routeString.test.ts diff --git a/web-app/src/services/state/routeString.test.ts b/web-app/src/services/state/routeString.test.ts new file mode 100644 index 00000000..e733d41f --- /dev/null +++ b/web-app/src/services/state/routeString.test.ts @@ -0,0 +1,16 @@ +import { createRouteString } from './useStateMachine' + +describe('route string', () => { + it('should take a single key route', () => { + const result = createRouteString('a') + expect(result).toBe('a') + }) + it('should take a 1 level nested key route', () => { + const result = createRouteString({ a: 'b' }) + expect(result).toBe('a.b') + }) + it('should take a 3 level nested key route', () => { + const result = createRouteString({ a: { b: { c: 'd' } } }) + expect(result).toBe('a.b.c.d') + }) +}) diff --git a/web-app/src/services/state/useStateMachine.tsx b/web-app/src/services/state/useStateMachine.tsx index f71f5bf7..f8217606 100644 --- a/web-app/src/services/state/useStateMachine.tsx +++ b/web-app/src/services/state/useStateMachine.tsx @@ -1,6 +1,5 @@ import * as React from 'react' import * as T from 'typings' -import { State } from 'xstate' import { createMachine } from './machine' import { useMachine } from '../xstate-react' import logger from '../logger' @@ -13,9 +12,27 @@ interface Output { declare let acquireVsCodeApi: any -const createRouteString = (routeObject: object) => { - let paths = [] - const key = Object.keys(routeObject)[0] +export const createRouteString = (route: object | string): string => { + if (typeof route === 'string') { + return route + } + const paths: string[] = [] + let current: object | string | undefined = route + while (current) { + // current is final string value + if (typeof current === 'string') { + paths.push(current) + break + } + + // current is object + const next: string = Object.keys(current)[0] + paths.push(next) + // @ts-ignore + current = current[next] + } + + return paths.join('.') } const editor = acquireVsCodeApi() @@ -33,8 +50,6 @@ const useStateMachine = (): Output => { send(action) } - console.log(`STATE: ${JSON.stringify(state.value)}`) - // event bus listener React.useEffect(() => { const listener = 'message' @@ -54,9 +69,13 @@ const useStateMachine = (): Output => { } }, []) + // convert route to a string to avoid unnecessary React re-renders on deeply nested objects + const route = createRouteString(state.value) + console.log(`STATE: ${route}`) + return { context: state.context, - route: '', + route, send: sendWithLog, } } From ce08fa6d7f99a79b79b1a75234769579303854df Mon Sep 17 00:00:00 2001 From: shmck Date: Wed, 1 Jul 2020 15:43:51 -0700 Subject: [PATCH 5/6] fix router edge case Signed-off-by: shmck --- web-app/src/Routes.tsx | 6 +++++- web-app/src/components/Router/index.tsx | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/web-app/src/Routes.tsx b/web-app/src/Routes.tsx index 384f5811..2753a653 100644 --- a/web-app/src/Routes.tsx +++ b/web-app/src/Routes.tsx @@ -8,6 +8,10 @@ import SelectTutorialPage from './containers/SelectTutorial' import CompletedPage from './containers/Tutorial/CompletedPage' import TutorialPage from './containers/Tutorial' +/* + * NOTE: due to a lack of URLs and a dependency on xstate + * we have to implement a custom router here + */ const Routes = () => { const { context, route, send } = useStateMachine() @@ -35,7 +39,7 @@ const Routes = () => { - + {/* Completed */} diff --git a/web-app/src/components/Router/index.tsx b/web-app/src/components/Router/index.tsx index 1999b77c..cf81e174 100644 --- a/web-app/src/components/Router/index.tsx +++ b/web-app/src/components/Router/index.tsx @@ -15,6 +15,10 @@ const matches = (route: string, paths: object): boolean => { for (const key of keys) { const next = current[key] if (next) { + // exit early if property value is true + if (next === true) { + return true + } current = next continue } else { From 7dc54c110362ec1fba860c02718ffe556e8de93d Mon Sep 17 00:00:00 2001 From: shmck Date: Wed, 1 Jul 2020 15:45:30 -0700 Subject: [PATCH 6/6] remove unnecessary debug logs Signed-off-by: shmck --- web-app/src/containers/Tutorial/components/Level.tsx | 5 ----- web-app/src/services/sentry/init.tsx | 3 ++- web-app/src/services/state/useStateMachine.tsx | 2 +- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/web-app/src/containers/Tutorial/components/Level.tsx b/web-app/src/containers/Tutorial/components/Level.tsx index 2f7d2016..98a4e1a2 100644 --- a/web-app/src/containers/Tutorial/components/Level.tsx +++ b/web-app/src/containers/Tutorial/components/Level.tsx @@ -129,13 +129,8 @@ const Level = ({ }) } React.useEffect(() => { - console.log(position.levelId) - console.log(JSON.stringify(position)) // set the hints to empty on level starts setDisplayHintsIndex(steps.map((s) => -1)) - return () => { - console.log('LEVEL UNMOUNTED') - } }, [position.levelId]) const menu = ( diff --git a/web-app/src/services/sentry/init.tsx b/web-app/src/services/sentry/init.tsx index 8f71c272..7100a1a6 100644 --- a/web-app/src/services/sentry/init.tsx +++ b/web-app/src/services/sentry/init.tsx @@ -1,5 +1,6 @@ import * as sentry from '@sentry/browser' import { NODE_ENV, SENTRY_DSN } from '../../environment' +import logger from '../logger' try { if (SENTRY_DSN && NODE_ENV === 'production') { @@ -9,5 +10,5 @@ try { }) } } catch (error) { - console.log(`Error in Sentry init: ${error.message}`) + logger(`Error in Sentry init: ${error.message}`) } diff --git a/web-app/src/services/state/useStateMachine.tsx b/web-app/src/services/state/useStateMachine.tsx index f8217606..5b9ceda1 100644 --- a/web-app/src/services/state/useStateMachine.tsx +++ b/web-app/src/services/state/useStateMachine.tsx @@ -71,7 +71,7 @@ const useStateMachine = (): Output => { // convert route to a string to avoid unnecessary React re-renders on deeply nested objects const route = createRouteString(state.value) - console.log(`STATE: ${route}`) + logger(`STATE: "${route}"`) return { context: state.context,