Skip to content

Commit 4ed2b3d

Browse files
authored
feat: add ability to view solutions in lessons (#202)
* feat: add solution to Basics tutorials * fix: console error * feat: add solution to blog lessons * tweak lesson wording * add instructions for solution code * chore: switch _solution to solution * fix: typo
1 parent f972941 commit 4ed2b3d

File tree

14 files changed

+317
-262
lines changed

14 files changed

+317
-262
lines changed

README.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,20 @@ Remember that you can add comments to your default code to orient the user, such
153153
// your code goes here
154154
```
155155

156+
#### Provide the simplest solution to your exercise
157+
158+
`solution` is a string property. The value you set for `solution` in your Vue
159+
file will be used to populate the code editor if the user clicks the "View
160+
Solution" option. (We hope you'll have provided enough clues that they won't need
161+
to do this!)
162+
163+
Be sure to test your solution code. If the user clicks "View Solution" and then
164+
"Submit", they should see your success message.
165+
166+
There's almost always more than one way to solve a coding challenge. Although your
167+
validation code (see below) should allow all reasonable solutions to pass, the
168+
`solution` code you provide should be the most straightforward option which
169+
requires the least thorough understanding of JavaScript.
156170

157171
#### Validate the user's submitted code
158172

@@ -189,7 +203,10 @@ const validate = async (result, ipfs) => {
189203
```
190204
Be sure to include conditionals that will catch common mistakes and provide useful clues.
191205

192-
If the object returned by your `validate` function the has the property `fail`, the message string you've provided will be shown highlighted in red, and the user will have the opportunity to update and resubmit their code. If it has the property `success`, the user will se the success message highlighted in green, and the "Submit" button will change into a "Next" button allowing them to advance to the next lesson.
206+
If the object returned by your `validate` function the has the property `fail`, the message string you've provided will be shown highlighted in red, and the user will have the opportunity to update and resubmit their code. If it has the property `success`, the user will see the success message highlighted in green, and the "Submit" button will change into a "Next" button allowing them to advance to the next lesson.
207+
208+
If this is the last lesson in your tutorial, the user will see a "More Tutorials" button instead of a "Next" button. Please create a success message for your last lesson that notes that the user has completed the whole tutorial. For example, `Great job! You've completed this series of lessons!`)
209+
193210

194211
##### Override external error messages (optional)
195212

