feat: make project preview data available for frame

This commit is contained in:
Oliver Eyton-Williams
2021-10-20 13:07:00 +02:00
committed by moT01
parent 8deef3099a
commit 0807a584c4
6 changed files with 120 additions and 22 deletions

View File

@ -87,6 +87,13 @@ exports.createPages = function createPages({ graphql, actions, reporter }) {
src src
} }
challengeOrder challengeOrder
challengeFiles {
name
ext
contents
head
tail
}
solutions { solutions {
contents contents
ext ext

View File

@ -32,6 +32,7 @@ export const actionTypes = createTypes(
'openModal', 'openModal',
'previewMounted', 'previewMounted',
'projectPreviewMounted',
'challengeMounted', 'challengeMounted',
'checkChallenge', 'checkChallenge',
'executeChallenge', 'executeChallenge',

View File

@ -236,6 +236,41 @@ function* previewChallengeSaga({ flushLogs = true } = {}) {
} }
} }
// TODO: DRY this and previewChallengeSaga
// TODO: remove everything about proxyLogger here
function* previewProjectSolutionSaga({ payload }) {
if (!payload) return;
const { isFirstChallengeInBlock, challengeData } = payload;
if (!isFirstChallengeInBlock) return;
const logProxy = yield channel();
const proxyLogger = args => logProxy.put(args);
try {
yield fork(takeEveryConsole, logProxy);
if (canBuildChallenge(challengeData)) {
const challengeMeta = yield select(challengeMetaSelector);
const protect = isLoopProtected(challengeMeta);
const buildData = yield buildChallengeData(challengeData, {
preview: true,
protect
});
if (challengeHasPreview(challengeData)) {
const document = yield getContext('document');
yield call(updatePreview, buildData, document, proxyLogger, true);
}
}
} catch (err) {
if (err === 'timeout') {
// TODO: translate the error
// eslint-disable-next-line no-ex-assign
err = `The code you have written is taking longer than the ${previewTimeout}ms our challenges allow. You may have created an infinite loop or need to write a more efficient algorithm`;
}
console.log(err);
yield put(updateConsole(escape(err)));
}
}
export function createExecuteChallengeSaga(types) { export function createExecuteChallengeSaga(types) {
return [ return [
takeLatest(types.executeChallenge, executeCancellableChallengeSaga), takeLatest(types.executeChallenge, executeCancellableChallengeSaga),
@ -247,6 +282,7 @@ export function createExecuteChallengeSaga(types) {
types.resetChallenge types.resetChallenge
], ],
executeCancellablePreviewSaga executeCancellablePreviewSaga
) ),
takeLatest(types.projectPreviewMounted, previewProjectSolutionSaga)
]; ];
} }

View File

@ -39,7 +39,8 @@ const initialState = {
completion: false, completion: false,
help: false, help: false,
video: false, video: false,
reset: false reset: false,
projectPreview: false
}, },
projectFormValues: {}, projectFormValues: {},
successMessage: 'Happy Coding!' successMessage: 'Happy Coding!'
@ -114,6 +115,9 @@ export const closeModal = createAction(actionTypes.closeModal);
export const openModal = createAction(actionTypes.openModal); export const openModal = createAction(actionTypes.openModal);
export const previewMounted = createAction(actionTypes.previewMounted); export const previewMounted = createAction(actionTypes.previewMounted);
export const projectPreviewMounted = createAction(
actionTypes.projectPreviewMounted
);
export const challengeMounted = createAction(actionTypes.challengeMounted); export const challengeMounted = createAction(actionTypes.challengeMounted);
export const checkChallenge = createAction(actionTypes.checkChallenge); export const checkChallenge = createAction(actionTypes.checkChallenge);
export const executeChallenge = createAction(actionTypes.executeChallenge); export const executeChallenge = createAction(actionTypes.executeChallenge);
@ -148,6 +152,8 @@ export const isCompletionModalOpenSelector = state =>
export const isHelpModalOpenSelector = state => state[ns].modal.help; export const isHelpModalOpenSelector = state => state[ns].modal.help;
export const isVideoModalOpenSelector = state => state[ns].modal.video; export const isVideoModalOpenSelector = state => state[ns].modal.video;
export const isResetModalOpenSelector = state => state[ns].modal.reset; export const isResetModalOpenSelector = state => state[ns].modal.reset;
export const isProjectPreviewModalOpenSelector = state =>
state[ns].modal.projectPreview;
export const isResettingSelector = state => state[ns].isResetting; export const isResettingSelector = state => state[ns].isResetting;
export const isBuildEnabledSelector = state => state[ns].isBuildEnabled; export const isBuildEnabledSelector = state => state[ns].isBuildEnabled;

View File

