diff --git a/src/channel/index.ts b/src/channel/index.ts index 00e9f202..29649369 100644 --- a/src/channel/index.ts +++ b/src/channel/index.ts @@ -8,14 +8,15 @@ import saveCommit from '../actions/saveCommit' import { setupActions, solutionActions } from '../actions/setupActions' import tutorialConfig from '../actions/tutorialConfig' import { COMMANDS } from '../editor/commands' -import logger from '../services/logger' import Context from './context' -import { version, compareVersions } from '../services/dependencies' -import { openWorkspace, checkWorkspaceEmpty } from '../services/workspace' import { readFile } from 'fs' import { join } from 'path' import { promisify } from 'util' +import logger from '../services/logger' +import { version, compareVersions } from '../services/dependencies' +import { openWorkspace, checkWorkspaceEmpty } from '../services/workspace' import { showOutput } from '../services/testRunner/output' +import { exec } from '../services/node' import { WORKSPACE_ROOT, TUTORIAL_URL } from '../environment' const readFileAsync = promisify(readFile) @@ -319,6 +320,17 @@ class Channel implements Channel { case 'EDITOR_RUN_TEST': vscode.commands.executeCommand(COMMANDS.RUN_TEST, action?.payload) return + case 'EDITOR_RUN_RESET_SCRIPT': + const tutorial: TT.Tutorial | null = this.context.tutorial.get() + // if tutorial.config.reset.command, run it + if (tutorial?.config?.reset?.command) { + await exec({ command: tutorial.config.reset.command }) + } + return + case 'EDITOR_RUN_RESET_TO_LAST_PASS': + return + case 'EDITOR_RUN_RESET_TO_TIMELINE': + return default: logger(`No match for action type: ${actionType}`) return diff --git a/src/services/git/lastPass.ts b/src/services/git/lastPass.ts new file mode 100644 index 00000000..8a2324cf --- /dev/null +++ b/src/services/git/lastPass.ts @@ -0,0 +1,12 @@ +import * as TT from '../../../typings/tutorial' +import * as T from '../../../typings' + +const getLastPassCommitHash = (position: T.Position, levels: TT.Level[]) => { + // get previous position + const { levelId, stepId } = position + + // get solution hash if it exists + // else get setup hash +} + +export default getLastPassCommitHash diff --git a/typings/tutorial.d.ts b/typings/tutorial.d.ts index 8dc5d6b3..6dac2b9d 100644 --- a/typings/tutorial.d.ts +++ b/typings/tutorial.d.ts @@ -2,11 +2,16 @@ import { ProgressStatus } from './index' export type Maybe = T | null +export type ConfigReset = { + command?: string +} + export type TutorialConfig = { - appVersions: TutorialAppVersions + appVersions?: TutorialAppVersions testRunner: TestRunnerConfig repo: TutorialRepo dependencies?: TutorialDependency[] + reset?: ConfigReset } /** Logical groupings of tasks */ diff --git a/web-app/package.json b/web-app/package.json index ee9be258..afb5e63e 100644 --- a/web-app/package.json +++ b/web-app/package.json @@ -39,6 +39,7 @@ "react-addons-css-transition-group": "^15.6.2", "react-dom": "^16.13.1", "reselect": "^4.0.0", + "use-media": "^1.4.0", "xstate": "^4.11.0" }, "devDependencies": { diff --git a/web-app/src/containers/Tutorial/components/Reset.tsx b/web-app/src/containers/Tutorial/components/Reset.tsx new file mode 100644 index 00000000..1bf34dfc --- /dev/null +++ b/web-app/src/containers/Tutorial/components/Reset.tsx @@ -0,0 +1,57 @@ +import * as React from 'react' +import { Dialog } from '@alifd/next' +import Button from '../../../components/Button' +import Markdown from '../../../components/Markdown' + +interface Props { + disabled: boolean + onReset(): void +} + +const Reset = (props: Props) => { + const [modalState, setModalState] = React.useState<'none' | 'confirm' | 'progress'>('none') + + const onClose = () => { + setModalState('none') + } + + const onOk = () => { + setModalState('progress') + props.onReset() + return setTimeout(() => { + setModalState('none') + }, 3000) + } + + return ( + <> + + + + {`Are you sure you want to reset your progress? +Resetting progress will remove the commits you have made and replace them with the tutorial commit timeline. Your code may look different after resetting.`} + + + + Reverting progress to an earlier commit... + + + ) +} + +export default Reset diff --git a/web-app/src/containers/Tutorial/components/StepProgress.tsx b/web-app/src/containers/Tutorial/components/StepProgress.tsx new file mode 100644 index 00000000..5cb67012 --- /dev/null +++ b/web-app/src/containers/Tutorial/components/StepProgress.tsx @@ -0,0 +1,48 @@ +import * as React from 'react' +import { Progress } from '@alifd/next' +import useMedia from 'use-media' + +const styles = { + progress: { + display: 'flex' as 'flex', + justifyContent: 'flex-end' as 'flex-end', + alignItems: 'center' as 'center', + width: '10rem', + color: 'white', + }, + text: { color: 'white' }, +} + +interface Props { + current: number + max: number +} + +const StepProgress = (props: Props) => { + const Text = ( + + {props.current} of {props.max} + + ) + + const isWide = useMedia({ minWidth: '340px' }) + + if (isWide) { + return ( + { + return Text + }} + /> + ) + } + return
{Text}
+} + +export default StepProgress diff --git a/web-app/src/containers/Tutorial/index.tsx b/web-app/src/containers/Tutorial/index.tsx index baf1bfe7..cb2d7f10 100644 --- a/web-app/src/containers/Tutorial/index.tsx +++ b/web-app/src/containers/Tutorial/index.tsx @@ -4,14 +4,15 @@ import * as selectors from '../../services/selectors' import SideMenu from './components/SideMenu' import Level from './components/Level' import Icon from '../../components/Icon' -import SettingsPage from './containers/Settings' import ReviewPage from './containers/Review' import Button from '../../components/Button' import ProcessMessages from '../../components/ProcessMessages' import TestMessage from '../../components/TestMessage' -import { Progress } from '@alifd/next' +import StepProgress from './components/StepProgress' import { DISPLAY_RUN_TEST_BUTTON } from '../../environment' import formatLevels from './formatLevels' +// import SettingsPage from './containers/Settings' +// import Reset from './components/Reset' const styles = { header: { @@ -47,13 +48,6 @@ const styles = { right: 0, color: 'white', }, - taskProgress: { - display: 'flex' as 'flex', - justifyContent: 'flex-end' as 'flex-end', - alignItems: 'center' as 'center', - width: '10rem', - color: 'white', - }, processes: { padding: '0 1rem', position: 'fixed' as 'fixed', @@ -100,6 +94,10 @@ const TutorialPage = (props: PageProps) => { props.send({ type: 'RUN_TEST' }) } + const onReset = (): void => { + // TODO + } + const [menuVisible, setMenuVisible] = React.useState(false) const [page, setPage] = React.useState<'level' | 'settings' | 'review'>('level') @@ -140,39 +138,27 @@ const TutorialPage = (props: PageProps) => { )} {/* Left */} - {DISPLAY_RUN_TEST_BUTTON && level.status !== 'COMPLETE' ? ( - - ) : ( -
- )} +
+ {DISPLAY_RUN_TEST_BUTTON && level.status !== 'COMPLETE' ? ( + + ) : null} +
{/* Center */} -
+
+ {/* 0} /> */} +
{/* Right */} -
+
{level.status === 'COMPLETE' || !level.steps.length ? ( ) : ( - { - return ( - - {stepIndex} of {level.steps.length} - - ) - }} - /> + )}
diff --git a/web-app/src/services/state/actions/editor.ts b/web-app/src/services/state/actions/editor.ts index da0c09e3..befbac26 100644 --- a/web-app/src/services/state/actions/editor.ts +++ b/web-app/src/services/state/actions/editor.ts @@ -117,4 +117,9 @@ export default (editorSend: any) => ({ payload: { position: context.position }, }) }, + runResetScript() { + editorSend({ + type: 'EDITOR_RUN_RESET_SCRIPT', + }) + }, }) diff --git a/web-app/src/services/state/machine.ts b/web-app/src/services/state/machine.ts index d04c7918..453e977a 100644 --- a/web-app/src/services/state/machine.ts +++ b/web-app/src/services/state/machine.ts @@ -168,6 +168,9 @@ export const createMachine = (options: any) => { RUN_TEST: { actions: ['runTest'], }, + RESET_SCRIPT: { + actions: ['runResetScript'], + }, }, }, TestRunning: { diff --git a/web-app/stories/Review.stories.tsx b/web-app/stories/Review.stories.tsx index b2d77502..51660ec0 100644 --- a/web-app/stories/Review.stories.tsx +++ b/web-app/stories/Review.stories.tsx @@ -142,16 +142,5 @@ storiesOf('Review', module) .addDecorator(SideBarDecorator) .addDecorator(withKnobs) .add('Example', () => { - const progress = { - levels: { - '1': true, - }, - steps: { - '1.1': true, - '1.2': true, - '1.3': true, - '2.1': true, - }, - } - return + return }) diff --git a/web-app/stories/Tests.stories.tsx b/web-app/stories/Tests.stories.tsx index bdba73d5..220bbb43 100644 --- a/web-app/stories/Tests.stories.tsx +++ b/web-app/stories/Tests.stories.tsx @@ -5,4 +5,4 @@ import SideBarDecorator from './utils/SideBarDecorator' storiesOf('Test Message', module) .addDecorator(SideBarDecorator) - .add('Fail', () => ) + .add('Fail', () => ) diff --git a/web-app/stories/Tutorial.stories.tsx b/web-app/stories/Tutorial.stories.tsx index ff5be851..4854a57e 100644 --- a/web-app/stories/Tutorial.stories.tsx +++ b/web-app/stories/Tutorial.stories.tsx @@ -56,7 +56,6 @@ const context: Partial = { solution: { commits: ['hijklmn'], }, - status: 'COMPLETE', hints: ['First Hint', 'Second Hint'], }, { @@ -68,7 +67,6 @@ const context: Partial = { solution: { commits: ['hijklmn'], }, - status: 'COMPLETE', }, { id: '1.3', @@ -79,7 +77,6 @@ const context: Partial = { solution: { commits: ['hijklmn'], }, - status: 'COMPLETE', }, ], }, @@ -100,7 +97,6 @@ const context: Partial = { solution: { commits: ['hijklmn'], }, - status: 'COMPLETE', hints: ['first hint', 'second hint'], }, { @@ -112,7 +108,6 @@ const context: Partial = { solution: { commits: ['hijklmn'], }, - status: 'ACTIVE', hints: ['another hint', 'another other hint'], }, { @@ -124,7 +119,6 @@ const context: Partial = { solution: { commits: ['hijklmn'], }, - status: 'INCOMPLETE', }, ], }, @@ -134,7 +128,6 @@ const context: Partial = { summary: 'A summary of the 3rd level', content: 'Should support markdown test\n ```js\nvar a = 1\n```\nwhew it works!', setup: null, - status: 'INCOMPLETE', steps: [ { id: '3.1', @@ -145,7 +138,6 @@ const context: Partial = { solution: { commits: ['hijklmn'], }, - status: 'INCOMPLETE', }, { id: '3.2', @@ -156,7 +148,6 @@ const context: Partial = { solution: { commits: ['hijklmn'], }, - status: 'INCOMPLETE', }, { id: '3.3', @@ -167,7 +158,6 @@ const context: Partial = { solution: { commits: ['hijklmn'], }, - status: 'INCOMPLETE', }, ], }, diff --git a/web-app/stories/utils/ApolloDecorator.tsx b/web-app/stories/utils/ApolloDecorator.tsx deleted file mode 100644 index 90ab62f7..00000000 --- a/web-app/stories/utils/ApolloDecorator.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { ApolloProvider } from '@apollo/react-hooks' -import React, { Fragment } from 'react' -import client from '../../src/services/apollo' - -function StorybookProvider({ children }) { - return ( - - {children} - - ) -} - -export default (story) => { - return {story()} -} diff --git a/web-app/stories/utils/ProviderDecorator.tsx b/web-app/stories/utils/ProviderDecorator.tsx new file mode 100644 index 00000000..f6a2ca2a --- /dev/null +++ b/web-app/stories/utils/ProviderDecorator.tsx @@ -0,0 +1,15 @@ +import React, { Fragment } from 'react' +import { ConfigProvider } from '@alifd/next' +import enUS from '@alifd/next/lib/locale/en-us' + +export function Provider({ children }) { + return ( + + {children} + + ) +} + +export default (story) => { + return {story()} +} diff --git a/web-app/stories/utils/SideBarDecorator.tsx b/web-app/stories/utils/SideBarDecorator.tsx index 7001797d..318a6494 100644 --- a/web-app/stories/utils/SideBarDecorator.tsx +++ b/web-app/stories/utils/SideBarDecorator.tsx @@ -1,5 +1,6 @@ import * as React from 'react' import { css, jsx } from '@emotion/core' +import { Provider } from './ProviderDecorator' const styles = { container: { @@ -14,6 +15,10 @@ const styles = { }, } -const SideBarDecorator = (storyFn) =>
{storyFn()}
+const SideBarDecorator = (storyFn) => ( + +
{storyFn()}
+
+) export default SideBarDecorator diff --git a/web-app/yarn.lock b/web-app/yarn.lock index 10f4dbb1..4578b7e2 100644 --- a/web-app/yarn.lock +++ b/web-app/yarn.lock @@ -13431,6 +13431,11 @@ use-callback-ref@^1.2.1: resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.2.3.tgz#9f939dfb5740807bbf9dd79cdd4e99d27e827756" integrity sha512-DPBPh1i2adCZoIArRlTuKRy7yue7QogtEnfv0AKrWsY+GA+4EKe37zhRDouNnyWMoNQFYZZRF+2dLHsWE4YvJA== +use-media@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/use-media/-/use-media-1.4.0.tgz#e777bf1f382a7aacabbd1f9ce3da2b62e58b2a98" + integrity sha512-XsgyUAf3nhzZmEfhc5MqLHwyaPjs78bgytpVJ/xDl0TF4Bptf3vEpBNBBT/EIKOmsOc8UbuECq3mrP3mt1QANA== + use-sidecar@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.0.2.tgz#e72f582a75842f7de4ef8becd6235a4720ad8af6"