Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

file upload functionality and error hijacking #111

Merged
merged 48 commits into from
Apr 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
cc3d9b4
Initial work on file upload lesson.
mikeal Jan 3, 2019
117c701
Fix linter error
terichadbourne Jan 29, 2019
cb0f3af
Merge branch 'code' into file-upload
terichadbourne Jan 29, 2019
d8fa09a
Remove unused properties
terichadbourne Feb 12, 2019
7956414
Add dummy file tutorial lesson
terichadbourne Feb 12, 2019
14c2cb8
Offer multiple boilerplates & update instructions
terichadbourne Feb 12, 2019
627eb39
Update UI for file upload
terichadbourne Feb 13, 2019
5cb9e04
Remove "Step 2" header from non-file exercise box
terichadbourne Feb 13, 2019
b934a1d
Remove unused console logs
terichadbourne Feb 13, 2019
6c50a79
Add file tutorial to homepage and adjust titles
terichadbourne Feb 13, 2019
2cc679f
Appease linter
terichadbourne Feb 13, 2019
f1f16d6
Beautify upload formatting
terichadbourne Feb 13, 2019
9bc44ad
Repair file upload and code submission
terichadbourne Feb 13, 2019
f0d5b70
Change icon and fix formatting for uploaded files
terichadbourne Feb 15, 2019
be14009
Disable submit button in file lesson until upload
terichadbourne Mar 5, 2019
28d4039
Disallow folder upload
terichadbourne Mar 5, 2019
7204e1a
Add 2nd file lesson
terichadbourne Mar 5, 2019
d768208
Improve formatting on disabled button hover
terichadbourne Mar 6, 2019
2c78e7f
Complete files lesson 1 content
terichadbourne Mar 6, 2019
59329d7
Update 2nd file lesson (WIP)
terichadbourne Mar 6, 2019
337e123
More validation flailing
terichadbourne Mar 6, 2019
9071b2c
Update package-lock.json
terichadbourne Mar 7, 2019
4d350c7
Merge branch 'code' into file-upload
terichadbourne Mar 7, 2019
e72a8cf
Restructure initial lesson content
terichadbourne Apr 1, 2019
bb550f5
Merge branch 'code' into file-upload
terichadbourne Apr 1, 2019
190f1eb
Remove old console logs
terichadbourne Apr 1, 2019
64e3626
Remove `code` and `validate` from text lessons
terichadbourne Apr 1, 2019
2d1d381
Merge branch 'code' into file-upload
terichadbourne Apr 1, 2019
ae26690
Update dependencies to fix IPFS import
terichadbourne Apr 1, 2019
8cb8e2b
Fix console logs for file basics lesson 2
terichadbourne Apr 1, 2019
e7405e5
Add a lesson #4 on MFS `write` method
terichadbourne Apr 1, 2019
26d5466
Rename `lessons` dir, create `boilerplates` dir
terichadbourne Apr 2, 2019
8beaa63
Change /lessons references to /tutorials
terichadbourne Apr 2, 2019
ef4a779
Fix validation for MFS Lesson 4
terichadbourne Apr 3, 2019
2b35f36
Rename file tutorial to be about MFS
terichadbourne Apr 3, 2019
3ddc148
Add lesson on `files.ls`
terichadbourne Apr 3, 2019
40cb898
Update validation error message
terichadbourne Apr 3, 2019
3244aa0
chore: sync with code branch
fsdiogo Apr 4, 2019
0f3f156
chore: boilerplates tidy up and path fix
fsdiogo Apr 4, 2019
b94d668
fix: expected result of Lesson 3
fsdiogo Apr 5, 2019
e833e79
chore: some rephrasing to the first mfs lessons
fsdiogo Apr 5, 2019
215e98b
Add lesson on files.mkdir
terichadbourne Apr 9, 2019
711cae4
Sync with code branch
fsdiogo Apr 10, 2019
6a2f2ee
feat: add the ability to override external errors
fsdiogo Apr 11, 2019
1e250f6
Merge branch 'code' into file-upload
fsdiogo Apr 16, 2019
68e8e36
chore: add instructions for overriding errors
fsdiogo Apr 16, 2019
c5aee7e
chore: hide MFS lesson
fsdiogo Apr 16, 2019
ad72e2b
Clarify instructions for adding error override
terichadbourne Apr 17, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ cache:
before_script:
npm install
script:
npm test
Copy link
Collaborator