@ -9,7 +9,8 @@ import { getTransformers } from '../rechallenge/transformers';
import { import {
createTestFramer, createTestFramer,
runTestInTestFrame, runTestInTestFrame,
createMainFramer createMainPreviewFramer,
createProjectPreviewFramer
} from './frame'; } from './frame';
import createWorker from './worker-executor'; import createWorker from './worker-executor';
@ -197,13 +198,27 @@ export function buildBackendChallenge({ url }) {
}; };
} }
export async function updatePreview(buildData, document, proxyLogger) { // TODO: use fewer arguments (and no boolean ones!)
export async function updatePreview(
buildData,
document,
proxyLogger,
previewProjectSolution
) {
const { challengeType } = buildData; const { challengeType } = buildData;
// TODO: no need to await here, nothing is waiting for the preview to be
// visible
if (challengeType === challengeTypes.html) { if (challengeType === challengeTypes.html) {
await new Promise(resolve => if (previewProjectSolution) {
createMainFramer(document, resolve, proxyLogger)(buildData) await new Promise(resolve =>
); createProjectPreviewFramer(document, resolve, proxyLogger)(buildData)
);
} else {
await new Promise(resolve =>
createMainPreviewFramer(document, resolve, proxyLogger)(buildData)
);
}
} else { } else {
throw new Error(`Cannot show preview for challenge type ${challengeType}`); throw new Error(`Cannot show preview for challenge type ${challengeType}`);
} }

View File

@ -1,6 +1,7 @@
const path = require('path'); const path = require('path');
const { createPoly } = require('../../../utils/polyvinyl');
const { dasherize } = require('../../../utils/slugs'); const { dasherize } = require('../../../utils/slugs');
const { sortChallengeFiles } = require('../../../utils/sort-challengefiles');
const { viewTypes } = require('../challenge-types'); const { viewTypes } = require('../challenge-types');
const backend = path.resolve( const backend = path.resolve(
@ -57,7 +58,7 @@ function getTemplateComponent(challengeType) {
} }
exports.createChallengePages = function (createPage) { exports.createChallengePages = function (createPage) {
return function ({ node }, index, allChallenges) { return function ({ node: challenge }, index, allChallengeEdges) {
const { const {
superBlock, superBlock,
block, block,
@ -65,17 +66,11 @@ exports.createChallengePages = function (createPage) {
required = [], required = [],
template, template,
challengeType, challengeType,
id, id
challengeOrder } = challenge;
} = node;
// TODO: challengeType === 7 and isPrivate are the same, right? If so, we // TODO: challengeType === 7 and isPrivate are the same, right? If so, we
// should remove one of them. // should remove one of them.
const challengesInBlock = allChallenges.filter(
({ node }) => node.block === block
);
const allSolutionsToLastChallenge =
challengesInBlock[challengesInBlock.length - 1].node.solutions;
createPage({ createPage({
path: slug, path: slug,
component: getTemplateComponent(challengeType), component: getTemplateComponent(challengeType),
@ -85,18 +80,56 @@ exports.createChallengePages = function (createPage) {
block, block,
template, template,
required, required,
nextChallengePath: getNextChallengePath(node, index, allChallenges), nextChallengePath: getNextChallengePath(
prevChallengePath: getPrevChallengePath(node, index, allChallenges), challenge,
id, index,
isFirstChallengeInBlock: challengeOrder === 0, allChallengeEdges
solutionToLastChallenge: allSolutionsToLastChallenge[0] ),
prevChallengePath: getPrevChallengePath(
challenge,
index,
allChallengeEdges
),
id
}, },
projectPreview: getProjectPreviewConfig(challenge, allChallengeEdges),
slug slug
} }
}); });
}; };
}; };
function getProjectPreviewConfig(challenge, allChallengeEdges) {
const { block, challengeOrder } = challenge;
const challengesInBlock = allChallengeEdges
.filter(({ node }) => node.block === block)
.map(({ node }) => node);
const lastChallenge = challengesInBlock[challengesInBlock.length - 1];
const solutionToLastChallenge = sortChallengeFiles(
lastChallenge.solutions[0] ?? []
);
const lastChallengeFiles = sortChallengeFiles(
lastChallenge.challengeFiles ?? []
);
const projectPreviewChallengeFiles = lastChallengeFiles.map((file, id) =>
createPoly({
...file,
contents: solutionToLastChallenge[id]?.contents ?? file.contents
})
);
return {
isFirstChallengeInBlock: challengeOrder === 0,
challengeData: {
challengeType: lastChallenge.challengeType,
challengeFiles: projectPreviewChallengeFiles,
required: lastChallenge.required,
template: lastChallenge.template
}
};
}
exports.createBlockIntroPages = function (createPage) { exports.createBlockIntroPages = function (createPage) {
return function (edge) { return function (edge) {
const { const {