From f8bf7cbccf7e4242fbcb2be81dcdf673cab34c6c Mon Sep 17 00:00:00 2001 From: Natsumi Date: Thu, 3 Jun 2021 07:55:21 +1200 Subject: [PATCH] Upload world image --- html/src/app.js | 307 +++++++++++++++++++++++++++++++++++++++++++++ html/src/index.pug | 2 + 2 files changed, 309 insertions(+) diff --git a/html/src/app.js b/html/src/app.js index 4c8c691c..74fe18d4 100644 --- a/html/src/app.js +++ b/html/src/app.js @@ -8965,6 +8965,9 @@ speechSynthesis.getVoices(); case 'Rename': this.promptRenameWorld(D); break; + case 'Upload Image': + document.getElementById('WorldImageUploadButton').click(); + break; case 'Change Image': this.displayPreviousImages('World', 'Change'); break; @@ -11264,6 +11267,310 @@ speechSynthesis.getVoices(); }); }; + // Upload world image + + $app.methods.onFileChangeWorldImage = function (e) { + var clearFile = function () { + if (document.querySelector('#WorldImageUploadButton')) { + document.querySelector('#WorldImageUploadButton').value = ''; + } + }; + var files = e.target.files || e.dataTransfer.files; + if ((!files.length) || (!this.worldDialog.visible) || (this.worldDialog.loading)) { + clearFile(); + return; + } + if (files[0].size >= 10000000) { //10MB + $app.$message({ + message: 'File size too large', + type: 'error' + }); + clearFile(); + return; + } + if (!files[0].type.match(/image.png/)) { + $app.$message({ + message: 'File isn\'t a png', + type: 'error' + }); + clearFile(); + return; + } + this.worldDialog.loading = true; + var r = new FileReader(); + r.onload = async function (file) { + var base64File = btoa(r.result); + var fileMd5 = await $app.genMd5(base64File); + var fileSizeInBytes = file.total; + var base64SignatureFile = await $app.genSig(base64File); + var signatureMd5 = await $app.genMd5(base64SignatureFile); + var signatureSizeInBytes = await $app.genLength(base64SignatureFile); + var worldId = $app.worldDialog.id; + var { imageUrl } = $app.worldDialog.ref; + var fileId = extractFileId(imageUrl); + if (!fileId) { + $app.$message({ + message: 'Current world image invalid', + type: 'error' + }); + clearFile(); + return; + } + $app.worldImage = { + base64File, + fileMd5, + base64SignatureFile, + signatureMd5, + fileId, + worldId + }; + var params = { + fileMd5, + fileSizeInBytes, + signatureMd5, + signatureSizeInBytes + }; + API.uploadWorldImage(params, fileId); + }; + r.readAsBinaryString(files[0]); + clearFile(); + }; + + API.uploadWorldImage = async function (params, fileId) { + try { + return await this.call(`file/${fileId}`, { + method: 'POST', + params + }).then((json) => { + var args = { + json, + params + }; + this.$emit('WORLDIMAGE:INIT', args); + return args; + }); + } catch (err) { + console.error(err); + this.uploadWorldFailCleanup(fileId); + } + }; + + API.uploadWorldFailCleanup = async function (fileId) { + var json = await this.call(`file/${fileId}`, { + method: 'GET' + }).then((json) => { + return json; + }); + var fileId = json.id; + var fileVersion = json.versions[json.versions.length - 1].version; + this.call(`file/${fileId}/${fileVersion}/signature/finish`, { + method: 'PUT' + }); + this.call(`file/${fileId}/${fileVersion}/file/finish`, { + method: 'PUT' + }); + $app.worldDialog.loading = false; + }; + + API.$on('WORLDIMAGE:INIT', function (args) { + var fileId = args.json.id; + var fileVersion = args.json.versions[args.json.versions.length - 1].version; + var params = { + fileId, + fileVersion + }; + this.uploadWorldImageFileStart(params); + }); + + API.uploadWorldImageFileStart = async function (params) { + try { + return await this.call(`file/${params.fileId}/${params.fileVersion}/file/start`, { + method: 'PUT' + }).then((json) => { + var args = { + json, + params + }; + this.$emit('WORLDIMAGE:FILESTART', args); + return args; + }); + } catch (err) { + console.error(err); + this.uploadWorldFailCleanup(params.fileId); + } + }; + + API.$on('WORLDIMAGE:FILESTART', function (args) { + var { url } = args.json; + var { fileId, fileVersion } = args.params; + var params = { + url, + fileId, + fileVersion + }; + this.uploadWorldImageFileAWS(params); + }); + + API.uploadWorldImageFileAWS = function (params) { + return webApiService.execute({ + url: params.url, + uploadFilePUT: true, + fileData: $app.worldImage.base64File, + fileMIME: 'image/png', + headers: { + 'Content-MD5': $app.worldImage.fileMd5 + } + }).then((json) => { + if (json.status !== 200) { + $app.worldDialog.loading = false; + this.$throw('World image upload failed', json); + } + var args = { + json, + params + }; + this.$emit('WORLDIMAGE:FILEAWS', args); + return args; + }); + }; + + API.$on('WORLDIMAGE:FILEAWS', function (args) { + var { fileId, fileVersion } = args.params; + var params = { + fileId, + fileVersion + }; + this.uploadWorldImageFileFinish(params); + }); + + API.uploadWorldImageFileFinish = function (params) { + return this.call(`file/${params.fileId}/${params.fileVersion}/file/finish`, { + method: 'PUT', + params: { + maxParts: 0, + nextPartNumber: 0 + } + }).then((json) => { + var args = { + json, + params + }; + this.$emit('WORLDIMAGE:FILEFINISH', args); + return args; + }); + }; + + API.$on('WORLDIMAGE:FILEFINISH', function (args) { + var { fileId, fileVersion } = args.params; + var params = { + fileId, + fileVersion + }; + this.uploadWorldImageSigStart(params); + }); + + API.uploadWorldImageSigStart = async function (params) { + try { + return await this.call(`file/${params.fileId}/${params.fileVersion}/signature/start`, { + method: 'PUT' + }).then((json) => { + var args = { + json, + params + }; + this.$emit('WORLDIMAGE:SIGSTART', args); + return args; + }); + } catch (err) { + console.error(err); + this.uploadWorldFailCleanup(params.fileId); + } + }; + + API.$on('WORLDIMAGE:SIGSTART', function (args) { + var { url } = args.json; + var { fileId, fileVersion } = args.params; + var params = { + url, + fileId, + fileVersion + }; + this.uploadWorldImageSigAWS(params); + }); + + API.uploadWorldImageSigAWS = function (params) { + return webApiService.execute({ + url: params.url, + uploadFilePUT: true, + fileData: $app.worldImage.base64SignatureFile, + fileMIME: 'application/x-rsync-signature', + headers: { + 'Content-MD5': $app.worldImage.signatureMd5 + } + }).then((json) => { + if (json.status !== 200) { + $app.worldDialog.loading = false; + this.$throw('World image upload failed', json); + } + var args = { + json, + params + }; + this.$emit('WORLDIMAGE:SIGAWS', args); + return args; + }); + }; + + API.$on('WORLDIMAGE:SIGAWS', function (args) { + var { fileId, fileVersion } = args.params; + var params = { + fileId, + fileVersion + }; + this.uploadWorldImageSigFinish(params); + }); + + API.uploadWorldImageSigFinish = function (params) { + return this.call(`file/${params.fileId}/${params.fileVersion}/signature/finish`, { + method: 'PUT', + params: { + maxParts: 0, + nextPartNumber: 0 + } + }).then((json) => { + var args = { + json, + params + }; + this.$emit('WORLDIMAGE:SIGFINISH', args); + return args; + }); + }; + + API.$on('WORLDIMAGE:SIGFINISH', function (args) { + var { fileId, fileVersion } = args.params; + var parmas = { + id: $app.worldImage.worldId, + imageUrl: `https://api.vrchat.cloud/api/1/file/${fileId}/${fileVersion}/file` + }; + this.setWorldImage(parmas); + }); + + API.setWorldImage = function (params) { + return this.call(`worlds/${params.id}`, { + method: 'PUT', + params + }).then((json) => { + var args = { + json, + params + }; + this.$emit('WORLDIMAGE:SET', args); + this.$emit('WORLD', args); + return args; + }); + }; + API.$on('AVATARIMAGE:SET', function (args) { $app.avatarDialog.loading = false; if (args.json.imageUrl === args.params.imageUrl) { diff --git a/html/src/index.pug b/html/src/index.pug index b126fba2..4a841216 100644 --- a/html/src/index.pug +++ b/html/src/index.pug @@ -1260,6 +1260,8 @@ html el-dropdown-item(icon="el-icon-edit" command="Rename") Rename el-dropdown-item(icon="el-icon-edit" command="Change Description") Change Description el-dropdown-item(icon="el-icon-picture-outline" command="Change Image") Change Image + el-dropdown-item(icon="el-icon-upload2" command="Upload Image") Upload Image + input(type="file" multiple accept="image/*" @change="onFileChangeWorldImage" id="WorldImageUploadButton" style="display:none") el-dropdown-item(icon="el-icon-delete" command="Delete" style="color:#F56C6C" divided) Delete el-tabs el-tab-pane(label="Instances")