@olizilla olizilla Apr 18, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fsdiogo needs more hyphens:

script:
  - npm test
  - npm run build

npm run build
notifications:
email: false
Expand Down
17 changes: 12 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,23 @@ Example (while in `tutorials`):

#### Vue file

Select the boilerplate Vue file for your lesson from the `tutorials` directory.
Select the appropriate boilerplate Vue file for your lesson from the `tutorials/boilerplates` directory:

- `boilerplate-standard.vue` for a lesson with an exercise which does not require a file upload
- `boilerplate-file-upload.vue` for a lesson with an exercise that requires a file upload
- `boilerplate-no-exercise.vue` for a text-only lesson

Copy that boilerplate into your tutorial folder and rename it to the 2-digit number of the lesson.

Example:
Example (while in `tutorials`):

```sh
> cp tutorials/boilerplate.vue tutorials/Tutorial-Shortname/01.vue
> cp boilerplate/boilerplate-standard.vue Tutorial-Shortname/01.vue
```

Replace anything in the boilerplate file that reads "REPLACEME".


#### Markdown file

Create a `.md` file alongside your `.vue` and add the markdown formatted text
Expand Down Expand Up @@ -100,7 +105,7 @@ lessons in your tutorial.

For example, if you add 3 lessons with the following routes:

```
```js
{ path: '/basics/01', component: LessonBasics01 },
{ path: '/basics/02', component: LessonBasics02 },
{ path: '/basics/03', component: LessonBasics03 },
Expand All @@ -111,7 +116,7 @@ your second lesson will display the following under the lesson title:

If you add 5 lesssons with the following routes:

```
```js
{ path: '/data-structures/01', component: LessonDataStructures01 },
{ path: '/data-structures/02', component: LessonDataStructures02 },
{ path: '/data-structures/03', component: LessonDataStructures03 },
Expand All @@ -125,6 +130,7 @@ your third lesson will display the following under the lesson title:

Notice how multi-word lesson shortnames are treated here. In filepaths, they are lowercase and hyphenated (e.g. `/data-structures/01`). In component names they are upper camel case (smushed together with the first letter of each word capitalized, e.g. `LessonDataStructures01`).


### Add your tutorial to `tutorials.json` and `courses.json`

In `static/tutorials.json`, add a new key for your tutorial (for example, `tutorialShortname` as shown in the example below) and fill in the appropriate values:
Expand Down Expand Up @@ -162,6 +168,7 @@ In `static/courses.json`, add the tutorial key to the `all` array so it will app

The project maintainers will take care of making any updates needed to ensure your project is featured in any relevant course listings.


## Boilerplate Explained

```javascript
Expand Down
4,351 changes: 2,224 additions & 2,127 deletions package-lock.json

Large diffs are not rendered by default.

52 changes: 52 additions & 0 deletions src/components/File-Lesson.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<script>
import Lesson from './Lesson.vue'

const defaultCode = `/* globals ipfs */

const run = async (files) => {
// your code goes here!
// be sure this function returns the requested value
}

return run

