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
}
challengeOrder
challengeFiles {
name
ext
contents
head
tail
}
solutions {
contents
ext

View File

@ -32,6 +32,7 @@ export const actionTypes = createTypes(
'openModal',
'previewMounted',
'projectPreviewMounted',
'challengeMounted',
'checkChallenge',
'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) {
return [
takeLatest(types.executeChallenge, executeCancellableChallengeSaga),
@ -247,6 +282,7 @@ export function createExecuteChallengeSaga(types) {
types.resetChallenge
],
executeCancellablePreviewSaga
)
),
takeLatest(types.projectPreviewMounted, previewProjectSolutionSaga)
];
}

View File

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

View File

@ -9,7 +9,8 @@ import { getTransformers } from '../rechallenge/transformers';
import {
createTestFramer,
runTestInTestFrame,
createMainFramer
createMainPreviewFramer,
createProjectPreviewFramer
} from './frame';
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;
// TODO: no need to await here, nothing is waiting for the preview to be
// visible
if (challengeType === challengeTypes.html) {
await new Promise(resolve =>
createMainFramer(document, resolve, proxyLogger)(buildData)
);
if (previewProjectSolution) {
await new Promise(resolve =>
createProjectPreviewFramer(document, resolve, proxyLogger)(buildData)
);
} else {
await new Promise(resolve =>
createMainPreviewFramer(document, resolve, proxyLogger)(buildData)
);
}
} else {
throw new Error(`Cannot show preview for challenge type ${challengeType}`);
}

View File

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