fix(UI): keep portal preview open (#48451)
* moved portal states to redux
stored a global window
open window if does not exist clear htmlif already exists
moved close window to desktop-layout
close portal if user navigates to route outside of challenges
naming conventions, specify url
dispatch setIsAdvancing and use in portal preview
* fix: close portal if main window closes
* fix: refactor it
* Revert "fix: refactor it"
This reverts commit 197a40a3a6
.
* feat: add isAdvancing to hotkeys
* fix: revert to pane on first step of block
* fix: safari issues
* fix: revert to pane when not advancing
---------
Co-authored-by: kravmaguy <flex4lease@gmail.com>
Co-authored-by: moT01 <20648924+moT01@users.noreply.github.com>
pull/49272/head
parent
4b8d4c495b
commit
4494b0c05b
|
@ -277,6 +277,7 @@ export type ChallengeMeta = {
|
|||
block: string;
|
||||
id: string;
|
||||
introPath: string;
|
||||
isFirstStep: boolean;
|
||||
nextChallengePath: string;
|
||||
prevChallengePath: string;
|
||||
removeComments: boolean;
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { first } from 'lodash-es';
|
||||
import React, { useState, ReactElement } from 'react';
|
||||
import React, { useState, useEffect, ReactElement } from 'react';
|
||||
import { ReflexContainer, ReflexSplitter, ReflexElement } from 'react-reflex';
|
||||
import { createSelector } from 'reselect';
|
||||
import { connect } from 'react-redux';
|
||||
import { sortChallengeFiles } from '../../../../../utils/sort-challengefiles';
|
||||
import { challengeTypes } from '../../../../utils/challenge-types';
|
||||
import {
|
||||
|
@ -8,6 +10,13 @@ import {
|
|||
ChallengeFiles,
|
||||
ResizeProps
|
||||
} from '../../../redux/prop-types';
|
||||
import { setShowPreviewPortal, setShowPreviewPane } from '../redux/actions';
|
||||
import {
|
||||
portalWindowSelector,
|
||||
showPreviewPortalSelector,
|
||||
showPreviewPaneSelector,
|
||||
isAdvancingToChallengeSelector
|
||||
} from '../redux/selectors';
|
||||
import PreviewPortal from '../components/preview-portal';
|
||||
import ActionRow from './action-row';
|
||||
|
||||
|
@ -21,6 +30,8 @@ interface DesktopLayoutProps {
|
|||
hasNotes: boolean;
|
||||
hasPreview: boolean;
|
||||
instructions: ReactElement;
|
||||
isAdvancing: boolean;
|
||||
isFirstStep: boolean;
|
||||
layoutState: {
|
||||
codePane: Pane;
|
||||
editorPane: Pane;
|
||||
|
@ -34,16 +45,51 @@ interface DesktopLayoutProps {
|
|||
resizeProps: ResizeProps;
|
||||
testOutput: ReactElement;
|
||||
windowTitle: string;
|
||||
showPreviewPortal: boolean;
|
||||
showPreviewPane: boolean;
|
||||
setShowPreviewPortal: (arg: boolean) => void;
|
||||
setShowPreviewPane: (arg: boolean) => void;
|
||||
portalWindow: null | Window;
|
||||
}
|
||||
|
||||
const reflexProps = {
|
||||
propagateDimensions: true
|
||||
};
|
||||
|
||||
const mapDispatchToProps = {
|
||||
setShowPreviewPortal,
|
||||
setShowPreviewPane
|
||||
};
|
||||
|
||||
const mapStateToProps = createSelector(
|
||||
isAdvancingToChallengeSelector,
|
||||
showPreviewPortalSelector,
|
||||
showPreviewPaneSelector,
|
||||
portalWindowSelector,
|
||||
|
||||
(
|
||||
isAdvancing: boolean,
|
||||
showPreviewPortal: boolean,
|
||||
showPreviewPane: boolean,
|
||||
portalWindow: null | Window
|
||||
) => ({
|
||||
isAdvancing,
|
||||
showPreviewPortal,
|
||||
showPreviewPane,
|
||||
portalWindow
|
||||
})
|
||||
);
|
||||
|
||||
const DesktopLayout = (props: DesktopLayoutProps): JSX.Element => {
|
||||
const {
|
||||
showPreviewPane,
|
||||
showPreviewPortal,
|
||||
setShowPreviewPane,
|
||||
setShowPreviewPortal,
|
||||
portalWindow
|
||||
} = props;
|
||||
|
||||
const [showNotes, setShowNotes] = useState(false);
|
||||
const [showPreviewPane, setShowPreviewPane] = useState(true);
|
||||
const [showPreviewPortal, setShowPreviewPortal] = useState(false);
|
||||
const [showConsole, setShowConsole] = useState(false);
|
||||
const [showInstructions, setShowInstuctions] = useState(true);
|
||||
|
||||
|
@ -52,10 +98,12 @@ const DesktopLayout = (props: DesktopLayoutProps): JSX.Element => {
|
|||
case 'showPreviewPane':
|
||||
if (!showPreviewPane && showPreviewPortal) setShowPreviewPortal(false);
|
||||
setShowPreviewPane(!showPreviewPane);
|
||||
portalWindow?.close();
|
||||
break;
|
||||
case 'showPreviewPortal':
|
||||
if (!showPreviewPortal && showPreviewPane) setShowPreviewPane(false);
|
||||
setShowPreviewPortal(!showPreviewPortal);
|
||||
if (showPreviewPortal) portalWindow?.close();
|
||||
break;
|
||||
case 'showConsole':
|
||||
setShowConsole(!showConsole);
|
||||
|
@ -88,6 +136,8 @@ const DesktopLayout = (props: DesktopLayoutProps): JSX.Element => {
|
|||
testOutput,
|
||||
hasNotes,
|
||||
hasPreview,
|
||||
isAdvancing,
|
||||
isFirstStep,
|
||||
layoutState,
|
||||
notes,
|
||||
preview,
|
||||
|
@ -95,6 +145,18 @@ const DesktopLayout = (props: DesktopLayoutProps): JSX.Element => {
|
|||
windowTitle
|
||||
} = props;
|
||||
|
||||
// on mount
|
||||
useEffect(() => {
|
||||
if (isFirstStep) {
|
||||
setShowPreviewPortal(false);
|
||||
portalWindow?.close();
|
||||
setShowPreviewPane(true);
|
||||
} else if (!isAdvancing && !showPreviewPane && !showPreviewPortal) {
|
||||
togglePane('showPreviewPane');
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const challengeFile = getChallengeFile();
|
||||
const projectBasedChallenge = hasEditableBoundaries;
|
||||
const isMultifileCertProject =
|
||||
|
@ -197,9 +259,7 @@ const DesktopLayout = (props: DesktopLayoutProps): JSX.Element => {
|
|||
)}
|
||||
</ReflexContainer>
|
||||
{displayPreviewPortal && (
|
||||
<PreviewPortal togglePane={togglePane} windowTitle={windowTitle}>
|
||||
{preview}
|
||||
</PreviewPortal>
|
||||
<PreviewPortal windowTitle={windowTitle}>{preview}</PreviewPortal>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
@ -207,4 +267,4 @@ const DesktopLayout = (props: DesktopLayoutProps): JSX.Element => {
|
|||
|
||||
DesktopLayout.displayName = 'DesktopLayout';
|
||||
|
||||
export default DesktopLayout;
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(DesktopLayout);
|
||||
|
|
|
@ -46,7 +46,8 @@ import {
|
|||
previewMounted,
|
||||
updateChallengeMeta,
|
||||
openModal,
|
||||
setEditorFocusability
|
||||
setEditorFocusability,
|
||||
setIsAdvancing
|
||||
} from '../redux/actions';
|
||||
import {
|
||||
challengeFilesSelector,
|
||||
|
@ -84,7 +85,8 @@ const mapDispatchToProps = (dispatch: Dispatch) =>
|
|||
cancelTests,
|
||||
previewMounted,
|
||||
openModal,
|
||||
setEditorFocusability
|
||||
setEditorFocusability,
|
||||
setIsAdvancing
|
||||
},
|
||||
dispatch
|
||||
);
|
||||
|
@ -113,6 +115,7 @@ interface ShowClassicProps {
|
|||
updateChallengeMeta: (arg0: ChallengeMeta) => void;
|
||||
openModal: (modal: string) => void;
|
||||
setEditorFocusability: (canFocus: boolean) => void;
|
||||
setIsAdvancing: (arg: boolean) => void;
|
||||
previewMounted: () => void;
|
||||
savedChallenges: CompletedChallenge[];
|
||||
}
|
||||
|
@ -292,6 +295,7 @@ class ShowClassic extends Component<ShowClassicProps, ShowClassicState> {
|
|||
initTests,
|
||||
updateChallengeMeta,
|
||||
openModal,
|
||||
setIsAdvancing,
|
||||
savedChallenges,
|
||||
data: {
|
||||
challengeNode: {
|
||||
|
@ -327,6 +331,7 @@ class ShowClassic extends Component<ShowClassicProps, ShowClassicState> {
|
|||
helpCategory
|
||||
});
|
||||
challengeMounted(challengeMeta.id);
|
||||
setIsAdvancing(false);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
@ -480,7 +485,7 @@ class ShowClassic extends Component<ShowClassicProps, ShowClassicState> {
|
|||
const {
|
||||
executeChallenge,
|
||||
pageContext: {
|
||||
challengeMeta: { nextChallengePath, prevChallengePath },
|
||||
challengeMeta: { isFirstStep, nextChallengePath, prevChallengePath },
|
||||
projectPreview: { challengeData, showProjectPreview }
|
||||
},
|
||||
challengeFiles,
|
||||
|
@ -539,6 +544,7 @@ class ShowClassic extends Component<ShowClassicProps, ShowClassicState> {
|
|||
instructions={this.renderInstructionsPanel({
|
||||
showToolPanel: true
|
||||
})}
|
||||
isFirstStep={isFirstStep}
|
||||
layoutState={this.state.layout}
|
||||
notes={this.renderNotes(notes)}
|
||||
preview={this.renderPreview()}
|
||||
|
|
|
@ -4,12 +4,14 @@ import { HotKeys, GlobalHotKeys } from 'react-hotkeys';
|
|||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { ChallengeFiles, Test, User } from '../../../redux/prop-types';
|
||||
import { isChallenge } from '../../../utils/path-parsers';
|
||||
|
||||
import { userSelector } from '../../../redux/selectors';
|
||||
import {
|
||||
setEditorFocusability,
|
||||
submitChallenge,
|
||||
openModal
|
||||
openModal,
|
||||
setIsAdvancing
|
||||
} from '../redux/actions';
|
||||
import {
|
||||
canFocusEditorSelector,
|
||||
|
@ -40,7 +42,8 @@ const mapStateToProps = createSelector(
|
|||
const mapDispatchToProps = {
|
||||
setEditorFocusability,
|
||||
submitChallenge,
|
||||
openShortcutsModal: () => openModal('shortcuts')
|
||||
openShortcutsModal: () => openModal('shortcuts'),
|
||||
setIsAdvancing
|
||||
};
|
||||
|
||||
const keyMap = {
|
||||
|
@ -66,6 +69,7 @@ interface HotkeysProps {
|
|||
nextChallengePath: string;
|
||||
prevChallengePath: string;
|
||||
setEditorFocusability: (arg0: boolean) => void;
|
||||
setIsAdvancing: (arg0: boolean) => void;
|
||||
tests: Test[];
|
||||
usesMultifileEditor?: boolean;
|
||||
openShortcutsModal: () => void;
|
||||
|
@ -83,6 +87,7 @@ function Hotkeys({
|
|||
nextChallengePath,
|
||||
prevChallengePath,
|
||||
setEditorFocusability,
|
||||
setIsAdvancing,
|
||||
submitChallenge,
|
||||
tests,
|
||||
usesMultifileEditor,
|
||||
|
@ -130,10 +135,16 @@ function Hotkeys({
|
|||
},
|
||||
navigationMode: () => setEditorFocusability(false),
|
||||
navigatePrev: () => {
|
||||
if (!canFocusEditor) void navigate(prevChallengePath);
|
||||
if (!canFocusEditor) {
|
||||
if (isChallenge(prevChallengePath)) setIsAdvancing(true);
|
||||
void navigate(prevChallengePath);
|
||||
}
|
||||
},
|
||||
navigateNext: () => {
|
||||
if (!canFocusEditor) void navigate(nextChallengePath);
|
||||
if (!canFocusEditor) {
|
||||
if (isChallenge(nextChallengePath)) setIsAdvancing(true);
|
||||
void navigate(nextChallengePath);
|
||||
}
|
||||
},
|
||||
showShortcuts: (e: React.KeyboardEvent) => {
|
||||
if (!canFocusEditor && e.shiftKey && e.key === '?') {
|
||||
|
|
|
@ -2,35 +2,60 @@ import { Component, ReactElement } from 'react';
|
|||
import ReactDOM from 'react-dom';
|
||||
import { TFunction, withTranslation } from 'react-i18next';
|
||||
import { connect } from 'react-redux';
|
||||
import { storePortalDocument, removePortalDocument } from '../redux/actions';
|
||||
import { createSelector } from 'reselect';
|
||||
import {
|
||||
storePortalWindow,
|
||||
removePortalWindow,
|
||||
setShowPreviewPortal,
|
||||
setIsAdvancing
|
||||
} from '../redux/actions';
|
||||
import {
|
||||
portalWindowSelector,
|
||||
isAdvancingToChallengeSelector
|
||||
} from '../redux/selectors';
|
||||
|
||||
interface PreviewPortalProps {
|
||||
children: ReactElement | null;
|
||||
togglePane: (pane: string) => void;
|
||||
windowTitle: string;
|
||||
t: TFunction;
|
||||
storePortalDocument: (document: Document | undefined) => void;
|
||||
removePortalDocument: () => void;
|
||||
storePortalWindow: (window: Window | null) => void;
|
||||
removePortalWindow: () => void;
|
||||
portalWindow: null | Window;
|
||||
setShowPreviewPortal: (arg: boolean) => void;
|
||||
setIsAdvancing: (arg: boolean) => void;
|
||||
isAdvancing: boolean;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
storePortalDocument,
|
||||
removePortalDocument
|
||||
storePortalWindow,
|
||||
removePortalWindow,
|
||||
setShowPreviewPortal,
|
||||
setIsAdvancing
|
||||
};
|
||||
|
||||
const mapStateToProps = createSelector(
|
||||
isAdvancingToChallengeSelector,
|
||||
portalWindowSelector,
|
||||
(isAdvancing: boolean, portalWindow: null | Window) => ({
|
||||
isAdvancing,
|
||||
portalWindow
|
||||
})
|
||||
);
|
||||
|
||||
class PreviewPortal extends Component<PreviewPortalProps> {
|
||||
static displayName = 'PreviewPortal';
|
||||
mainWindow: Window;
|
||||
externalWindow: Window | null = null;
|
||||
isAdvancing: boolean;
|
||||
containerEl;
|
||||
titleEl;
|
||||
styleEl;
|
||||
|
||||
constructor(props: PreviewPortalProps) {
|
||||
super(props);
|
||||
|
||||
this.mainWindow = window;
|
||||
this.externalWindow = null;
|
||||
this.externalWindow = this.props.portalWindow;
|
||||
this.isAdvancing = this.props.isAdvancing;
|
||||
this.containerEl = document.createElement('div');
|
||||
this.titleEl = document.createElement('title');
|
||||
this.styleEl = document.createElement('style');
|
||||
|
@ -39,6 +64,17 @@ class PreviewPortal extends Component<PreviewPortalProps> {
|
|||
componentDidMount() {
|
||||
const { t, windowTitle } = this.props;
|
||||
|
||||
if (!this.externalWindow) {
|
||||
this.externalWindow = window.open(
|
||||
'',
|
||||
'',
|
||||
'width=960,height=540,left=100,top=100'
|
||||
);
|
||||
} else {
|
||||
this.externalWindow.document.head.innerHTML = '';
|
||||
this.externalWindow.document.body.innerHTML = '';
|
||||
}
|
||||
|
||||
this.titleEl.innerText = `${t(
|
||||
'learn.editor-tabs.preview'
|
||||
)} | ${windowTitle}`;
|
||||
|
@ -51,12 +87,6 @@ class PreviewPortal extends Component<PreviewPortalProps> {
|
|||
}
|
||||
`;
|
||||
|
||||
this.externalWindow = window.open(
|
||||
'',
|
||||
'',
|
||||
'width=960,height=540,left=100,top=100'
|
||||
);
|
||||
|
||||
this.externalWindow?.document.head.appendChild(this.titleEl);
|
||||
this.externalWindow?.document.head.appendChild(this.styleEl);
|
||||
this.externalWindow?.document.body.setAttribute(
|
||||
|
@ -69,19 +99,23 @@ class PreviewPortal extends Component<PreviewPortalProps> {
|
|||
);
|
||||
this.externalWindow?.document.body.appendChild(this.containerEl);
|
||||
this.externalWindow?.addEventListener('beforeunload', () => {
|
||||
this.props.togglePane('showPreviewPortal');
|
||||
this.props.setShowPreviewPortal(false);
|
||||
});
|
||||
|
||||
this.props.storePortalDocument(this.externalWindow?.document);
|
||||
this.props.storePortalWindow(this.externalWindow);
|
||||
|
||||
// close the portal if the main window closes
|
||||
this.mainWindow?.addEventListener('beforeunload', () => {
|
||||
this.externalWindow?.close();
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.externalWindow?.close();
|
||||
this.props.removePortalDocument();
|
||||
if (!this.props.isAdvancing) {
|
||||
this.externalWindow?.close();
|
||||
}
|
||||
this.props.removePortalWindow();
|
||||
this.props.setIsAdvancing(false);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -92,6 +126,6 @@ class PreviewPortal extends Component<PreviewPortalProps> {
|
|||
PreviewPortal.displayName = 'PreviewPortal';
|
||||
|
||||
export default connect(
|
||||
null,
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(withTranslation()(PreviewPortal));
|
||||
|
|
|
@ -28,14 +28,16 @@ export const actionTypes = createTypes(
|
|||
'storedCodeFound',
|
||||
'noStoredCodeFound',
|
||||
'saveEditorContent',
|
||||
|
||||
'setShowPreviewPane',
|
||||
'setShowPreviewPortal',
|
||||
'closeModal',
|
||||
'openModal',
|
||||
'setIsAdvancing',
|
||||
|
||||
'previewMounted',
|
||||
'projectPreviewMounted',
|
||||
'storePortalDocument',
|
||||
'removePortalDocument',
|
||||
'storePortalWindow',
|
||||
'removePortalWindow',
|
||||
'challengeMounted',
|
||||
'checkChallenge',
|
||||
'executeChallenge',
|
||||
|
|
|
@ -22,7 +22,6 @@ export const createQuestion = createAction(actionTypes.createQuestion);
|
|||
export const initTests = createAction(actionTypes.initTests);
|
||||
export const updateTests = createAction(actionTypes.updateTests);
|
||||
export const cancelTests = createAction(actionTypes.cancelTests);
|
||||
|
||||
export const initConsole = createAction(actionTypes.initConsole);
|
||||
export const initLogs = createAction(actionTypes.initLogs);
|
||||
export const updateChallengeMeta = createAction(
|
||||
|
@ -37,6 +36,10 @@ export const updateSolutionFormValues = createAction(
|
|||
export const updateSuccessMessage = createAction(
|
||||
actionTypes.updateSuccessMessage
|
||||
);
|
||||
export const setShowPreviewPortal = createAction(
|
||||
actionTypes.setShowPreviewPortal
|
||||
);
|
||||
export const setShowPreviewPane = createAction(actionTypes.setShowPreviewPane);
|
||||
|
||||
export const logsToConsole = createAction(actionTypes.logsToConsole);
|
||||
|
||||
|
@ -48,7 +51,7 @@ export const disableBuildOnError = createAction(
|
|||
export const storedCodeFound = createAction(actionTypes.storedCodeFound);
|
||||
export const noStoredCodeFound = createAction(actionTypes.noStoredCodeFound);
|
||||
export const saveEditorContent = createAction(actionTypes.saveEditorContent);
|
||||
|
||||
export const setIsAdvancing = createAction(actionTypes.setIsAdvancing);
|
||||
export const closeModal = createAction(actionTypes.closeModal);
|
||||
export const openModal = createAction(actionTypes.openModal);
|
||||
|
||||
|
@ -57,12 +60,8 @@ export const projectPreviewMounted = createAction(
|
|||
actionTypes.projectPreviewMounted
|
||||
);
|
||||
|
||||
export const storePortalDocument = createAction(
|
||||
actionTypes.storePortalDocument
|
||||
);
|
||||
export const removePortalDocument = createAction(
|
||||
actionTypes.removePortalDocument
|
||||
);
|
||||
export const storePortalWindow = createAction(actionTypes.storePortalWindow);
|
||||
export const removePortalWindow = createAction(actionTypes.removePortalWindow);
|
||||
|
||||
export const challengeMounted = createAction(actionTypes.challengeMounted);
|
||||
export const checkChallenge = createAction(actionTypes.checkChallenge);
|
||||
|
|
|
@ -3,7 +3,7 @@ import { omit } from 'lodash-es';
|
|||
import { ofType } from 'redux-observable';
|
||||
import { empty, of } from 'rxjs';
|
||||
import { catchError, concat, retry, switchMap, tap } from 'rxjs/operators';
|
||||
|
||||
import { isChallenge } from '../../../utils/path-parsers';
|
||||
import { challengeTypes, submitTypes } from '../../../../utils/challenge-types';
|
||||
import { actionTypes as submitActionTypes } from '../../../redux/action-types';
|
||||
import {
|
||||
|
@ -16,7 +16,11 @@ import { mapFilesToChallengeFiles } from '../../../utils/ajax';
|
|||
import { standardizeRequestBody } from '../../../utils/challenge-request-helpers';
|
||||
import postUpdate$ from '../utils/post-update';
|
||||
import { actionTypes } from './action-types';
|
||||
import { closeModal, updateSolutionFormValues } from './actions';
|
||||
import {
|
||||
closeModal,
|
||||
updateSolutionFormValues,
|
||||
setIsAdvancing
|
||||
} from './actions';
|
||||
import {
|
||||
challengeFilesSelector,
|
||||
challengeMetaSelector,
|
||||
|
@ -174,6 +178,7 @@ export default function completionEpic(action$, state$) {
|
|||
};
|
||||
|
||||
return submitter(type, state).pipe(
|
||||
concat(of(setIsAdvancing(isChallenge(pathToNavigateTo())))),
|
||||
tap(res => {
|
||||
if (res.type !== submitActionTypes.updateFailed) {
|
||||
navigate(pathToNavigateTo());
|
||||
|
|
|
@ -41,9 +41,12 @@ const initialState = {
|
|||
projectPreview: false,
|
||||
shortcuts: false
|
||||
},
|
||||
portalDocument: false,
|
||||
portalWindow: null,
|
||||
showPreviewPortal: false,
|
||||
showPreviewPane: true,
|
||||
projectFormValues: {},
|
||||
successMessage: 'Happy Coding!'
|
||||
successMessage: 'Happy Coding!',
|
||||
isAdvancing: false
|
||||
};
|
||||
|
||||
export const epics = [
|
||||
|
@ -178,18 +181,30 @@ export const reducer = handleActions(
|
|||
...state,
|
||||
isBuildEnabled: false
|
||||
}),
|
||||
[actionTypes.storePortalDocument]: (state, { payload }) => ({
|
||||
[actionTypes.setShowPreviewPortal]: (state, { payload }) => ({
|
||||
...state,
|
||||
portalDocument: payload
|
||||
showPreviewPortal: payload
|
||||
}),
|
||||
[actionTypes.removePortalDocument]: state => ({
|
||||
[actionTypes.setShowPreviewPane]: (state, { payload }) => ({
|
||||
...state,
|
||||
portalDocument: false
|
||||
showPreviewPane: payload
|
||||
}),
|
||||
[actionTypes.storePortalWindow]: (state, { payload }) => ({
|
||||
...state,
|
||||
portalWindow: payload
|
||||
}),
|
||||
[actionTypes.removePortalWindow]: state => ({
|
||||
...state,
|
||||
portalWindow: null
|
||||
}),
|
||||
[actionTypes.updateSuccessMessage]: (state, { payload }) => ({
|
||||
...state,
|
||||
successMessage: payload
|
||||
}),
|
||||
[actionTypes.setIsAdvancing]: (state, { payload }) => ({
|
||||
...state,
|
||||
isAdvancing: payload
|
||||
}),
|
||||
[actionTypes.closeModal]: (state, { payload }) => ({
|
||||
...state,
|
||||
modal: {
|
||||
|
|
|
@ -29,8 +29,9 @@ export const successMessageSelector = state => state[ns].successMessage;
|
|||
|
||||
export const projectFormValuesSelector = state =>
|
||||
state[ns].projectFormValues || {};
|
||||
|
||||
export const portalDocumentSelector = state => state[ns].portalDocument;
|
||||
export const isAdvancingToChallengeSelector = state => state[ns].isAdvancing;
|
||||
export const portalDocumentSelector = state => state[ns].portalWindow?.document;
|
||||
export const portalWindowSelector = state => state[ns].portalWindow;
|
||||
|
||||
export const challengeDataSelector = state => {
|
||||
const { challengeType } = challengeMetaSelector(state);
|
||||
|
@ -84,3 +85,5 @@ export const challengeDataSelector = state => {
|
|||
export const attemptsSelector = state => state[ns].attempts;
|
||||
export const canFocusEditorSelector = state => state[ns].canFocusEditor;
|
||||
export const visibleEditorsSelector = state => state[ns].visibleEditors;
|
||||
export const showPreviewPortalSelector = state => state[ns].showPreviewPortal;
|
||||
export const showPreviewPaneSelector = state => state[ns].showPreviewPane;
|
||||
|
|
|
@ -48,6 +48,14 @@ const views = {
|
|||
// quiz: Quiz
|
||||
};
|
||||
|
||||
function getIsFirstStep(_node, index, nodeArray) {
|
||||
const current = nodeArray[index];
|
||||
const previous = nodeArray[index - 1];
|
||||
|
||||
if (!previous) return true;
|
||||
return previous.node.challenge.block !== current.node.challenge.block;
|
||||
}
|
||||
|
||||
function getNextChallengePath(_node, index, nodeArray) {
|
||||
const next = nodeArray[index + 1];
|
||||
return next ? next.node.challenge.fields.slug : '/learn';
|
||||
|
@ -85,6 +93,7 @@ exports.createChallengePages = function (createPage) {
|
|||
certification,
|
||||
superBlock,
|
||||
block,
|
||||
isFirstStep: getIsFirstStep(challenge, index, allChallengeEdges),
|
||||
template,
|
||||
required,
|
||||
nextChallengePath: getNextChallengePath(
|
||||
|
|
Loading…
Reference in New Issue