feat: make project preview data available for frame
This commit is contained in:
committed by
moT01
parent
8deef3099a
commit
0807a584c4
@ -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
|
||||||
|
@ -32,6 +32,7 @@ export const actionTypes = createTypes(
|
|||||||
'openModal',
|
'openModal',
|
||||||
|
|
||||||
'previewMounted',
|
'previewMounted',
|
||||||
|
'projectPreviewMounted',
|
||||||
'challengeMounted',
|
'challengeMounted',
|
||||||
'checkChallenge',
|
'checkChallenge',
|
||||||
'executeChallenge',
|
'executeChallenge',
|
||||||
|
@ -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)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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}`);
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
Reference in New Issue
Block a user