diff --git a/html/src/app.js b/html/src/app.js index b88838d4..b6ea9f61 100644 --- a/html/src/app.js +++ b/html/src/app.js @@ -7141,6 +7141,7 @@ speechSynthesis.getVoices(); layout: 'table' }; $app.data.VRCPlusIconsTable = {}; + $app.data.galleryTable = {}; $app.data.inviteMessageTable = { visible: false, data: [], @@ -8648,8 +8649,10 @@ speechSynthesis.getVoices(); } } else if (command === 'Previous Images') { this.displayPreviousImages('User', 'Display'); - } else if (command === 'Select Avatar') { + } else if (command === 'Select Avatar') { this.promptSelectAvatarDialog(); + } else if (command === 'Manage Gallery') { + this.showGalleryDialog(); } else { this.$confirm(`Continue? ${command}`, 'Confirm', { confirmButtonText: 'Confirm', @@ -9947,15 +9950,16 @@ speechSynthesis.getVoices(); $app.VRCPlusIconsTable = {}; }); - $app.methods.displayVRCPlusIconsTable = function () { + $app.methods.refreshVRCPlusIconsTable = function () { + this.galleryDialogIconsLoading = true; var params = { - n: 50, + n: 100, tag: 'icon' }; - API.refreshVRCPlusIconsTableData(params); + API.getFileList(params); }; - API.refreshVRCPlusIconsTableData = function (params) { + API.getFileList = function (params) { return this.call('files', { method: 'GET', params @@ -9964,20 +9968,27 @@ speechSynthesis.getVoices(); json, params }; - this.$emit('VRCPLUSICON:LIST', args); + this.$emit('FILES:LIST', args); return args; }); }; - API.$on('VRCPLUSICON:LIST', function (args) { - $app.VRCPlusIconsTable = args.json; + API.$on('FILES:LIST', function (args) { + if (args.params.tag === 'icon') { + $app.VRCPlusIconsTable = args.json; + $app.galleryDialogIconsLoading = false; + } }); - $app.methods.setVRCPlusIcon = function (userIcon) { - if (userIcon !== '') { - userIcon = `https://api.vrchat.cloud/api/1/file/${userIcon}/1`; + $app.methods.setVRCPlusIcon = function (fileId) { + var userIcon = ''; + if (fileId) { + userIcon = `https://api.vrchat.cloud/api/1/file/${fileId}/1`; } - API.setVRCPlusIcon({ + if (userIcon === API.currentUser.userIcon) { + return; + } + API.saveCurrentUser({ userIcon }).then((args) => { this.$message({ @@ -9988,22 +9999,8 @@ speechSynthesis.getVoices(); }); }; - API.setVRCPlusIcon = function (params) { - return this.call(`users/${this.currentUser.id}`, { - method: 'PUT', - params - }).then((json) => { - var args = { - json, - params - }; - this.$emit('USER:CURRENT:SAVE', args); - return args; - }); - }; - - $app.methods.deleteVRCPlusIcon = function (userIcon) { - API.deleteVRCPlusIcon(userIcon).then((args) => { + $app.methods.deleteVRCPlusIcon = function (fileId) { + API.deleteFile(fileId).then((args) => { API.$emit('VRCPLUSICON:DELETE', args); return args; }); @@ -10013,32 +10010,29 @@ speechSynthesis.getVoices(); var array = $app.VRCPlusIconsTable; var { length } = array; for (var i = 0; i < length; ++i) { - if (args.userIcon === array[i].id) { + if (args.fileId === array[i].id) { array.splice(i, 1); break; } } }); - API.deleteVRCPlusIcon = function (userIcon) { - return this.call(`file/${userIcon}`, { + API.deleteFile = function (fileId) { + return this.call(`file/${fileId}`, { method: 'DELETE' }).then((json) => { var args = { json, - userIcon + fileId }; return args; }); }; $app.methods.compareCurrentVRCPlusIcon = function (userIcon) { - try { - var currentUserIcon = extractFileId(API.currentUser.userIcon); - if (userIcon === currentUserIcon) { - return true; - } - } catch (err) { + var currentUserIcon = extractFileId(API.currentUser.userIcon); + if (userIcon === currentUserIcon) { + return true; } return false; }; @@ -12157,6 +12151,149 @@ speechSynthesis.getVoices(); } }; + // gallery + + $app.data.galleryDialog = {}; + $app.data.galleryDialogVisible = false; + $app.data.galleryDialogGalleryLoading = false; + $app.data.galleryDialogIconsLoading = false; + + API.$on('LOGIN', function () { + $app.galleryTable = {}; + }); + + $app.methods.showGalleryDialog = function () { + this.galleryDialogVisible = true; + this.refreshGalleryTable(); + this.refreshVRCPlusIconsTable(); + }; + + $app.methods.refreshGalleryTable = function () { + this.galleryDialogGalleryLoading = true; + var params = { + n: 100, + tag: 'gallery' + }; + API.getFileList(params); + }; + + API.$on('FILES:LIST', function (args) { + if (args.params.tag === 'gallery') { + $app.galleryTable = args.json; + $app.galleryDialogGalleryLoading = false; + } + }); + + $app.methods.setProfilePicOverride = function (fileId) { + var profilePicOverride = ''; + if (fileId) { + profilePicOverride = `https://api.vrchat.cloud/api/1/file/${fileId}/1`; + } + if (profilePicOverride === API.currentUser.profilePicOverride) { + return; + } + API.saveCurrentUser({ + profilePicOverride + }).then((args) => { + this.$message({ + message: 'Profile picture changed', + type: 'success' + }); + return args; + }); + }; + + $app.methods.deleteGalleryImage = function (fileId) { + API.deleteFile(fileId).then((args) => { + API.$emit('GALLERYIMAGE:DELETE', args); + return args; + }); + }; + + API.$on('GALLERYIMAGE:DELETE', function (args) { + var array = $app.galleryTable; + var { length } = array; + for (var i = 0; i < length; ++i) { + if (args.fileId === array[i].id) { + array.splice(i, 1); + break; + } + } + }); + + $app.methods.compareCurrentProfilePic = function (fileId) { + var currentProfilePicOverride = extractFileId(API.currentUser.profilePicOverride); + if (fileId === currentProfilePicOverride) { + return true; + } + return false; + }; + + $app.methods.onFileChangeGallery = function (e) { + var clearFile = function () { + if (document.querySelector('#GalleryUploadButton')) { + document.querySelector('#GalleryUploadButton').value = ''; + } + }; + var files = e.target.files || e.dataTransfer.files; + if (!files.length) { + return; + } + if (files[0].size >= 10000000) { //10MB + $app.$message({ + message: 'File size too large', + type: 'error' + }); + clearFile(); + return; + } + if (!files[0].type.match(/image.*/)) { + $app.$message({ + message: 'File isn\'t an image', + type: 'error' + }); + clearFile(); + return; + } + var r = new FileReader(); + r.onload = function () { + var base64Body = btoa(r.result); + API.uploadGalleryImage(base64Body).then((args) => { + $app.$message({ + message: 'Gallery image uploaded', + type: 'success' + }); + return args; + }); + }; + r.readAsBinaryString(files[0]); + clearFile(); + }; + + $app.methods.displayGalleryUpload = function () { + document.getElementById('GalleryUploadButton').click(); + }; + + API.uploadGalleryImage = function (params) { + return this.call('gallery', { + uploadImage: true, + imageData: params + }).then((json) => { + var args = { + json, + params + }; + this.$emit('GALLERYIMAGE:ADD', args); + return args; + }); + }; + + API.$on('GALLERYIMAGE:ADD', function (args) { + if (Object.keys($app.galleryTable).length !== 0) { + $app.galleryTable.push(args.json); + } + }); + $app = new Vue($app); window.$app = $app; }()); diff --git a/html/src/app.scss b/html/src/app.scss index 37469ba5..5aa8c2dc 100644 --- a/html/src/app.scss +++ b/html/src/app.scss @@ -371,22 +371,21 @@ img.friends-list-avatar { .x-friend-item > .vrcplus-icon { border: 4px solid #dcdfe6; - border-radius: 50%; - margin: 0; - width: 120px; - height: 120px; + border-radius: 20px; + width: 200px; + height: 200px; cursor: pointer; } .x-friend-item > .current-vrcplus-icon { border: 4px solid #67c23a; - border-radius: 50%; + cursor: default; } .x-friend-item > .vrcplus-icon > img { width: 100%; height: 100%; - border-radius: 50%; + border-radius: 15px; object-fit: cover; } diff --git a/html/src/index.pug b/html/src/index.pug index 06258a81..f1807dc2 100644 --- a/html/src/index.pug +++ b/html/src/index.pug @@ -255,7 +255,7 @@ html .x-container(v-show="$refs.menu && $refs.menu.activeIndex === 'favorite'" v-if="$refs.menu && $refs.menu.activeIndex === 'favorite'") el-button(type="default" :loading="API.isFavoriteLoading" @click="API.refreshFavorites()" size="small" icon="el-icon-refresh" circle style="position:relative;float:right;z-index:1") el-tabs(type="card" v-loading="API.isFavoriteLoading") - el-tab-pane(label="Friend") + el-tab-pane(label="Friends") el-collapse(style="border:0") el-collapse-item(v-for="group in API.favoriteFriendGroups" :key="group.name") template(slot="title") @@ -296,7 +296,7 @@ html template(v-else) span(v-text="favorite.name || favorite.id") el-button(type="text" icon="el-icon-close" size="mini" @click.stop="deleteFavorite(favorite.id)" style="margin-left:5px") - el-tab-pane(label="World") + el-tab-pane(label="Worlds") el-collapse(style="border:0") el-collapse-item(v-for="group in API.favoriteWorldGroups" :key="group.name") template(slot="title") @@ -343,7 +343,7 @@ html template(v-else) span(v-text="favorite.name || favorite.id") el-button(type="text" icon="el-icon-close" size="mini" @click.stop="deleteFavorite(favorite.id)" style="margin-left:5px") - el-tab-pane(label="Avatar") + el-tab-pane(label="Avatars") el-tooltip(placement="top") template(#content) span Add favorite @@ -597,35 +597,6 @@ html el-table-column(label="Action" width="60" align="right") template(v-once #default="scope") el-button(type="text" icon="el-icon-edit" size="mini" @click="showEditInviteMessageDialog('requestResponse', scope.row)") - div.options-container - span.header VRCPlus Icons - el-tooltip(placement="top") - template(#content) - span Refresh - el-button(type="default" @click="displayVRCPlusIconsTable()" size="mini" icon="el-icon-refresh" circle style="margin-left:5px") - el-tooltip(placement="top") - template(#content) - span Clear results - el-button(type="default" @click="VRCPlusIconsTable = {}" size="mini" icon="el-icon-delete" circle style="margin-left:0") - template(v-if="API.currentUser.$isVRCPlus") - el-tooltip(placement="top") - template(#content) - span Upload icon - div(style="display:inline-block") - el-button(type="default" @click="displayVRCPlusIconUpload" size="mini" icon="el-icon-upload2" circle style="margin-left:0") - input(type="file" multiple accept="image/*" @change="onFileChangeVRCPlusIcon" id="VRCPlusIconUploadButton" style="display:none") - el-tooltip(placement="top") - template(#content) - span Reset icon - el-button(type="default" @click="setVRCPlusIcon('')" size="mini" icon="el-icon-close" circle style="margin-left:0" :disabled="!API.currentUser.userIcon") - br - .x-friend-item(v-for="icon in VRCPlusIconsTable" :key="icon.id" style="display:inline-block;margin-top:10px;cursor:default") - .vrcplus-icon(style="" @click="setVRCPlusIcon(icon.id)" :class="{ 'current-vrcplus-icon': compareCurrentVRCPlusIcon(icon.id) }") - img.avatar(v-if="icon.versions[1].file.url" v-lazy="icon.versions[1].file.url") - el-tooltip(placement="top") - template(#content) - span Delete - el-button(type="default" @click="deleteVRCPlusIcon(icon.id)" size="mini" icon="el-icon-delete" circle style="float:right;") div.options-container span.header Past Display Names data-tables(v-bind="pastDisplayNameTable" style="margin-top:10px") @@ -1042,8 +1013,8 @@ html div(v-loading="userDialog.loading") div(style="display:flex") el-popover(v-if="displayProfilePicOverrideAsAvatar && userDialog.ref.profilePicOverride" placement="right" width="500px" trigger="click") - img.x-link(slot="reference" v-lazy="userDialog.ref.profilePicOverride" style="flex:none;width:160px;height:120px;border-radius:4px") - img.x-link(v-lazy="userDialog.ref.profilePicOverride" style="width:500px;height:375px" @click="openExternalLink(userDialog.ref.profilePicOverride)") + img.x-link(slot="reference" v-lazy="userDialog.ref.profilePicOverride" style="flex:none;width:160px;height:120px;border-radius:4px;object-fit:cover") + img.x-link(v-lazy="userDialog.ref.profilePicOverride" style="width:500px;height:375px;object-fit:cover" @click="openExternalLink(userDialog.ref.profilePicOverride)") el-popover(v-else placement="right" width="500px" trigger="click") img.x-link(slot="reference" v-lazy="userDialog.ref.currentAvatarThumbnailImageUrl" style="flex:none;width:160px;height:120px;border-radius:4px") img.x-link(v-lazy="userDialog.ref.currentAvatarImageUrl" style="width:500px;height:375px" @click="openExternalLink(userDialog.ref.currentAvatarImageUrl)") @@ -1088,7 +1059,8 @@ html el-dropdown-menu(#default="dropdown") el-dropdown-item(icon="el-icon-refresh" command="Refresh") Refresh template(v-if="userDialog.ref.id === API.currentUser.id") - el-dropdown-item(icon="el-icon-check" command="Select Avatar" divided) Select Avatar By ID + el-dropdown-item(icon="el-icon-picture-outline" command="Manage Gallery" divided) Manage Gallery/Icons + el-dropdown-item(icon="el-icon-check" command="Select Avatar") Select Avatar By ID el-dropdown-item(icon="el-icon-s-custom" command="Show Avatar Author") Show Avatar Author el-dropdown-item(icon="el-icon-s-custom" command="Show Fallback Avatar Details") Show Fallback Avatar Details el-dropdown-item(icon="el-icon-edit" command="Edit Social Status" divided) Social Status @@ -1989,6 +1961,40 @@ html div(style="display:inline-block" v-for="image in previousImagesTable" :key="image.version" v-if="image.file") .x-change-image-item img.image(v-lazy="image.file.url") + + //- dialog: Gallery/VRCPlusIcons + el-dialog.x-dialog(ref="galleryDialog" :visible.sync="galleryDialogVisible" title="Gallery and Icons" width="1130px") + el-tabs(type="card") + el-tab-pane(v-loading="galleryDialogGalleryLoading") + span(slot="label") Gallery + span(style="color:#909399;font-size:12px;margin-left:5px") {{ galleryTable.length }}/64 + input(type="file" multiple accept="image/*" @change="onFileChangeGallery" id="GalleryUploadButton" style="display:none") + el-button-group + el-button(type="default" size="small" @click="refreshGalleryTable" icon="el-icon-refresh") Refresh + el-button(type="default" size="small" @click="displayGalleryUpload" icon="el-icon-upload2" :disabled="!API.currentUser.$isVRCPlus") Upload + el-button(type="default" size="small" @click="setProfilePicOverride('')" icon="el-icon-close" :disabled="!API.currentUser.profilePicOverride") Clear + br + .x-friend-item(v-for="image in galleryTable" :key="image.id" style="display:inline-block;margin-top:10px;width:unset;cursor:default") + .vrcplus-icon(v-if="image.versions[1].file.url" @click="setProfilePicOverride(image.id)" :class="{ 'current-vrcplus-icon': compareCurrentProfilePic(image.id) }") + img.avatar(v-lazy="image.versions[1].file.url") + div(style="float:right;margin-top:5px") + el-button(type="default" @click="openExternalLink(image.versions[1].file.url)" size="mini" icon="el-icon-paperclip" circle) + el-button(type="default" @click="deleteGalleryImage(image.id)" size="mini" icon="el-icon-delete" circle style="margin-left:5px") + el-tab-pane(v-loading="galleryDialogIconsLoading") + span(slot="label") Icons + span(style="color:#909399;font-size:12px;margin-left:5px") {{ VRCPlusIconsTable.length }}/64 + input(type="file" multiple accept="image/*" @change="onFileChangeVRCPlusIcon" id="VRCPlusIconUploadButton" style="display:none") + el-button-group + el-button(type="default" size="small" @click="refreshVRCPlusIconsTable" icon="el-icon-refresh") Refresh + el-button(type="default" size="small" @click="displayVRCPlusIconUpload" icon="el-icon-upload2" :disabled="!API.currentUser.$isVRCPlus") Upload + el-button(type="default" size="small" @click="setVRCPlusIcon('')" icon="el-icon-close" :disabled="!API.currentUser.userIcon") Clear + br + .x-friend-item(v-for="image in VRCPlusIconsTable" :key="image.id" style="display:inline-block;margin-top:10px;width:unset;cursor:default") + .vrcplus-icon(v-if="image.versions[1].file.url" @click="setVRCPlusIcon(image.id)" :class="{ 'current-vrcplus-icon': compareCurrentVRCPlusIcon(image.id) }") + img.avatar(v-lazy="image.versions[1].file.url") + div(style="float:right;margin-top:5px") + el-button(type="default" @click="openExternalLink(image.versions[1].file.url)" size="mini" icon="el-icon-paperclip" circle) + el-button(type="default" @click="deleteVRCPlusIcon(image.id)" size="mini" icon="el-icon-delete" circle style="margin-left:5px") //- dialog: open source software notice el-dialog.x-dialog(:visible.sync="ossDialog" title="Open Source Software Notice" width="650px")