`

export default {
extends: Lesson,
beforeCreate: function () {
this.isFileLesson = true
this.defaultCode = defaultCode
},
methods: {
onFileDrop: function (event) {
event.preventDefault()
event.stopPropagation()
let files = Array.from(event.dataTransfer.files)
for (let f of Array.from(event.dataTransfer.items)) {
let isFile = f.getAsEntry ? f.getAsEntry().isFile : (f.webkitGetAsEntry ? f.webkitGetAsEntry().isFile : true)
if (!isFile) {
return alert("Folder upload is not supported. Please select a file or multiple files.")
}
}
this.onFiles(files)
return false
},
onFileClick: function (event) {
event.preventDefault()
event.stopPropagation()
let elem = document.createElement('input')
elem.setAttribute("type", "file")
elem.setAttribute('multiple', true)
elem.onchange = () => {
this.onFiles(Array.from(elem.files))
}
elem.click()
},
onFiles: function (files) {
this.uploadedFiles = files
window.uploadedFiles = files
}
}
}
</script>
101 changes: 87 additions & 14 deletions src/components/Lesson.vue
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,36 @@
</div>
</h2>
<div v-if="exercise" v-html="parsedExercise" class='lh-copy'></div>
<div v-if="isFileLesson">
<div class="f5 fw7 mt4 mb2"> Step 1: Upload file(s)
<span class="pl1"><img v-if="uploadedFiles" src="../static/images/complete.svg" alt="complete" style="height: 1.2rem;" class="v-mid"/></span>
</div>
<div id="drop-area" v-if="!uploadedFiles" v-on:drop="onFileDrop"
v-on:click="onFileClick"
@dragenter="dragging=true" @dragend="dragging=false" @dragleave="dragging=false"
@dragover.prevent v-bind:class="{dragging: dragging}" class="dropfile mb2 pa2 w-100 br3 shadow-4 bg-white color-navy">
<div class="o-80 glow">
<label for="add-files" class="flex items-center h4 pointer">
<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>
<div class="f5 charcoal">
<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>
</div>
</label>
</div>
</div>
<div v-else class="mt2">
<span v-on:click="resetFileUpload" class="textLink fr pb1">Start Over</span>
<div class="mb2 pl3 pa2 w-100 br3 h4 shadow-4 bg-white color-navy flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" class="fill-aqua" height="60px"><path d="M55.94 19.17H30a4 4 0 0 0-4 4v53.65a4 4 0 0 0 4 4h40.1a4 4 0 0 0 4-4V38.06zm5.28 21.08c-4.33 0-7.47-2.85-7.47-6.77V21l18.13 19.25z"/></svg>
<ul class="list pl0">
<li v-for="(file, idx) in uploadedFiles" :key="`file-${idx}`">{{file.name}}</li>
</ul>
</div>
</div>
<div class="f5 fw7 mt4 mb2">Step 2: Update code
<span class="pl1"><img v-if="cachedCode" src="../static/images/complete.svg" alt="complete" style="height: 1.2rem;" class="v-mid"/></span>
</div>
</div>
</div>
<div>
<span v-if="cachedCode" v-on:click="resetCode" class="textLink fr pb1">Reset Code</span>
Expand Down Expand Up @@ -86,7 +116,12 @@
</div>
</div>
<div class="lh-copy pv2 ph3" v-else>
Update the code to complete the exercise. Click <strong>submit</strong> to check your answer.
<div v-if="isFileLesson">
Upload file(s) and update the code to complete the exercise. Click <strong>Submit</strong> to check your answer.
</div>
<div v-else>
Update the code to complete the exercise. Click <strong>Submit</strong> to check your answer.
</div>
</div>
</div>
<div class="pt3 ph2 tr">
Expand All @@ -97,7 +132,11 @@
<Button v-bind:click="next" class="bg-aqua white">Next</Button>
</div>
<div v-else>
<Button v-bind:click="run" class="bg-aqua white">Submit</Button>
<span v-if="isFileLesson && !uploadedFiles" class="disabledButtonWrapper"><Button v-bind:click="next" class="bg-aqua white" disabled>Submit</Button></span>
<Button v-else v-bind:click="run" class="bg-aqua white">Submit</Button>
<div v-if="isFileLesson && !uploadedFiles" class="red lh-copy pt2 o-0">
You must upload a file before submitting.
</div>
</div>
</div>
</div>
Expand Down Expand Up @@ -129,8 +168,8 @@ import MonacoEditor from 'vue-monaco-editor'
import Explorer from './Explorer.vue'
import Button from './Button.vue'
import Header from './Header.vue'
const CID = require('cids')
const marked = require('marked')
import CID from 'cids'
import marked from 'marked'

const hljs = require('highlight.js/lib/highlight.js')
hljs.registerLanguage('js', require('highlight.js/lib/languages/javascript'))
Expand All @@ -143,7 +182,7 @@ marked.setOptions({
}
})

const _eval = async (text, ipfs, modules = {}) => {
const _eval = async (text, ipfs, modules = {}, args = []) => {
await new Promise(resolve => ipfs.on('ready', resolve))

let fn
Expand All @@ -161,7 +200,7 @@ const _eval = async (text, ipfs, modules = {}) => {
return modules[name]
}
try {
result = await fn(ipfs, require)()
result = await fn(ipfs, require)(...args)
} catch (e) {
result = {error: e}
}
Expand Down Expand Up @@ -193,7 +232,9 @@ export default {
exercise: self.$attrs.exercise,
concepts: self.$attrs.concepts,
cachedCode: !!localStorage['cached' + self.$route.path],
code: localStorage[self.cacheKey] || self.$attrs.code || defaultCode,
code: localStorage[self.cacheKey] || self.$attrs.code || self.defaultCode,
overrideErrors: self.$attrs.overrideErrors,
isFileLesson: self.isFileLesson,
parsedText: marked(self.$attrs.text),
parsedExercise: marked(self.$attrs.exercise || ''),
parsedConcepts: marked(self.$attrs.concepts || ''),
Expand All @@ -204,6 +245,8 @@ export default {
lessonTitle: self.$attrs.lessonTitle,
output: self.output,
expandExercise: false,
dragging: false,
uploadedFiles: window.uploadedFiles || false,
options: {
selectOnLineNumbers: false,
lineNumbersMinChars: 3,
Expand Down Expand Up @@ -259,7 +302,8 @@ export default {
},
beforeCreate: function () {
this.output = {}
this.IPFSPromise = import('ipfs')
this.defaultCode = defaultCode
this.IPFSPromise = import('ipfs').then(m => m.default)
// doesn't work to set lessonPassed in here because it can't recognize lessonKey yet
},
updated: function () {
Expand All @@ -269,7 +313,7 @@ export default {
// runs on every keystroke in editor, NOT on page load, NOT on code submit
},
methods: {
run: async function () {
run: async function (...args) {
if (oldIPFS) {
oldIPFS.stop()
oldIPFS = null
Expand All @@ -279,14 +323,16 @@ export default {
let code = this.editor.getValue()
let modules = {}
if (this.$attrs.modules) modules = this.$attrs.modules
let result = await _eval(code, ipfs, modules)

if (result && result.error) {
if (this.isFileLesson) args.unshift(this.uploadedFiles)
// Output external errors or not depending on flag
let result = await _eval(code, ipfs, modules, args)
if (!this.$attrs.overrideErrors && result && result.error) {
Vue.set(output, 'test', result)
this.lessonPassed = !!localStorage[this.lessonKey]
return
}
let test = await this.$attrs.validate(result, ipfs)
// Run the `validate` function in the lesson
let test = await this.$attrs.validate(result, ipfs, args)
Vue.set(output, 'test', test)
if (CID.isCID(result)) {
oldIPFS = ipfs
Expand All @@ -304,7 +350,7 @@ export default {
return this.$attrs.createIPFS()
} else {
let ipfs = this.IPFSPromise.then(IPFS => {
return IPFS.createNode({repo: Math.random().toString()})
return new IPFS({repo: Math.random().toString()})
})
return ipfs
}
Expand All @@ -316,6 +362,11 @@ export default {
this.editor.setValue(this.code)
this.clearPassed()
},
resetFileUpload: function () {
this.uploadedFiles = false
this.dragging = false
console.log({uploadedFiles: this.uploadedFiles})
},
clearPassed: function () {
delete localStorage[this.lessonKey]
this.lessonPassed = !!localStorage[this.lessonKey]
Expand Down Expand Up @@ -380,6 +431,18 @@ export default {
</script>

<style scoped>

button:disabled {
cursor: not-allowed;
}

.disabledButtonWrapper:hover + div {
opacity: 1;
transition: opacity .2s ease-in;
}
.dragging {
border: 5px solid #69c4cd;
}
.editor {
height: 100%;
min-height: 15rem;
Expand Down Expand Up @@ -420,4 +483,14 @@ footer a {
margin-left: 93px;
}
}

div.dropfile {
cursor: pointer;
}
div.dropfile input {
display: none;
}
div#drop-area * {
pointer-events: none;
}
</style>
4 changes: 2 additions & 2 deletions src/components/Navigation.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
</div>
<!-- standard nav -->
<div v-else class="dn flex overflow-auto items-center bg-aqua white pv3 center tc mw7">
<div v-for="link in links">
<div v-for="(link, idx) in links" :key="`desktop-${idx}`">
<router-link :class="[isActive(link) ? 'white' : 'navy ', 'nav-link']" :to="`${link.path}`">{{link.text}}</router-link>
</div>
</div>
Expand All @@ -37,7 +37,7 @@
<!-- hamburger displayed when requested -->
<div :class="{ dn: isHamburgerClosed }">
<div class="tc bg-aqua-muted white">
<div v-for="link in links">
<div v-for="(link, idx) in links" :key="`mobile-${idx}`">
<router-link @click.native="toggleHamburger" :class="[isActive(link) || isActiveLesson(link) ? 'white' : 'navy', 'link pa3 fw5 f4 db bb border-aqua']" :to="`${link.path}`">{{link.text}}</router-link>
</div>
</div>
Expand Down
12 changes: 12 additions & 0 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ import LessonDataStructures02 from './tutorials/Data-Structures/02.vue'
import LessonDataStructures03 from './tutorials/Data-Structures/03.vue'
import LessonDataStructures04 from './tutorials/Data-Structures/04.vue'
import LessonDataStructures05 from './tutorials/Data-Structures/05.vue'
// import MutableFileSystem01 from './tutorials/Mutable-File-System/01.vue'
// import MutableFileSystem02 from './tutorials/Mutable-File-System/02.vue'
// import MutableFileSystem03 from './tutorials/Mutable-File-System/03.vue'
// import MutableFileSystem04 from './tutorials/Mutable-File-System/04.vue'
// import MutableFileSystem05 from './tutorials/Mutable-File-System/05.vue'

Vue.use(VueRouter)

Expand Down Expand Up @@ -61,6 +66,13 @@ const routes = [
{ path: '/blog/05', component: LessonBlog05 },
{ path: '/blog/06', component: LessonBlog06 },
{ path: '/blog/07', component: LessonBlog07 },
// Lessons - MFS
// { path: '/mutable-file-system', component: Landing, props: { tutorialId: 'mutableFileSystem' } },
// { path: '/mutable-file-system/01', component: MutableFileSystem01 },
// { path: '/mutable-file-system/02', component: MutableFileSystem02 },
// { path: '/mutable-file-system/03', component: MutableFileSystem03 },
// { path: '/mutable-file-system/04', component: MutableFileSystem04 },
// { path: '/mutable-file-system/05', component: MutableFileSystem05 },
// 404
{ path: '*', name: '404' }
]
Expand Down
2 changes: 1 addition & 1 deletion src/static/courses.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"all": ["dataStructures", "basics", "blog"],
"featured": ["dataStructures", "basics", "blog"]
}
}
13 changes: 13 additions & 0 deletions src/static/images/glyph_document.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading