Skip to content
This repository was archived by the owner on Jul 12, 2023. It is now read-only.

Commit e889648

Browse files
committed
Add media upload
1 parent 353d2e9 commit e889648

File tree

7 files changed

+270
-8
lines changed

7 files changed

+270
-8
lines changed

demo.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
</head>
2626
<body>
2727

28-
<micro-panel-editor hidden micropub="/fake-micropub"></micro-panel-editor>
28+
<micro-panel-editor hidden micropub="/fake-micropub" media="/fake-media"></micro-panel-editor>
2929

3030
<micro-panel-toolbar></micro-panel-toolbar>
3131

devserver.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const fs = require('fs')
22
const polka = require('polka')
3+
const Busboy = require('busboy')
34
const esModuleDevserver = require('es-module-devserver')
45

56
polka()
@@ -29,5 +30,21 @@ Look, the post source is **different**! This is a *demo*, so the micropub endpoi
2930
res.statusCode = 201
3031
res.end()
3132
})
33+
.post('/fake-media', (req, res) => {
34+
let filename = null
35+
const bb = new Busboy({ headers: req.headers })
36+
bb.on('file', (_fld, file, fname, _enc, _mt) => {
37+
file.on('data', _ => {}) // needs these
38+
file.on('end', () => {})
39+
filename = fname
40+
})
41+
bb.on('finish', () => {
42+
console.log(filename)
43+
res.setHeader('Location', `https://example.com/fake/media/${filename}`)
44+
res.statusCode = 201
45+
res.end()
46+
})
47+
setTimeout(() => req.pipe(bb), 500) // simulate processing time
48+
})
3249
.listen(3003)
3350
.then(_ => console.log('Running on localhost:3003'))

package-lock.json

Lines changed: 62 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"homepage": "https://github.com/myfreeweb/micro-panel#readme",
2828
"devDependencies": {
2929
"@webcomponents/webcomponentsjs": "^2.0.2",
30+
"busboy": "^0.2.14",
3031
"es-module-devserver": "^0.1.2",
3132
"polka": "^0.4.0",
3233
"rollup": "^0.62.0",

src/micro-panel-editor-entry.js

Lines changed: 139 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,28 @@ import 'codeflask-element'
22
import 'prismjs/components/prism-markdown.min.js'
33
import 'prismjs/components/prism-json.min.js'
44
import { LitElement, html } from '@polymer/lit-element'
5-
import { sharedStyles, icons, iconCode } from './util.js'
5+
import { upload, sharedStyles, icons, iconCode } from './util.js'
66
import produce from 'immer'
77

88
export default class MicroPanelEditorEntry extends LitElement {
99
static get properties () {
1010
return {
11-
entry: Object, setEntry: Function, openJsonEditors: Object, jsonParseError: Object
11+
entry: Object, setEntry: Function,
12+
openUploaders: Object, uploadQueues: Object,
13+
openJsonEditors: Object, jsonParseError: Object,
14+
media: /* endpoint */ String, mediatoken: String,
1215
}
1316
}
1417

1518
constructor () {
1619
super()
20+
this.openUploaders = {}
21+
this.uploadQueues = {}
1722
this.openJsonEditors = {}
1823
this.jsonParseError = {}
1924
}
2025

21-
_render ({ entry, openJsonEditors, jsonParseError }) {
26+
_render ({ entry, openUploaders, uploadQueues, openJsonEditors, jsonParseError, media, mediatoken }) {
2227
return html`
2328
${sharedStyles}
2429
<style>
@@ -66,6 +71,32 @@ export default class MicroPanelEditorEntry extends LitElement {
6671
padding: 0.5rem;
6772
}
6873
74+
#upload-zone {
75+
position: relative;
76+
padding: 0.5rem;
77+
}
78+
.drag-overlay {
79+
display: none;
80+
}
81+
.dragging .drag-overlay {
82+
display: flex;
83+
align-items: center;
84+
justify-content: center;
85+
text-align: center;
86+
position: absolute;
87+
top: 0;
88+
right: 0;
89+
bottom: 0;
90+
left: 0;
91+
background: rgba(60, 60, 60, 0.8);
92+
color: #fff;
93+
}
94+
progress {
95+
display: block;
96+
width: 100%;
97+
margin: 0.1rem 0 0.4rem;
98+
}
99+
69100
@media screen and (min-width: 700px) {
70101
fieldset { width: 70%; }
71102
}
@@ -88,6 +119,12 @@ export default class MicroPanelEditorEntry extends LitElement {
88119
this.openJsonEditors = produce(openJsonEditors, x => { x[propname] = !(x[propname] || false) })
89120
this.jsonParseError = produce(jsonParseError, pes => { pes[propname] = null })
90121
}} title="Edit this property as JSON" class="icon-button">${iconCode(icons.json)}</button>
122+
${media && !openUploaders[propname] ? html`
123+
<button on-click=${_ => {
124+
this.uploadQueues = produce(uploadQueues, x => { x[propname] = [] })
125+
this.openUploaders = produce(openUploaders, x => { x[propname] = true })
126+
}} title="Upload media files" class="icon-button">${iconCode(icons.cloudUpload)}</button>
127+
` : ''}
91128
<button on-click=${_ =>
92129
this._modify(entry, draft => draft.properties[propname].push(''))
93130
} title="Add new value to this property" class="icon-button">${iconCode(icons.plus)}</button>
@@ -101,6 +138,7 @@ export default class MicroPanelEditorEntry extends LitElement {
101138
} title="Delete this value" class="icon-button">${iconCode(icons.minus)}</button>
102139
</div>
103140
`))}
141+
${openUploaders[propname] ? this._mediaUploader(entry, propname, media, mediatoken, uploadQueues) : ''}
104142
</fieldset>
105143
`)}
106144
@@ -161,6 +199,104 @@ export default class MicroPanelEditorEntry extends LitElement {
161199
`
162200
}
163201

202+
_mediaUploader (entry, propname, media, mediatoken, uploadQueues) {
203+
return html`
204+
<div id="upload-zone"
205+
on-dragenter=${e => {
206+
e.stopPropagation()
207+
e.preventDefault()
208+
console.log(this)
209+
if (this.dragFirst) {
210+
this.dragSecond = true
211+
} else {
212+
this.dragFirst = true
213+
e.dataTransfer.dropEffect = 'copy'
214+
this.shadowRoot.getElementById('upload-zone').classList.add('dragging')
215+
}
216+
}}
217+
on-dragover=${e => {
218+
e.stopPropagation()
219+
e.preventDefault()
220+
}}
221+
on-dragleave=${e => {
222+
e.stopPropagation()
223+
e.preventDefault()
224+
if (this.dragSecond) {
225+
this.dragSecond = false
226+
} else {
227+
this.dragFirst = false
228+
}
229+
if (!this.dragFirst && !this.dragSecond) {
230+
this.shadowRoot.getElementById('upload-zone').classList.remove('dragging')
231+
}
232+
}}
233+
on-drop=${e => {
234+
e.stopPropagation()
235+
e.preventDefault()
236+
this.dragFirst = false
237+
this.dragSecond = false
238+
this.shadowRoot.getElementById('upload-zone').classList.remove('dragging')
239+
this.uploadQueues = produce(uploadQueues, x => {
240+
for (const file of e.dataTransfer.files) {
241+
x[propname].push({ file })
242+
console.log(file)
243+
}
244+
})
245+
}}>
246+
Drag'n'drop or select <input type="file" multiple on-change=${e => {
247+
this.uploadQueues = produce(uploadQueues, x => {
248+
for (const file of e.target.files) {
249+
x[propname].push({ file })
250+
console.log(file)
251+
}
252+
})
253+
}}>
254+
to upload.
255+
${uploadQueues[propname].length > 0 ? html`
256+
<div class="upload-queue">
257+
${uploadQueues[propname].map(({ file, progress }, idx) => html`
258+
<div class="upload-queue-file bar">
259+
<div class="stretchy">
260+
<div>${file.name}</div>
261+
${progress ? html`<progress max="100" value=${progress}>${progress}%</progress>` : ''}
262+
</div>
263+
<button on-click=${_ =>
264+
this.uploadQueues = produce(uploadQueues, x => { x[propname].splice(idx, 1) })
265+
} title="Delete this file from the queue" class="icon-button">${iconCode(icons.minus)}</button>
266+
</div>
267+
`)}
268+
</div>
269+
<button on-click=${async e => {
270+
for (const [idx, wrapper] of uploadQueues[propname].entries()) {
271+
try {
272+
const result = await upload(media, mediatoken, wrapper.file, e =>
273+
this.uploadQueues = produce(this.uploadQueues, x => {
274+
const idxx = x[propname].findIndex(y => y.file === wrapper.file)
275+
if (e.lengthComputable) {
276+
x[propname][idxx].progress = e.loaded / e.total * 100
277+
} else {
278+
x[propname][idxx].progress = 'ind'
279+
}
280+
}))
281+
this._modify(this.entry, draft => {
282+
draft.properties[propname].push(result)
283+
})
284+
this.uploadQueues = produce(this.uploadQueues, x => {
285+
x[propname].splice(x[propname].findIndex(y => y.file === wrapper.file), 1)
286+
})
287+
} catch (e) {
288+
alert(e)
289+
}
290+
}
291+
}}>Upload!</button>
292+
` : ''}
293+
<div class="drag-overlay">
294+
Drop files here!
295+
</div>
296+
</div>
297+
`
298+
}
299+
164300
addNewProp (e, entry) {
165301
if ('key' in e && e.key !== 'Enter') {
166302
return

src/micro-panel-editor.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,13 @@ function micropubPost(endpoint, obj) {
2626
export default class MicroPanelEditor extends LitElement {
2727
static get properties () {
2828
return {
29-
micropub: String, entry: Object, entryIsModified: Boolean, requestInFlight: Boolean,
29+
micropub: String, media: String, mediatoken: String,
30+
entry: Object,
31+
entryIsModified: Boolean, requestInFlight: Boolean,
3032
}
3133
}
3234

33-
_render ({ micropub, entry, entryIsModified }) {
35+
_render ({ micropub, media, mediatoken, entry, entryIsModified }) {
3436
return html`
3537
${sharedStyles}
3638
<style>
@@ -63,7 +65,9 @@ export default class MicroPanelEditor extends LitElement {
6365
`}
6466
</header>
6567
66-
<micro-panel-editor-entry id="root-editor" entry=${entry}
68+
<micro-panel-editor-entry id="root-editor"
69+
media=${media} mediatoken=${mediatoken}
70+
entry=${entry}
6771
setEntry=${entry => {
6872
this.entry = entry
6973
this.entryIsModified = true

0 commit comments

Comments
 (0)