Skip to content

Commit 9672565

Browse files
authored
Merge pull request #396 from coderoad/refactor-editor
Refactor editor
2 parents cfd92c7 + 0ec0902 commit 9672565

23 files changed

+541
-480
lines changed

Diff for: src/actions/index.ts

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export { default as onStartup } from './onStartup'
2+
export { default as onTutorialConfig } from './onTutorialConfig'
3+
export { default as onTutorialContinueConfig } from './onTutorialContinueConfig'
4+
export { default as onValidateSetup } from './onValidateSetup'
5+
export { default as onRunReset } from './onRunReset'
6+
export { default as onErrorPage } from './onErrorPage'
7+
export { default as onTestPass } from './onTestPass'
8+
export { onSetupActions, onSolutionActions } from './onActions'

Diff for: src/actions/setupActions.ts renamed to src/actions/onActions.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ interface SetupActions {
1313
dir?: string
1414
}
1515

16-
export const setupActions = async ({ actions, send, dir }: SetupActions): Promise<void> => {
16+
export const onSetupActions = async ({ actions, send, dir }: SetupActions): Promise<void> => {
1717
if (!actions) {
1818
return
1919
}
@@ -49,7 +49,7 @@ export const setupActions = async ({ actions, send, dir }: SetupActions): Promis
4949
}
5050
}
5151

