Skip to content

Commit 1bf59c8

Browse files
Merge pull request #86 from ProtoSchool/save-state
[WIP] feat: save lesson code state, fixes #13
2 parents 5e2273b + 9183d40 commit 1bf59c8

File tree

6 files changed

+130
-19
lines changed

6 files changed

+130
-19
lines changed

src/components/Lesson.vue

Lines changed: 91 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@
1212
<div class="flex-l items-start bt border-aqua bw4">
1313
<section class="pv3 indent-1">
1414
<h1 class="f3 measure-wide">{{lessonTitle}}</h1>
15-
<span class="green"><span class="b">{{workshopShortname}}</span> | Lesson {{lessonNumber}} of {{lessonsInWorkshop}}</span>
15+
<div class="lh-solid v-mid">
16+
<span class="green v-mid"><span class="b">{{workshopShortname}}</span> | Lesson {{lessonNumber}} of {{lessonsInWorkshop}}</span>
17+
<span class="pl1"><img v-if="lessonPassed" src="./home/complete.svg" alt="complete" style="height: 1.2rem;" class="v-mid"/></span>
18+
</div>
1619
<div class="lesson-text lh-copy measure-wide" v-html="parsedText"></div>
1720
</section>
1821
<section v-if="concepts" class='dn db-ns ba border-green ph4 ml3 ml5-l mt5 mb3 mr3 measure' style="background: rgba(105, 196, 205, 10%)">
@@ -24,11 +27,19 @@
2427
<section v-bind:class="{expand: expandExercise}" class="indent-1 exercise pb4 pt3 ph3 ph4-l mb3 mr5 flex flex-column" style="background: #F6F7F9;">
2528
<div class="flex-none">
2629
<h2 class="mt0 mb2 green fw4 fill-current">
27-
<svg viewBox="0 0 12 12" width='12' xmlns="http://www.w3.org/2000/svg" style='vertical-align:-1px'>
28-
<circle cx="6" cy="6" r="6"/>
29-
</svg>
30-
<span class="green ttu f6 pl2 pr3">Exercise</span>
31-
<span class="navy fw5 f5">{{lessonTitle}}</span>
30+
<span style='vertical-align:-1px'>
31+
<img v-if="lessonPassed" src="./home/complete.svg" alt="complete" style="height: 1rem;"/>
32+
<img v-else-if="cachedCode" src="./home/in-progress.svg" alt="complete" style="height: 1rem;"/>
33+
<img v-else src="./home/not-started.svg" alt="not yet started" style="height: 1rem;"/>
34+
</span>
35+
<span class="green ttu f6 pl2 pr1 fw7 v-mid">
36+
<span v-if="lessonPassed">You did it!</span>
37+
<span v-else-if="cachedCode">Keep working.</span>
38+
<span v-else>Try it!</span>
39+
</span>
40+
<span class="green f6 fw5 v-mid">
41+
<span v-if="cachedCode && !lessonPassed">{{cachedStateMsg}}</span>
42+
</span>
3243
<div class="fr">
3344
<button
3445
v-if="expandExercise"
@@ -44,10 +55,14 @@
4455
class='b--transparent bg-transparent charcoal-muted hover-green pointer focus-outline'>
4556
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 32 32"><path d="M16 4 L28 4 L28 16 L24 12 L20 16 L16 12 L20 8z M4 16 L8 20 L12 16 L16 20 L12 24 L16 28 L4 28z"></path></svg>
4657
</button>
58+
4759
</div>
4860
</h2>
4961
<div v-if="exercise" v-html="parsedExercise" class='lh-copy'></div>
5062
</div>
63+
<div>
64+
<span v-if="cachedCode" v-on:click="resetCode" class="textLink fr pb1">Reset Code</span>
65+
</div>
5166
<div class="bg-white flex-auto" style='height:100%;'>
5267
<MonacoEditor
5368
class="editor"
@@ -63,14 +78,14 @@
6378
</div>
6479
<div class='flex-none'>
6580
<div class="pv2">
66-
<div v-if="output.test" v-bind="output.test">
81+
<div v-if="output.test && this.cachedCode" v-bind="output.test">
6782
<div class="lh-copy pv2 ph3 bg-red white" v-if="output.test.error">
6883
Error: {{output.test.error.message}}
6984
</div>
7085
<div class="lh-copy pv2 ph3 bg-red white" v-if="output.test.fail">
7186
{{output.test.fail}}
7287
</div>
73-
<div class="lh-copy pv2 ph3 bg-green white" v-if="output.test.success">
88+
<div class="lh-copy pv2 ph3 bg-green white" v-if="output.test.success && lessonPassed">
7489
{{output.test.success}}
7590
<a v-if="output.test.cid"
7691
class="link fw7 underline-hover dib ph2 mh2 white" target='explore-ipld' :href='exploreIpldUrl'>
@@ -79,14 +94,14 @@
7994
</div>
8095
</div>
8196
<div class="lh-copy pv2 ph3" v-else>
82-
Update the code to complete the exercise. Click <strong>submit</strong> to check your answer.
97+
Update the code to complete the exercise. Click <strong>submit</strong> to check your answer.
8398
</div>
8499
</div>
85100
<div class="pt3 ph2 tr">
86-
<div v-if="output.test && output.test.success && lessonNumber === lessonsInWorkshop">
101+
<div v-if="((output.test && output.test.success) || lessonPassed) && lessonNumber === lessonsInWorkshop">
87102
<Button v-bind:click="workshopMenu" class="bg-aqua white">More Workshops</Button>
88103
</div>
89-
<div v-else-if="output.test && output.test.success">
104+
<div v-else-if="lessonPassed">
90105
<Button v-bind:click="next" class="bg-aqua white">Next</Button>
91106
</div>
92107
<div v-else>
@@ -173,10 +188,15 @@ export default {
173188
text: self.$attrs.text,
174189
exercise: self.$attrs.exercise,
175190
concepts: self.$attrs.concepts,
176-
code: self.$attrs.code || defaultCode,
191+
cachedCode: !!localStorage['cached' + self.$route.path],
192+
code: localStorage[self.cacheKey] || self.$attrs.code || defaultCode,
177193
parsedText: marked(self.$attrs.text),
178194
parsedExercise: marked(self.$attrs.exercise || ''),
179195
parsedConcepts: marked(self.$attrs.concepts || ''),
196+
cacheKey: 'cached' + self.$route.path,
197+
cachedStateMsg: "",
198+
lessonKey: 'passed' + self.$route.path,
199+
lessonPassed: !!localStorage['passed' + self.$route.path],
180200
lessonTitle: self.$attrs.lessonTitle,
181201
issueUrl: `https://github.com/ipfs-shipyard/proto.school/issues/new?labels=question&title=Question+on+Lesson+${self.$route.path.slice(self.$route.path.lastIndexOf('/') + 1)}:+${self.$attrs.lessonTitle}+(${self.$route.path})&body=Have%20a%20question%20or%20suggestion%20regarding%20a%20ProtoSchool%20lesson%3F%20Please%20use%20this%0Atemplate%20to%20share%20it!%0A%0A1.%20URL%20of%20the%20lesson%20that's%20confusing%3A%0A%20https%3A%2F%2Fproto.school%2F%23${self.$route.path}%0A%0A2.%20What%27s%20confusing%20about%20this%20lesson%3F%0A%0A3.%20What%20additional%20context%20could%20we%20provide%20to%20help%20you%20succeed%3F%0A%0A4.%20What%20other%20feedback%20would%20you%20like%20to%20share%20about%20ProtoSchool%3F%0A`,
182202
output: self.output,
@@ -191,6 +211,7 @@ export default {
191211
}
192212
},
193213
computed: {
214+
194215
exploreIpldUrl: function () {
195216
let cid = this.output.test && this.output.test.cid && this.output.test.cid.toBaseEncodedString()
196217
cid = cid || ''
@@ -225,6 +246,13 @@ export default {
225246
},
226247
beforeCreate: function () {
227248
this.output = {}
249+
// doesn't work to set lessonPassed in here because it can't recognize lessonKey yet
250+
},
251+
updated: function () {
252+
// runs on page load AND every keystroke in editor AND submit
253+
},
254+
beforeUpdate: function () {
255+
// runs on every keystroke in editor, NOT on page load, NOT on code submit
228256
},
229257
methods: {
230258
run: async function () {
@@ -238,8 +266,10 @@ export default {
238266
let modules = {}
239267
if (this.$attrs.modules) modules = this.$attrs.modules
240268
let result = await _eval(code, ipfs, modules)
269+
241270
if (result && result.error) {
242271
Vue.set(output, 'test', result)
272+
this.lessonPassed = !!localStorage[this.lessonKey]
243273
return
244274
}
245275
let test = await this.$attrs.validate(result, ipfs)
@@ -250,6 +280,10 @@ export default {
250280
} else {
251281
ipfs.stop()
252282
}
283+
if (output.test.success) {
284+
localStorage[this.lessonKey] = 'passed'
285+
}
286+
this.lessonPassed = !!localStorage[this.lessonKey]
253287
},
254288
createIPFS: function () {
255289
if (this.$attrs.createIPFS) {
@@ -258,11 +292,48 @@ export default {
258292
return new IPFS({repo: Math.random().toString()})
259293
}
260294
},
295+
resetCode: function () {
296+
// TRACK? User chose to reset code
297+
this.code = this.$attrs.code || defaultCode
298+
// this ^ triggers onCodeChange which will clear cache
299+
this.editor.setValue(this.code)
300+
this.clearPassed()
301+
},
302+
clearPassed: function () {
303+
delete localStorage[this.lessonKey]
304+
this.lessonPassed = !!localStorage[this.lessonKey]
305+
},
306+
loadCodeFromCache: function() {
307+
this.code = localStorage[this.cacheKey]
308+
this.editor.setValue(this.code)
309+
},
261310
onMounted: function (editor) {
311+
// runs on page load, NOT on every keystroke in editor
262312
this.editor = editor
313+
if (this.cachedCode) {
314+
// TRACK? returned to lesson previously visited
315+
this.loadCodeFromCache()
316+
this.cachedStateMsg = "Pick up where you left off. We've saved your code for you!"
317+
if (this.lessonPassed) {
318+
this.run()
319+
}
320+
} else {
321+
// TRACK? first time starting lesson
322+
}
263323
},
264-
onCodeChange: function (editor) {
265-
// console.log(editor.getValue())
324+
onCodeChange: function () {
325+
if (this.editor.getValue() === (this.$attrs.code || defaultCode) ) {
326+
// TRACK? edited back to default state by chance or by 'reset code'
327+
delete localStorage[this.cacheKey]
328+
this.cachedCode = !!localStorage[this.cacheKey]
329+
} else if (this.code === this.editor.getValue()) {
330+
//TRACK? returned to cached lesson in progress
331+
} else {
332+
localStorage[this.cacheKey] = this.editor.getValue()
333+
this.code = this.editor.getValue()
334+
this.cachedCode = !!localStorage[this.cacheKey]
335+
this.cachedStateMsg = "We're saving your code as you go."
336+
}
266337
},
267338
next: function () {
268339
Vue.set(this.output, 'test', null)
@@ -307,6 +378,12 @@ export default {
307378
.mw-900 {
308379
max-width: 900px;
309380
}
381+
span.textLink {
382+
color: blue;
383+
cursor: pointer;
384+
text-decoration: underline;
385+
}
386+
310387
@media screen and (min-width: 60rem) {
311388
.indent-1 {
312389
margin-left: 93px;

src/components/home/ExerciseLink.vue

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
<template>
22
<router-link :to="to" class="link db pa3 bb b--white green hover-bg-washed-yellow">
33
<div class="flex">
4-
<div class="green ttu f6 pr3" style="min-width: 83px">Lesson {{index}}</div>
4+
<div class="green ttu f6" style="min-width: 72px">Lesson {{index}}</div>
5+
<div class="pr2">
6+
<img v-if="lessonPassed('passed' + to)" src="./complete.svg" alt="complete" style="height: 1rem;"/>
7+
<img v-else-if="lessonCached('cached' + to)" src="./in-progress.svg" alt="complete" style="height: 1rem;"/>
8+
<img v-else src="./not-started.svg" alt="not yet started" style="height: 1rem;"/>
9+
</div>
510
<div class="navy fw5 mw6">{{name}}</div>
611
</div>
712
</router-link>
@@ -14,6 +19,14 @@ export default {
1419
'to',
1520
'index',
1621
'name'
17-
]
22+
],
23+
methods: {
24+
lessonPassed: function(lessonKey) {
25+
return !!localStorage[lessonKey]
26+
},
27+
lessonCached: function(cacheKey) {
28+
return !!localStorage[cacheKey]
29+
},
30+
}
1831
}
1932
</script>

src/components/home/Home.vue

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,6 @@
2626
</p>
2727
</section>
2828
<section class="db bt border-aqua bw4 relative">
29-
<label class="absolute ttu f6 fw5 white" style="top: -15px; left: 91px;">
30-
TOPICS
31-
</label>
3229
<div class="flex items-start pv4">
3330
<div class="section-1 flex-none tc">
3431
<h1 class="ma0 f3 fw6 pb2">IPFS</h1>
@@ -106,6 +103,27 @@ export default {
106103
name: 'home',
107104
components: {
108105
ExerciseLink
106+
},
107+
data: self => {
108+
return {
109+
firstVisit: true
110+
}
111+
},
112+
mounted: function () {
113+
this.checkFirstVisit()
114+
},
115+
methods: {
116+
checkFirstVisit: function () {
117+
for (let key of Object.keys(localStorage)) {
118+
if (key.startsWith('passed') || key.startsWith('cached')) {
119+
// TRACK? return visit
120+
this.firstVisit = false
121+
return
122+
}
123+
}

124+
// TRACK? first site visit
125+
this.firstVisit = true
126+
}
109127
}
110128
}
111129
</script>

src/components/home/complete.svg

Lines changed: 1 addition & 0 deletions
Loading

src/components/home/in-progress.svg

Lines changed: 1 addition & 0 deletions
Loading

src/components/home/not-started.svg

Lines changed: 1 addition & 0 deletions
Loading

0 commit comments

Comments
 (0)