src/components/Lesson.vue

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363
<label for="add-files" class="flex items-center h4 pointer">
6464
<svg viewBox="0 0 100 100" class="fill-aqua" height="60px" alt="Add"><path d="M71.13 28.87a29.88 29.88 0 1 0 0 42.26 29.86 29.86 0 0 0 0-42.26zm-18.39 37.6h-5.48V52.74H33.53v-5.48h13.73V33.53h5.48v13.73h13.73v5.48H52.74z"></path></svg>
6565
<div class="f5 charcoal">
66-
<p><strong>Drop one or more files here or click to select.</strong> Folder upload is not supported, but you may select multiple files using Ctrl+Click or Command+Click.</p>
66+
<p><strong>Drop one or more files here or click to select.</strong> Folder upload is not supported, but you may select multiple files using Ctrl+Click or Command+Click.</p>
6767
</div>
6868
</label>
6969
</div>
@@ -82,10 +82,11 @@
8282
</div>
8383
</div>
8484
</div>
85-
<div>
86-
<span v-if="cachedCode" v-on:click="resetCode" class="textLink fr pb1">Reset Code</span>
85+
<div class="mb2">
86+
<span v-if="solution" @click="viewSolution" class="ml1 fl textLink">View Solution</span>
87+
<span v-if="cachedCode" @click="resetCode" class="mr1 fr textLink">Reset Code</span>
8788
</div>
88-
<div class="bg-white flex-auto" style='height:100%;'>
89+
<div class="h-100 flex-auto bg-white">
8990
<MonacoEditor
9091
class="editor"
9192
srcPath="."
@@ -240,6 +241,7 @@ export default {
240241
concepts: self.$attrs.concepts,
241242
cachedCode: !!localStorage['cached' + self.$route.path],
242243
code: localStorage[self.cacheKey] || self.$attrs.code || self.defaultCode,
244+
solution: self.$attrs.solution,
243245
overrideErrors: self.$attrs.overrideErrors,
244246
isFileLesson: self.isFileLesson,
245247
parsedText: marked(self.$attrs.text),
@@ -368,10 +370,17 @@ export default {
368370
// this ^ triggers onCodeChange which will clear cache
369371
this.editor.setValue(this.code)
370372
this.clearPassed()
371-
if (this.output.test.log) {
373+
if (this.output.test && this.output.test.log) {
372374
delete this.output.test.log
373375
}
374376
},
377+
viewSolution: function () {
378+
// TRACK? User chose to view solution
379+
this.editor.setValue(this.$attrs.solution)
380+
if (this.output.test) {
381+
delete this.output.test
382+
}
383+
},
375384
resetFileUpload: function () {
376385
this.uploadedFiles = false
377386
this.dragging = false

src/tutorials/Basics/01.vue

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
:validate="validate"
55
:exercise="exercise"
66
:concepts="concepts"
7+
:solution="solution"
78
lessonTitle="Create a node and return a Content Identifier (CID)">
89
</Lesson>
910
</template>
@@ -36,12 +37,22 @@ const validate = async (result, ipfs) => {
3637
}
3738
}
3839
40+
const solution = `/* globals ipfs */
41+
42+
const run = async () => {
43+
let cid = await ipfs.dag.put({ test: 1 })
44+
return cid
45+
}
46+
47+
return run
48+
`
49+
3950
export default {
4051
components: {
4152
Lesson
4253
},
4354
data: () => {
44-
return { text, validate, exercise, concepts }
55+
return { text, validate, exercise, concepts, solution }
4556
}
4657
}
4758
</script>

src/tutorials/Basics/02-exercise.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
Create a named link called `bar` that points to the node we created in the first the lesson. Put it into IPFS and return its CID.
1+
Create a named link called `bar` that points to the node we created in the first lesson. Put it into IPFS and return its CID.
22

33
The editor is pre-populated with the code to create the node we're linking to.

src/tutorials/Basics/02.vue

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
:modules="modules"
77
:exercise="exercise"
88
:concepts="concepts"
9+
:solution="solution"
910
lessonTitle="Create a new node that's linked to an old one">
1011
</Lesson>
1112
</template>
@@ -17,7 +18,7 @@ import concepts from './02-concepts.md'
1718
import exercise from './02-exercise.md'
1819
import CID from 'cids'
1920
20-
let code = `/* globals ipfs */
21+
const code = `/* globals ipfs */
2122
2223
const run = async () => {
2324
let cid = await ipfs.dag.put({ test: 1 })
@@ -48,14 +49,25 @@ const validate = async (result, ipfs) => {
4849
}
4950
}
5051
52+
const solution = `/* globals ipfs */
53+
54+
const run = async () => {
55+
let cid = await ipfs.dag.put({ test: 1 })
56+
let cid2 = await ipfs.dag.put({ bar: cid })
57+
return cid2
58+
}
59+
60+
return run
61+
`
62+
5163
const modules = { cids: require('cids') }
5264
5365
export default {
5466
components: {
5567
Lesson
5668
},
5769
data: () => {
58-
return { code, text, validate, modules, exercise, concepts }
70+
return { code, text, validate, modules, exercise, concepts, solution }
5971
}
6072
}
6173
</script>

src/tutorials/Basics/03.vue

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
:validate="validate"
66
:modules="modules"
77
:exercise="exercise"
8+
:solution="solution"
89
lessonTitle="Read nested data using links">
910
</Lesson>
1011
</template>
@@ -14,12 +15,12 @@ import Lesson from '../../components/Lesson'
1415
import text from './03.md'
1516
import exercise from './03-exercise.md'
1617
17-
let code = `/* globals ipfs */
18+
const code = `/* globals ipfs */
1819
1920
const run = async () => {
2021
let cid = await ipfs.dag.put({ test: 1 })
2122
let cid2 = await ipfs.dag.put({ bar: cid })
22-
/* your code goes here */
23+
// your code goes here
2324
}
2425
2526
return run
@@ -43,14 +44,26 @@ const validate = async (result, ipfs) => {
4344
}
4445
}
4546
47+
const solution = `/* globals ipfs */
48+
49+
const run = async () => {
50+
let cid = await ipfs.dag.put({ test: 1 })
51+
let cid2 = await ipfs.dag.put({ bar: cid })
52+
let cid3 = await ipfs.dag.get(cid2, '/bar/test')
53+
return cid3.value
54+
}
55+
56+
return run
57+
`
58+
4659
const modules = { cids: require('cids') }
4760
4861
export default {
4962
components: {
5063
Lesson
5164
},
5265
data: () => {
53-
return { code, text, validate, modules, exercise }
66+
return { code, text, validate, modules, exercise, solution }
5467
}
5568
}
5669
</script>

src/tutorials/Blog/01.vue

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
:code="code"
55
:validate="validate"
66
:exercise="exercise"
7+
:solution="solution"
78
lessonTitle="Link an author to a blog post using its CID" />
89
</template>
910

@@ -31,29 +32,6 @@ const run = async () => {
3132
return run
3233
`
3334
34-
// eslint-disable-next-line no-unused-vars
35-
const _solution = `
36-
/* globals ipfs */
37-
38-
const run = async () => {
39-
const natCid = await ipfs.dag.put({ author: "Nat" })
40-
const samCid = await ipfs.dag.put({ author: "Sam" })
41-
42-
const treePostCid = await ipfs.dag.put({
43-
content: "trees",
44-
author: samCid
45-
})
46-
const computerPostCid = await ipfs.dag.put({
47-
content: "computers",
48-
author: natCid
49-
})
50-
51-
return [treePostCid, computerPostCid]
52-
}
53-
54-
return run
55-
`
56-
5735
const validate = async (result, ipfs) => {
5836
if (!result) {
5937
return { fail: 'You forgot to return a result :)' }
@@ -99,12 +77,33 @@ const validate = async (result, ipfs) => {
9977
}
10078
}
10179
80+
const solution = `/* globals ipfs */
81+
82+
const run = async () => {
83+
const natCid = await ipfs.dag.put({ author: "Nat" })
84+
const samCid = await ipfs.dag.put({ author: "Sam" })
85+
86+
const treePostCid = await ipfs.dag.put({
87+
content: "trees",
88+
author: samCid
89+
})
90+
const computerPostCid = await ipfs.dag.put({
91+
content: "computers",
92+
author: natCid
93+
})
94+
95+
return [treePostCid, computerPostCid]
96+
}
97+
98+
return run
99+
`
100+
102101
export default {
103102
components: {
104103
Lesson
105104
},
106105
data: () => {
107-
return { code, text, validate, exercise }
106+
return { code, text, validate, exercise, solution }
108107
}
109108
}
110109
</script>

src/tutorials/Blog/02-exercise.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Add a new field called `tags` whose value is an array of tags. Use the tags `outdoor` and `hobby` for the blog post about trees. The blog post about computers has only a single tag called `hobby`.
1+
To each post, add a new field called `tags` whose value is an array of tags. Use the tags `outdoor` and `hobby` for the blog post about trees. Give the blog post about computers a single tag called `hobby`.

src/tutorials/Blog/02.vue

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
:code="code"
55
:validate="validate"
66
:exercise="exercise"
7+
:solution="solution"
78
lessonTitle="Update posts with tags and watch their CIDs change" />
89
</template>
910

@@ -37,31 +38,6 @@ const run = async () => {
3738
3839
return run`
3940
40-
// eslint-disable-next-line no-unused-vars
41-
const _solution = `
42-
/* globals ipfs */
43-
44-
const run = async () => {
45-
const natCid = await ipfs.dag.put({ author: "Nat" })
46-
const samCid = await ipfs.dag.put({ author: "Sam" })
47-
48-
const treePostCid = await ipfs.dag.put({
49-
content: "trees",
50-
author: samCid,
51-
tags: ["outdoor", "hobby"]
52-
})
53-
const computerPostCid = await ipfs.dag.put({
54-
content: "computers",
55-
author: natCid,
56-
tags: ["hobby"]
57-
})
58-
59-
return [treePostCid, computerPostCid]
60-
}
61-
62-
return run
63-
`
64-
6541
const validate = async (result, ipfs) => {
6642
if (!result) {
6743
return { fail: 'You forgot to return a result :)' }
@@ -114,19 +90,43 @@ const validate = async (result, ipfs) => {
11490
// But that order really doesn't matter.
11591
return {
11692
success: 'Everything works!',
93+
logDesc: 'These are the CIDs of the blog posts. Notice how they change when the underlying data is altered.',
11794
log: {
11895
treePostCid: result[0].toBaseEncodedString(),
11996
computerPostCid: result[1].toBaseEncodedString()
12097
}
12198
}
12299
}
123100
101+
const solution = `/* globals ipfs */
102+
103+
const run = async () => {
104+
const natCid = await ipfs.dag.put({ author: "Nat" })
105+
const samCid = await ipfs.dag.put({ author: "Sam" })
106+
107+
const treePostCid = await ipfs.dag.put({
108+
content: "trees",
109+
author: samCid,
110+
tags: ["outdoor", "hobby"]
111+
})
112+
const computerPostCid = await ipfs.dag.put({
113+
content: "computers",
114+
author: natCid,
115+
tags: ["hobby"]
116+
})
117+
118+
return [treePostCid, computerPostCid]
119+
}
120+
121+
return run
122+
`
123+
124124
export default {
125125
components: {
126126
Lesson
127127
},
128128
data: () => {
129-
return { code, text, validate, exercise }
129+
return { code, text, validate, exercise, solution }
130130
}
131131
}
132132
</script>

0 commit comments

Comments
 (0)