From b58cf4c29b903bc4db172a6f93ce67ef2848f7f9 Mon Sep 17 00:00:00 2001 From: Stan Bondi Date: Wed, 26 Jan 2022 07:59:25 +0200 Subject: [PATCH] feat: show error if IPFS upload fails (#3746) Description --- - show error for failed file upload Motivation and Context --- File used to appear as if it was uploading forever, now an error appears How Has This Been Tested? --- Manually ![image](https://user-images.githubusercontent.com/1057902/150973276-a9b69c17-199b-482f-9e4d-4fe287b6d20f.png) --- .../tari_collectibles/web-app/.prettierrc | 7 + .../tari_collectibles/web-app/src/Create.js | 183 ++++++++++-------- 2 files changed, 112 insertions(+), 78 deletions(-) create mode 100644 applications/tari_collectibles/web-app/.prettierrc diff --git a/applications/tari_collectibles/web-app/.prettierrc b/applications/tari_collectibles/web-app/.prettierrc new file mode 100644 index 0000000000..9d3a59cc88 --- /dev/null +++ b/applications/tari_collectibles/web-app/.prettierrc @@ -0,0 +1,7 @@ +{ + "endOfLine": "auto", + "trailingComma": "all", + "printWidth": 100, + "tabWidth": 4, + "bracketSpacing": false +} diff --git a/applications/tari_collectibles/web-app/src/Create.js b/applications/tari_collectibles/web-app/src/Create.js index c4c392d52f..e643f6be69 100644 --- a/applications/tari_collectibles/web-app/src/Create.js +++ b/applications/tari_collectibles/web-app/src/Create.js @@ -52,6 +52,7 @@ class Create extends React.Component { image: "", cid: "", error: "", + ipfsUploadError: null, isSaving: false, tip001: true, tip002: false, @@ -73,17 +74,21 @@ class Create extends React.Component { } componentDidMount() { - this.cleanup = appWindow.listen("tauri://file-drop", this.dropFile); + this.cleanup = appWindow.listen("tauri://file-drop", (obj) => this.dropFile(obj)); console.log("didmount"); - } - dropFile = async ({payload}) => { + async dropFile({payload}) { if (payload.length > 0) { - // only use the first file if multiple are dropped - this.addFileToIPFS(payload[0]); + try { + // only use the first file if multiple are dropped + let cid = await this.addFileToIPFS(payload[0]); + this.setState({cid, ipfsUploadError: null}); + } catch (e) { + this.setState({ipfsUploadError: e.toString()}); + } } - }; + } componentWillUnmount() { if (this.cleanup) { @@ -143,14 +148,14 @@ class Create extends React.Component { description, image, templateIds, - templateParameters + templateParameters, ); // TODO: How to create the initial checkpoint? if (this.state.tip003) { let res = await binding.command_asset_create_initial_checkpoint( publicKey, - this.state.tip003Data.committee + this.state.tip003Data.committee, ); console.log(res); @@ -212,11 +217,7 @@ class Create extends React.Component { }; onDeleteCommitteeMember = (index) => { - let committee = this.state.tip003Data.committee.filter(function ( - _, - i, - arr - ) { + let committee = this.state.tip003Data.committee.filter(function (_, i, arr) { return i !== parseInt(index); }); let tip003Data = {...this.state.tip003Data, ...{committee}}; @@ -233,11 +234,20 @@ class Create extends React.Component { selectFile = async () => { const filePath = await dialog.open({ filters: [ - {name: "image types", extensions: ["png", "jpg", "jpeg", "gif"]}, // TODO more formats + { + name: "image types", + extensions: ["png", "jpg", "jpeg", "gif"], + }, // TODO more formats ], multiple: false, }); - await this.addFileToIPFS(filePath); + try { + let cid = await this.addFileToIPFS(filePath); + console.info("IPFS cid: ", cid); + this.setState({cid, ipfsUploadError: null}); + } catch (e) { + this.setState({ipfsUploadError: e.toString()}); + } }; addFileToIPFS = async (filePath) => { @@ -246,50 +256,63 @@ class Create extends React.Component { // unfortunately the ipfs http /add api doesn't play nicely with the tauri http client // resulting in "file argument 'path' is required" // so we'll shell out to the ipfs command - let cid = ""; - try { - const command = new Command("ipfs", ["add", "-Q", filePath]); + + const command = new Command("ipfs", ["add", "-Q", filePath]); + await command.spawn(); + + let cid = await new Promise((resolve, reject) => { let processing = false; + let cid; + command.on("close", (data) => { + if (data.code === 0) { + resolve(cid); + } + }); command.stdout.on("data", (line) => { if (!processing) { processing = true; cid = line; - const cp = new Command("ipfs", [ - "files", - "cp", - `/ipfs/${cid}`, - `/${name}`, - ]); - cp.spawn(); - this.setState({cid}); } }); - command.stderr.on("data", (line) => { - console.error("error: ", line); + command.on("error", (line) => { + reject(new Error(line)); }); - const child = command.spawn(); - return child; - - // console.log("command", command); - // const { success, output, error } = commandOutput(command); - // if (success) { - // const cid = output; - // console.log("cid", cid); - // const command = await runCommand("ipfs", [ - // "files", - // "cp", - // `/ipfs/${cid}`, - // `/${name}`, - // ]); - // const { error } = commandOutput(command); - // if (error) console.error("error: ", error); - // } else { - // console.error("error: ", error); - // } - } catch (e) { - // todo: display error - console.error("catch error: ", e); - } + }); + + const cp = new Command("ipfs", ["files", "cp", `/ipfs/${cid}`, `/${name}`]); + await cp.spawn(); + + await new Promise((resolve, reject) => { + cp.on("close", (data) => { + if (data.code === 0) { + resolve(null); + } else { + reject("IPFS command exited with code ", data.code); + } + }); + + cp.stderr.on("data", (line) => { + reject(new Error(`${line}. Ensure that IPFS is running.`)); + }); + }); + + return cid; + // console.log("command", command); + // const { success, output, error } = commandOutput(command); + // if (success) { + // const cid = output; + // console.log("cid", cid); + // const command = await runCommand("ipfs", [ + // "files", + // "cp", + // `/ipfs/${cid}`, + // `/${name}`, + // ]); + // const { error } = commandOutput(command); + // if (error) console.error("error: ", error); + // } else { + // console.error("error: ", error); + // } }; render() { @@ -302,15 +325,12 @@ class Create extends React.Component { {this.state.error ? ( {this.state.error} ) : ( - + )} Templates Implemented + } label="001 Metadata (required)" /> @@ -351,6 +371,7 @@ class Create extends React.Component { cid={this.state.cid} setCid={(cid) => this.setState({cid})} image={this.state.image} + error={this.state.ipfsUploadError} /> @@ -458,8 +479,7 @@ class Create extends React.Component { - ) - ; + ); } } @@ -491,59 +511,59 @@ const ImageUrl = ({setImage}) => { ); }; -const ImageUpload = ({selectFile}) => { +const ImageUpload = ({selectFile, error}) => { return (

Select an image, or drag and drop an image onto this window

+ + {error ? {error} : }
); }; -const ImageSelector = ({cid, image, selectFile, setImage, setCid}) => { +const ImageSelector = ({cid, image, selectFile, setImage, setCid, error}) => { const [mode, setMode] = useState(""); if (image) { return (
- -
+ +

setImage("")}>Change

); } if (cid) { - return ; + return ; } let display; switch (mode) { case "url": - display = ; + display = ; break; case "upload": - display = ; + display = ; break; default: - display = setMode(m)}/>; + display = ; } return display; }; -const IpfsImage = ({cid, setCid}) => { +const IpfsImage = ({cid, setCid, error}) => { const [src, setSrc] = useState(""); + const [httpError, setHttpError] = useState(null); useMemo(async () => { try { - const response = await fetch( - `http://localhost:5001/api/v0/cat?arg=${cid}`, - { - method: "POST", - responseType: ResponseType.Binary, - } - ); + const response = await fetch(`http://localhost:5001/api/v0/cat?arg=${cid}`, { + method: "POST", + responseType: ResponseType.Binary, + }); const typedArray = Uint8Array.from(response.data); const blob = new Blob([typedArray.buffer]); const reader = new FileReader(); @@ -551,21 +571,28 @@ const IpfsImage = ({cid, setCid}) => { reader.onerror = () => console.error("error"); reader.readAsDataURL(blob); } catch (e) { - // handle error - console.error(e); + setHttpError(e); } }, [cid]); if (src) { return (
- -
+ +

setCid("")}>Change

); } + if (error || httpError) { + return ( +
+ {error || httpError} +
+ ); + } + return

ipfs image loading...

; };