52-
export const solutionActions = async (params: SetupActions): Promise<void> => {
52+
export const onSolutionActions = async (params: SetupActions): Promise<void> => {
5353
await git.clear()
54-
return setupActions(params).catch(onError)
54+
return onSetupActions(params).catch(onError)
5555
}

Diff for: src/actions/onErrorPage.ts

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import * as T from 'typings'
2+
import { readFile } from '../services/node'
3+
import logger from '../services/logger'
4+
5+
const onErrorPage = async (action: T.Action) => {
6+
// Error middleware
7+
if (action?.payload?.error?.type) {
8+
// load error markdown message
9+
const error = action.payload.error
10+
const errorMarkdown = await readFile(__dirname, '..', '..', 'errors', `${action.payload.error.type}.md`).catch(
11+
() => {
12+
// onError(new Error(`Error Markdown file not found for ${action.type}`))
13+
},
14+
)
15+
16+
// log error to console for safe keeping
17+
logger(`ERROR:\n ${errorMarkdown}`)
18+
19+
if (errorMarkdown) {
20+
// add a clearer error message for the user
21+
error.message = `${errorMarkdown}\n\n${error.message}`
22+
}
23+
}
24+
}
25+
26+
export default onErrorPage

Diff for: src/actions/onRunReset.ts

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import * as T from 'typings'
2+
import * as TT from 'typings/tutorial'
3+
import Context from '../services/context/context'
4+
import { exec } from '../services/node'
5+
import reset from '../services/reset'
6+
import getLastCommitHash from '../services/reset/lastHash'
7+
8+
const onRunReset = async (context: Context) => {
9+
// reset to timeline
10+
const tutorial: TT.Tutorial | null = context.tutorial.get()
11+
const position: T.Position = context.position.get()
12+
13+
// get last pass commit
14+
const hash = getLastCommitHash(position, tutorial?.levels || [])
15+
16+
const branch = tutorial?.config.repo.branch
17+
18+
if (!branch) {
19+
console.error('No repo branch found for tutorial')
20+
return
21+
}
22+
23+
// load timeline until last pass commit
24+
reset({ branch, hash })
25+
26+
// if tutorial.config.reset.command, run it
27+
if (tutorial?.config?.reset?.command) {
28+
await exec({ command: tutorial.config.reset.command })
29+
}
30+
}
31+
32+
export default onRunReset

Diff for: src/actions/onStartup.ts

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import * as vscode from 'vscode'
2+
import * as T from 'typings'
3+
import * as TT from 'typings/tutorial'
4+
import * as E from 'typings/error'
5+
import Context from '../services/context/context'
6+
import { WORKSPACE_ROOT, TUTORIAL_URL } from '../environment'
7+
import fetch from 'node-fetch'
8+
import logger from '../services/logger'
9+
10+
const onStartup = async (
11+
context: Context,
12+
workspaceState: vscode.Memento,
13+
send: (action: T.Action) => Promise<void>,
14+
) => {
15+
try {
16+
// check if a workspace is open, otherwise nothing works
17+
const noActiveWorkspace = !WORKSPACE_ROOT.length
18+
if (noActiveWorkspace) {
19+
const error: E.ErrorMessage = {
20+
type: 'NoWorkspaceFound',
21+
message: '',
22+
actions: [
23+
{
24+
label: 'Open Workspace',
25+
transition: 'REQUEST_WORKSPACE',
26+
},
27+
],
28+
}
29+
send({ type: 'NO_WORKSPACE', payload: { error } })
30+
return
31+
}
32+
33+
const env = {
34+
machineId: vscode.env.machineId,
35+
sessionId: vscode.env.sessionId,
36+
}
37+
38+
// load tutorial from url
39+
if (TUTORIAL_URL) {
40+
try {
41+
const tutorialRes = await fetch(TUTORIAL_URL)
42+
const tutorial = await tutorialRes.json()
43+
send({ type: 'START_TUTORIAL_FROM_URL', payload: { tutorial } })
44+
return
45+
} catch (e) {
46+
console.log(`Failed to load tutorial from url ${TUTORIAL_URL} with error "${e.message}"`)
47+
}
48+
}
49+
50+
// continue from tutorial from local storage
51+
const tutorial: TT.Tutorial | null = context.tutorial.get()
52+
53+
// no stored tutorial, must start new tutorial
54+
if (!tutorial || !tutorial.id) {
55+
send({ type: 'START_NEW_TUTORIAL', payload: { env } })
56+
return
57+
}
58+
59+
// load continued tutorial position & progress
60+
const { position, progress } = await context.setTutorial(workspaceState, tutorial)
61+
logger('CONTINUE STATE', position, progress)
62+
63+
if (progress.complete) {
64+
// tutorial is already complete
65+
send({ type: 'TUTORIAL_ALREADY_COMPLETE', payload: { env } })
66+
return
67+
}
68+
// communicate to client the tutorial & stepProgress state
69+
send({ type: 'LOAD_STORED_TUTORIAL', payload: { env, tutorial, progress, position } })
70+
} catch (e) {
71+
const error = {
72+
type: 'UnknownError',
73+
message: `Location: Editor startup\n\n${e.message}`,
74+
}
75+
send({ type: 'EDITOR_STARTUP_FAILED', payload: { error } })
76+
}
77+
}
78+
79+
export default onStartup

Diff for: src/actions/onTestPass.ts

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import * as git from '../services/git'
2+
import * as T from 'typings'
3+
import Context from '../services/context/context'
4+
5+
const onTestPass = (action: T.Action, context: Context) => {
6+
const tutorial = context.tutorial.get()
7+
if (!tutorial) {
8+
throw new Error('Error with current tutorial. Tutorial may be missing an id.')
9+
}
10+
// update local storage stepProgress
11+
const progress = context.progress.setStepComplete(tutorial, action.payload.position.stepId)
12+
context.position.setPositionFromProgress(tutorial, progress)
13+
git.saveCommit('Save progress')
14+
}
15+
16+
export default onTestPass

Diff for: src/actions/onTutorialConfig.ts

+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import * as vscode from 'vscode'
2+
import * as T from 'typings'
3+
import * as TT from 'typings/tutorial'
4+
import * as E from 'typings/error'
5+
import { satisfies } from 'semver'
6+
import { onEvent } from '../services/telemetry'
7+
import { version, compareVersions } from '../services/dependencies'
8+
import Context from '../services/context/context'
9+
import tutorialConfig from './utils/tutorialConfig'
10+
11+
const onTutorialConfig = async (action: T.Action, context: Context, workspaceState: vscode.Memento, send: any) => {
12+
try {
13+
const data: TT.Tutorial = action.payload.tutorial
14+
15+
onEvent('tutorial_start', {
16+
tutorial_id: data.id,
17+
tutorial_version: data.version,
18+
tutorial_title: data.summary.title,
19+
})
20+
21+
// validate extension version
22+
const expectedAppVersion = data.config?.appVersions?.vscode
23+
if (expectedAppVersion) {
24+
const extension = vscode.extensions.getExtension('coderoad.coderoad')
25+
if (extension) {
26+
const currentAppVersion = extension.packageJSON.version
27+
const satisfied = satisfies(currentAppVersion, expectedAppVersion)
28+
if (!satisfied) {
29+
const error: E.ErrorMessage = {
30+
type: 'UnmetExtensionVersion',
31+
message: `Expected CodeRoad v${expectedAppVersion}, but found v${currentAppVersion}`,
32+
}
33+
send({ type: 'TUTORIAL_CONFIGURE_FAIL', payload: { error } })
34+
return
35+
}
36+
}
37+
}
38+
39+
// setup tutorial config (save watcher, test runner, etc)
40+
await context.setTutorial(workspaceState, data)
41+
42+
// validate dependencies
43+
const dependencies = data.config.dependencies
44+
if (dependencies && dependencies.length) {
45+
for (const dep of dependencies) {
46+
// check dependency is installed
47+
const currentVersion: string | null = await version(dep.name)
48+
if (!currentVersion) {
49+
// use a custom error message
50+
const error: E.ErrorMessage = {
51+
type: 'MissingTutorialDependency',
52+
message: dep.message || `Process "${dep.name}" is required but not found. It may need to be installed`,
53+
actions: [
54+
{
55+
label: 'Check Again',
56+
transition: 'TRY_AGAIN',
57+
},
58+
],
59+
}
60+
send({ type: 'TUTORIAL_CONFIGURE_FAIL', payload: { error } })
61+
return
62+
}
63+
64+
// check dependency version
65+
const satisfiedDependency = await compareVersions(currentVersion, dep.version)
66+
67+
if (!satisfiedDependency) {
68+
const error: E.ErrorMessage = {
69+
type: 'UnmetTutorialDependency',
70+
message: `Expected ${dep.name} to have version ${dep.version}, but found version ${currentVersion}`,
71+
actions: [
72+
{
73+
label: 'Check Again',
74+
transition: 'TRY_AGAIN',
75+
},
76+
],
77+
}
78+
send({ type: 'TUTORIAL_CONFIGURE_FAIL', payload: { error } })
79+
return
80+
}
81+
82+
if (satisfiedDependency !== true) {
83+
const error: E.ErrorMessage = satisfiedDependency || {
84+
type: 'UnknownError',
85+
message: `Something went wrong comparing dependency for ${name}`,
86+
actions: [
87+
{
88+
label: 'Try Again',
89+
transition: 'TRY_AGAIN',
90+
},
91+
],
92+
}
93+
send({ type: 'TUTORIAL_CONFIGURE_FAIL', payload: { error } })
94+
return
95+
}
96+
}
97+
}
98+
99+
const error: E.ErrorMessage | void = await tutorialConfig({ data }).catch((error: Error) => ({
100+
type: 'UnknownError',
101+
message: `Location: tutorial config.\n\n${error.message}`,
102+
}))
103+
104+
// has error
105+
if (error && error.type) {
106+
send({ type: 'TUTORIAL_CONFIGURE_FAIL', payload: { error } })
107+
return
108+
}
109+
110+
// report back to the webview that setup is complete
111+
send({ type: 'TUTORIAL_CONFIGURED' })
112+
} catch (e) {
113+
const error = {
114+
type: 'UnknownError',
115+
message: `Location: EditorTutorialConfig.\n\n ${e.message}`,
116+
}
117+
send({ type: 'TUTORIAL_CONFIGURE_FAIL', payload: { error } })
118+
}
119+
}
120+
121+
export default onTutorialConfig

Diff for: src/actions/onTutorialContinueConfig.ts

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import * as vscode from 'vscode'
2+
import * as T from 'typings'
3+
import * as TT from 'typings/tutorial'
4+
import Context from '../services/context/context'
5+
import tutorialConfig from './utils/tutorialConfig'
6+
import { COMMANDS } from '../commands'
7+
8+
const onTutorialContinueConfig = async (action: T.Action, context: Context, send: any) => {
9+
try {
10+
const tutorialContinue: TT.Tutorial | null = context.tutorial.get()
11+
if (!tutorialContinue) {
12+
throw new Error('Invalid tutorial to continue')
13+
}
14+
await tutorialConfig({
15+
data: tutorialContinue,
16+
alreadyConfigured: true,
17+
})
18+
// update the current stepId on startup
19+
vscode.commands.executeCommand(COMMANDS.SET_CURRENT_POSITION, action.payload.position)
20+
} catch (e) {
21+
const error = {
22+
type: 'UnknownError',
23+
message: `Location: Editor tutorial continue config.\n\n ${e.message}`,
24+
}
25+
send({ type: 'CONTINUE_FAILED', payload: { error } })
26+
}
27+
}
28+
29+
export default onTutorialContinueConfig

Diff for: src/actions/onValidateSetup.ts

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import * as E from 'typings/error'
2+
import { version } from '../services/dependencies'
3+
import { checkWorkspaceEmpty } from '../services/workspace'
4+
5+
const onValidateSetup = async (send: any) => {
6+
try {
7+
// check workspace is selected
8+
const isEmptyWorkspace = await checkWorkspaceEmpty()
9+
if (!isEmptyWorkspace) {
10+
const error: E.ErrorMessage = {
11+
type: 'WorkspaceNotEmpty',
12+
message: '',
13+
actions: [
14+
{
15+
label: 'Open Workspace',
16+
transition: 'REQUEST_WORKSPACE',
17+
},
18+
{
19+
label: 'Check Again',
20+
transition: 'RETRY',
21+
},
22+
],
23+
}
24+
send({ type: 'VALIDATE_SETUP_FAILED', payload: { error } })
25+
return
26+
}
27+
// check Git is installed.
28+
// Should wait for workspace before running otherwise requires access to root folder
29+
const isGitInstalled = await version('git')
30+
if (!isGitInstalled) {
31+
const error: E.ErrorMessage = {
32+
type: 'GitNotFound',
33+
message: '',
34+
actions: [
35+
{
36+
label: 'Check Again',
37+
transition: 'RETRY',
38+
},
39+
],
40+
}
41+
send({ type: 'VALIDATE_SETUP_FAILED', payload: { error } })
42+
return
43+
}
44+
send({ type: 'SETUP_VALIDATED' })
45+
} catch (e) {
46+
const error = {
47+
type: 'UknownError',
48+
message: e.message,
49+
}
50+
send({ type: 'VALIDATE_SETUP_FAILED', payload: { error } })
51+
}
52+
}
53+
54+
export default onValidateSetup

Diff for: src/actions/saveCommit.ts

-7
This file was deleted.

0 commit comments

Comments
 (0)