binaryUpload: refactor to use hooks

This commit is contained in:
Peter Cai 2020-02-20 10:10:58 +08:00
parent cf095911b0
commit 8c81b9e66f
No known key found for this signature in database
GPG key ID: 71F5FB4E4F3FD54F
3 changed files with 85 additions and 118 deletions

View file

@ -1,125 +1,87 @@
import React from "react" import React, { useState, useCallback } from "react"
import Dropzone from "react-dropzone" import { useDropzone } from "react-dropzone"
import HelpButton from "./helpButton" import HelpButton from "./helpButton"
import LinkButton from "./util/linkButton" import LinkButton from "./util/linkButton"
import * as hooks from "./hooks"
import * as crypto from "../crypto" import * as crypto from "../crypto"
import * as util from "../util" import * as util from "../util"
class BinaryUpload extends React.Component export default BinaryUpload = ->
constructor: (props) -> [openDialog, renderDialog] = hooks.useDialog()
super props [encrypt, toggleEncrypt] = hooks.useToggle false
@state = [encrypting, setEncrypting] = useState false
file: null [file, setFile] = useState null
uploading: false
progress: 0
encrypt: false
encrypting: false
onDrop: (files) => # Paste hook and event
@setState clearFile = (status) ->
file: files[0] setFile null if status == 200
[doPaste, pasting, progress] = hooks.usePaste openDialog,
useCallback clearFile, []
doUpload: => # Dropzone hook and event
key = null onDrop = (files) ->
iv = null setFile files[0]
@setState {getRootProps, getInputProps, isDragActive} = useDropzone
uploading: true onDrop: useCallback onDrop, []
encrypting: @state.encrypt
progress: 0
# Due to the lack of progress feature in current Fetch API
# We have to use XHR for now. Dang.
xhr = new XMLHttpRequest()
xhr.upload.addEventListener "progress", (e) =>
if e.lengthComputable
@setState
progress: e.loaded / e.total
xhr.addEventListener "readystatechange", =>
if xhr.readyState == XMLHttpRequest.DONE
@setState
uploading: false
encrypting: false
file: null
@props.openDialog do =>
if xhr.status == 200
url = if not @state.encrypt
xhr.responseText
else
xhr.responseText + "?crypt#" + key + "+" + iv
<a href={url} target="_blank">
https://{window.location.hostname}{url}
</a>
else
xhr.responseText
if not @state.encrypt # Upload handler, we basically extend the Paste hook with encryption
xhr.open 'PUT', '/paste/' + @state.file.name doUpload = ->
xhr.send @state.file if not encrypt
doPaste file.name, file.type, file
else else
[key, iv, name, mime, encrypted] = await crypto.encryptFile @state.file # Handle encryption
xhr.open 'PUT', '/paste/' + name setEncrypting true
xhr.setRequestHeader 'content-type', mime [key, iv, name, mime, encrypted] = await crypto.encryptFile file
xhr.send encrypted setEncrypting false
@setState doPaste name, mime, encrypted, (url) ->
encrypting: false url + "?crypt#" + key + "+" + iv
doUpload = useCallback doUpload, [file, encrypt, doPaste]
progressText: -> <div className="content-pastebin">
util.progressText @state.progress {renderDialog()}
<section className="container">
toggleEncrypt: => <div {...getRootProps({className: 'dropzone'})}>
@setState (state, props) -> <input {...getInputProps()} />
{ encrypt: not state.encrypt } <p>Drag 'n' drop a file here to upload, or click to select</p>
render: ->
<div className="content-pastebin">
<Dropzone onDrop={@onDrop}>
{({getRootProps, getInputProps}) =>
<section className="container">
<div {...getRootProps({className: 'dropzone'})}>
<input {...getInputProps()} />
<p>Drag 'n' drop a file here to upload, or click to select</p>
</div>
<aside>
<p>{
if not @state.file
"No Selected File"
else
"Selected File: #{@state.file.name}"
}</p>
</aside>
</section>
}
</Dropzone>
<div className="content-buttons">
<HelpButton openDialog={@props.openDialog} />
<button
className="button-blue"
disabled={@state.uploading}
onClick={@toggleEncrypt}
>
{ "Encrypt: " + if @state.encrypt then "ON" else "OFF" }
</button>
<LinkButton
className="button-blue"
disabled={@state.uploading}
to="/paste/text"
>
Text Mode
</LinkButton>
<button
className="button-blue"
disabled={@state.uploading or not @state.file}
onClick={@doUpload}
>
{
if not @state.uploading
"Upload"
else if @state.encrypting
"Encrypting"
else
@progressText()
}
</button>
</div> </div>
<aside>
<p>{
if not file
"No Selected File"
else
"Selected File: #{file.name}"
}</p>
</aside>
</section>
<div className="content-buttons">
<HelpButton openDialog={openDialog} />
<button
className="button-blue"
disabled={pasting}
onClick={toggleEncrypt}
>
{ "Encrypt: " + if encrypt then "ON" else "OFF" }
</button>
<LinkButton
className="button-blue"
disabled={pasting}
to="/paste/text"
>
Text Mode
</LinkButton>
<button
className="button-blue"
disabled={pasting or not file}
onClick={doUpload}
>
{
if encrypting
"Encrypting"
else if not pasting
"Upload"
else
util.progressText progress
}
</button>
</div> </div>
</div>
export default BinaryUpload

View file

@ -51,11 +51,11 @@ export useDialog = ->
] ]
# Handles shared file-uploading logic between text / binary pasting # Handles shared file-uploading logic between text / binary pasting
export usePaste = (openDialog, transformUrl, callback) -> export usePaste = (openDialog, callback) ->
[pasting, setPasting] = useState false [pasting, setPasting] = useState false
[progress, setProgress] = useState 0 [progress, setProgress] = useState 0
doPaste = (name, mime, content) -> doPaste = (name, mime, content, transformUrl) ->
# Unfortunately we have to all resort to using XHR here # Unfortunately we have to all resort to using XHR here
setProgress 0 setProgress 0
setPasting true setPasting true
@ -86,7 +86,8 @@ export usePaste = (openDialog, transformUrl, callback) ->
[ [
# our paste only depends on *setting* states, no reading required # our paste only depends on *setting* states, no reading required
useCallback(doPaste, []), # but all the callback it reads from its closure may change
useCallback(doPaste, [openDialog, callback]),
pasting, pasting,
progress progress
] ]

View file

@ -1,4 +1,4 @@
import React, { useState } from "react" import React, { useState, useCallback } from "react"
import * as hooks from "./hooks" import * as hooks from "./hooks"
import HelpButton from "./helpButton" import HelpButton from "./helpButton"
import LinkButton from "./util/linkButton" import LinkButton from "./util/linkButton"
@ -8,8 +8,12 @@ export default Pastebin = ->
[openDialog, renderDialog] = hooks.useDialog() [openDialog, renderDialog] = hooks.useDialog()
[highlight, toggleHighlight] = hooks.useToggle false [highlight, toggleHighlight] = hooks.useToggle false
[text, setText] = useState "" [text, setText] = useState ""
[doPaste, pasting, _] = hooks.usePaste openDialog, null, (status) ->
# Paste hook and events
clearText = (status) ->
setText "" if status == 200 setText "" if status == 200
[doPaste, pasting, _] = hooks.usePaste openDialog,
useCallback clearText, []
onEditTextUpdate = (ev) -> onEditTextUpdate = (ev) ->
setText ev.target.value setText ev.target.value