From bb7893db8ebfc891eeb4d9df9e58b5466040a2a0 Mon Sep 17 00:00:00 2001 From: Oliver Eyton-Williams Date: Mon, 29 Nov 2021 19:30:28 +0100 Subject: [PATCH] feat: show project preview (#43967) * feat: add data for preview to challengeMeta * feat: allow creation of project preview frames * feat: make project preview data available for frame * refactor: simplify reducer * feat: show project preview for first challenge * feat: show project preview on MultiFile challenges * test: check for presence/absence of preview modal * fix: simplify previewProject saga * test: uncomment project preview test * fix: increase modal size + change modal title * modal-footer * feat: adjust preview size * fix: remove margin, padding, and line-height for preview of finished projects * Revert "fix: remove margin, padding, and line-height for preview of finished projects" This reverts commit 0db11a08191935bca9c76cd95fa629a1ba2c4ac5. * fix: remove margin on all previews * refactor: use closeModal('projectPreview') for clarity Co-authored-by: Shaun Hamilton * fix: get started -> start coding! * fix: update closeModal type Co-authored-by: moT01 <20648924+moT01@users.noreply.github.com> Co-authored-by: Ahmad Abdolsaheb Co-authored-by: Shaun Hamilton --- client/gatsby-node.js | 12 ++ client/i18n/locales/english/translations.json | 6 +- client/src/redux/prop-types.ts | 2 +- .../src/templates/Challenges/classic/show.tsx | 36 ++++-- .../Challenges/components/preview.css | 1 + .../Challenges/components/preview.tsx | 32 +++--- .../components/project-preview-modal.css | 4 + .../components/project-preview-modal.tsx | 104 ++++++++++++++++++ .../Challenges/rechallenge/builders.js | 2 +- .../Challenges/redux/action-types.js | 1 + .../redux/execute-challenge-saga.js | 22 +++- .../src/templates/Challenges/redux/index.js | 33 +++--- .../src/templates/Challenges/utils/build.js | 29 +++-- .../src/templates/Challenges/utils/frame.js | 44 +++++--- client/utils/gatsby/challenge-page-creator.js | 51 ++++++++- .../learn/challenges/project-preview.js | 53 +++++++++ cypress/plugins/index.js | 8 ++ 17 files changed, 361 insertions(+), 79 deletions(-) create mode 100644 client/src/templates/Challenges/components/project-preview-modal.css create mode 100644 client/src/templates/Challenges/components/project-preview-modal.tsx create mode 100644 cypress/integration/learn/challenges/project-preview.js diff --git a/client/gatsby-node.js b/client/gatsby-node.js index 173c7e2a73..ced7ee5195 100644 --- a/client/gatsby-node.js +++ b/client/gatsby-node.js @@ -87,9 +87,21 @@ exports.createPages = function createPages({ graphql, actions, reporter }) { src } challengeOrder + challengeFiles { + name + ext + contents + head + tail + } + solutions { + contents + ext + } superBlock superOrder template + usesMultifileEditor } } } diff --git a/client/i18n/locales/english/translations.json b/client/i18n/locales/english/translations.json index 4c8a9f0206..b2baf28f22 100644 --- a/client/i18n/locales/english/translations.json +++ b/client/i18n/locales/english/translations.json @@ -62,7 +62,8 @@ "verify-email": "Verify Email", "submit-and-go": "Submit and go to next challenge", "go-to-next": "Go to next challenge", - "ask-later": "Ask me later" + "ask-later": "Ask me later", + "start-coding": "Start coding!" }, "landing": { "big-heading-1": "Learn to code — for free.", @@ -288,7 +289,8 @@ "preview": "Preview" }, "help-translate": "We are still translating the following certifications.", - "help-translate-link": "Help us translate." + "help-translate-link": "Help us translate.", + "project-preview-title": "Here's a preview of what you will build" }, "donate": { "title": "Support our nonprofit", diff --git a/client/src/redux/prop-types.ts b/client/src/redux/prop-types.ts index 1ac13fbbac..b48238930b 100644 --- a/client/src/redux/prop-types.ts +++ b/client/src/redux/prop-types.ts @@ -106,7 +106,7 @@ export type MarkdownRemark = { type Question = { text: string; answers: string[]; solution: number }; type Fields = { slug: string; blockName: string; tests: Test[] }; -type Required = { +export type Required = { link: string; raw: boolean; src: string; diff --git a/client/src/templates/Challenges/classic/show.tsx b/client/src/templates/Challenges/classic/show.tsx index dd975d58e3..f1e4fa997e 100644 --- a/client/src/templates/Challenges/classic/show.tsx +++ b/client/src/templates/Challenges/classic/show.tsx @@ -28,6 +28,9 @@ import CompletionModal from '../components/completion-modal'; import HelpModal from '../components/help-modal'; import Output from '../components/output'; import Preview from '../components/preview'; +import ProjectPreviewModal, { + PreviewConfig +} from '../components/project-preview-modal'; import SidePanel from '../components/side-panel'; import VideoModal from '../components/video-modal'; import { @@ -41,7 +44,10 @@ import { initConsole, initTests, isChallengeCompletedSelector, - updateChallengeMeta + previewMounted, + updateChallengeMeta, + openModal, + setEditorFocusability } from '../redux'; import { getGuideUrl } from '../utils'; import MultifileEditor from './MultifileEditor'; @@ -68,7 +74,10 @@ const mapDispatchToProps = (dispatch: Dispatch) => updateChallengeMeta, challengeMounted, executeChallenge, - cancelTests + cancelTests, + previewMounted, + openModal, + setEditorFocusability }, dispatch ); @@ -87,10 +96,13 @@ interface ShowClassicProps { output: string[]; pageContext: { challengeMeta: ChallengeMeta; + projectPreview: PreviewConfig & { showProjectPreview: boolean }; }; t: TFunction; tests: Test[]; updateChallengeMeta: (arg0: ChallengeMeta) => void; + openModal: (modal: string) => void; + setEditorFocusability: (canFocus: boolean) => void; } interface ShowClassicState { @@ -231,6 +243,7 @@ class ShowClassic extends Component { initConsole, initTests, updateChallengeMeta, + openModal, data: { challengeNode: { challengeFiles, @@ -240,11 +253,15 @@ class ShowClassic extends Component { helpCategory } }, - pageContext: { challengeMeta } + pageContext: { + challengeMeta, + projectPreview: { showProjectPreview } + } } = this.props; initConsole(''); createFiles(challengeFiles ?? []); initTests(tests); + if (showProjectPreview) openModal('projectPreview'); updateChallengeMeta({ ...challengeMeta, title, @@ -358,7 +375,11 @@ class ShowClassic extends Component { renderPreview() { return ( - + ); } @@ -383,7 +404,8 @@ class ShowClassic extends Component { const { executeChallenge, pageContext: { - challengeMeta: { nextChallengePath, prevChallengePath } + challengeMeta: { nextChallengePath, prevChallengePath }, + projectPreview }, challengeFiles, t @@ -443,6 +465,7 @@ class ShowClassic extends Component { + ); @@ -456,9 +479,6 @@ export default connect( mapDispatchToProps )(withTranslation()(ShowClassic)); -// TODO: handle jsx (not sure why it doesn't get an editableRegion) EDIT: -// probably because the dummy challenge didn't include it, so Gatsby couldn't -// infer it. export const query = graphql` query ClassicChallenge($slug: String!) { challengeNode(fields: { slug: { eq: $slug } }) { diff --git a/client/src/templates/Challenges/components/preview.css b/client/src/templates/Challenges/components/preview.css index 4601370abb..11a22f1310 100644 --- a/client/src/templates/Challenges/components/preview.css +++ b/client/src/templates/Challenges/components/preview.css @@ -1,6 +1,7 @@ .challenge-preview, .challenge-preview-frame { height: 100%; + min-height: 70vh; width: 100%; padding: 0; margin: 0; diff --git a/client/src/templates/Challenges/components/preview.tsx b/client/src/templates/Challenges/components/preview.tsx index 2ce520fa5b..02f1c4ab8b 100644 --- a/client/src/templates/Challenges/components/preview.tsx +++ b/client/src/templates/Challenges/components/preview.tsx @@ -1,30 +1,23 @@ import React, { useState, useEffect } from 'react'; -import { withTranslation } from 'react-i18next'; -import { connect } from 'react-redux'; -import { bindActionCreators, Dispatch } from 'redux'; +import { useTranslation } from 'react-i18next'; -import { previewMounted } from '../redux'; +import { mainPreviewId } from '../utils/frame'; import './preview.css'; -const mainId = 'fcc-main-frame'; - -const mapDispatchToProps = (dispatch: Dispatch) => - bindActionCreators( - { - previewMounted - }, - dispatch - ); - interface PreviewProps { className?: string; disableIframe?: boolean; previewMounted: () => void; - t: (text: string) => string; + previewId?: string; } -function Preview({ disableIframe, previewMounted, t }: PreviewProps) { +function Preview({ + disableIframe, + previewMounted, + previewId +}: PreviewProps): JSX.Element { + const { t } = useTranslation(); const [iframeStatus, setIframeStatus] = useState(false); const iframeToggle = iframeStatus ? 'disable' : 'enable'; @@ -36,11 +29,14 @@ function Preview({ disableIframe, previewMounted, t }: PreviewProps) { setIframeStatus(disableIframe); }, [disableIframe]); + // TODO: remove type assertion once frame.js has been migrated. + const id: string = previewId ?? (mainPreviewId as string); + return (