diff --git a/html/src/app.js b/html/src/app.js index d6203860..193c2969 100644 --- a/html/src/app.js +++ b/html/src/app.js @@ -14699,7 +14699,7 @@ speechSynthesis.getVoices(); if (D.visible === false || D.id !== args.ref.favoriteId) { return; } - D.isFavorite = false; + D.isFavorite = $app.localWorldFavoritesList.includes(D.id); }); $app.methods.showWorldDialog = function (tag, shortName) { @@ -14763,6 +14763,11 @@ speechSynthesis.getVoices(); D.loading = false; D.ref = args.ref; D.isFavorite = API.cachedFavoritesByObjectId.has(D.id); + if (!D.isFavorite) { + D.isFavorite = this.localWorldFavoritesList.includes( + D.id + ); + } var {isPC, isQuest} = this.getAvailablePlatforms( args.ref.unityPackages ); @@ -15405,7 +15410,8 @@ speechSynthesis.getVoices(); loading: false, type: '', objectId: '', - groups: [] + groups: [], + currentGroup: {} }; API.$on('LOGOUT', function () { @@ -15424,7 +15430,6 @@ speechSynthesis.getVoices(); D.loading = false; }) .then((args) => { - D.visible = false; return args; }); }; @@ -15480,8 +15485,33 @@ speechSynthesis.getVoices(); D.groups = API.favoriteAvatarGroups; D.visible = true; } + this.updateFavoriteDialog(objectId); }; + $app.methods.updateFavoriteDialog = function (objectId) { + var D = this.favoriteDialog; + if (!D.visible || D.objectId !== objectId) { + return; + } + D.currentGroup = {}; + var favorite = this.favoriteObjects.get(objectId); + if (favorite) { + for (var group of API.favoriteWorldGroups) { + if (favorite.groupKey === group.key) { + D.currentGroup = group; + } + } + } + }; + + API.$on('FAVORITE:ADD', function (args) { + $app.updateFavoriteDialog(args.params.favoriteId); + }); + + API.$on('FAVORITE:DELETE', function (args) { + $app.updateFavoriteDialog(args.params.objectId); + }); + // App: Invite Dialog $app.data.inviteDialog = { @@ -19575,7 +19605,8 @@ speechSynthesis.getVoices(); API.cachedWorlds.forEach((ref, id) => { if ( !API.cachedFavoritesByObjectId.has(id) && - ref.authorId !== API.currentUser.id + ref.authorId !== API.currentUser.id && + !this.localWorldFavoritesList.includes(id) ) { API.cachedWorlds.delete(id); } @@ -20395,6 +20426,7 @@ speechSynthesis.getVoices(); worldIdList: new Set(), errors: '', worldImportFavoriteGroup: null, + worldImportLocalFavoriteGroup: null, importProgress: 0, importProgressTotal: 0 }; @@ -20479,9 +20511,16 @@ speechSynthesis.getVoices(); $app.methods.selectWorldImportGroup = function (group) { var D = this.worldImportDialog; + D.worldImportLocalFavoriteGroup = null; D.worldImportFavoriteGroup = group; }; + $app.methods.selectWorldImportLocalGroup = function (group) { + var D = this.worldImportDialog; + D.worldImportFavoriteGroup = null; + D.worldImportLocalFavoriteGroup = group; + }; + $app.methods.cancelWorldImport = function () { var D = this.worldImportDialog; D.loading = false; @@ -20489,10 +20528,10 @@ speechSynthesis.getVoices(); $app.methods.importWorldImportTable = async function () { var D = this.worldImportDialog; - D.loading = true; - if (!D.worldImportFavoriteGroup) { + if (!D.worldImportFavoriteGroup && !D.worldImportLocalFavoriteGroup) { return; } + D.loading = true; var data = [...this.worldImportTable.data].reverse(); D.importProgressTotal = data.length; try { @@ -20501,7 +20540,17 @@ speechSynthesis.getVoices(); break; } var ref = data[i]; - await this.addFavoriteWorld(ref, D.worldImportFavoriteGroup); + if (D.worldImportFavoriteGroup) { + await this.addFavoriteWorld( + ref, + D.worldImportFavoriteGroup + ); + } else if (D.worldImportLocalFavoriteGroup) { + this.addLocalWorldFavorite( + ref.id, + D.worldImportLocalFavoriteGroup + ); + } removeFromArray(this.worldImportTable.data, ref); D.worldIdList.delete(ref.id); D.importProgress++; @@ -20520,9 +20569,11 @@ speechSynthesis.getVoices(); $app.resetWorldImport(); $app.worldImportDialog.visible = false; $app.worldImportFavoriteGroup = null; + $app.worldImportLocalFavoriteGroup = null; $app.worldExportDialogVisible = false; $app.worldExportFavoriteGroup = null; + $app.worldExportLocalFavoriteGroup = null; }); // App: world favorite export @@ -20531,12 +20582,14 @@ speechSynthesis.getVoices(); $app.data.worldExportDialogVisible = false; $app.data.worldExportContent = ''; $app.data.worldExportFavoriteGroup = null; + $app.data.worldExportLocalFavoriteGroup = null; $app.methods.showWorldExportDialog = function () { this.$nextTick(() => adjustDialogZ(this.$refs.worldExportDialogRef.$el) ); this.worldExportFavoriteGroup = null; + this.worldExportLocalFavoriteGroup = null; this.updateWorldExportDialog(); this.worldExportDialogVisible = true; }; @@ -20549,23 +20602,51 @@ speechSynthesis.getVoices(); return str; }; var lines = ['WorldID,Name']; - API.favoriteWorldGroups.forEach((group) => { - if ( - !this.worldExportFavoriteGroup || - this.worldExportFavoriteGroup === group - ) { - $app.favoriteWorlds.forEach((ref) => { - if (group.key === ref.groupKey) { - lines.push(`${_(ref.id)},${_(ref.name)}`); - } - }); + if (this.worldExportFavoriteGroup) { + API.favoriteWorldGroups.forEach((group) => { + if (this.worldExportFavoriteGroup === group) { + $app.favoriteWorlds.forEach((ref) => { + if (group.key === ref.groupKey) { + lines.push(`${_(ref.id)},${_(ref.name)}`); + } + }); + } + }); + } else if (this.worldExportLocalFavoriteGroup) { + var favoriteGroup = + this.localWorldFavorites[this.worldExportLocalFavoriteGroup]; + if (!favoriteGroup) { + return; } - }); + for (var i = 0; i < favoriteGroup.length; ++i) { + var ref = favoriteGroup[i]; + lines.push(`${_(ref.id)},${_(ref.name)}`); + } + } else { + // export all + this.favoriteWorlds.forEach((ref1) => { + lines.push(`${_(ref1.id)},${_(ref1.name)}`); + }); + for (var i = 0; i < this.localWorldFavoritesList.length; ++i) { + var worldId = this.localWorldFavoritesList[i]; + var ref2 = API.cachedWorlds.get(worldId); + if (typeof ref2 !== 'undefined') { + lines.push(`${_(ref2.id)},${_(ref2.name)}`); + } + } + } this.worldExportContent = lines.join('\n'); }; $app.methods.selectWorldExportGroup = function (group) { this.worldExportFavoriteGroup = group; + this.worldExportLocalFavoriteGroup = null; + this.updateWorldExportDialog(); + }; + + $app.methods.selectWorldExportLocalGroup = function (group) { + this.worldExportLocalFavoriteGroup = group; + this.worldExportFavoriteGroup = null; this.updateWorldExportDialog(); }; @@ -21161,6 +21242,227 @@ speechSynthesis.getVoices(); this.avatarRemoteDatabaseProvider = provider; }; + // App: local world favorites + + $app.data.localWorldFavoriteGroups = []; + $app.data.localWorldFavoritesList = []; + $app.data.localWorldFavorites = []; + + $app.methods.addLocalWorldFavorite = function (worldId, group) { + if (this.hasLocalWorldFavorite(worldId, group)) { + return; + } + var ref = API.cachedWorlds.get(worldId); + if (typeof ref === 'undefined') { + return; + } + if (!this.localWorldFavoritesList.includes(worldId)) { + this.localWorldFavoritesList.push(worldId); + } + if (!this.localWorldFavorites[group]) { + this.localWorldFavorites[group] = []; + } + if (!this.localWorldFavoriteGroups.includes(group)) { + this.localWorldFavoriteGroups.push(group); + } + this.localWorldFavorites[group].unshift(ref); + database.addWorldToCache(ref); + database.addWorldToFavorites(worldId, group); + if ( + this.favoriteDialog.visible && + this.favoriteDialog.objectId === worldId + ) { + this.updateFavoriteDialog(worldId); + } + if (this.worldDialog.visible && this.worldDialog.id === worldId) { + this.worldDialog.isFavorite = true; + } + }; + + $app.methods.removeLocalWorldFavorite = function (worldId, group) { + var favoriteGroup = this.localWorldFavorites[group]; + for (var i = 0; i < favoriteGroup.length; ++i) { + if (favoriteGroup[i].id === worldId) { + favoriteGroup.splice(i, 1); + } + } + var worldInFavorites = false; + for (var i = 0; i < this.localWorldFavoritesList.length; ++i) { + for (var j = 0; j < this.localWorldFavoritesList[i].length; ++j) { + if (this.localWorldFavoritesList[i][j] === worldId) { + worldInFavorites = true; + } + } + } + database.removeWorldFromFavorites(worldId, group); + if (!worldInFavorites) { + removeFromArray(this.localWorldFavoritesList, worldId); + database.removeWorldFromCache(worldId); + } + if ( + this.favoriteDialog.visible && + this.favoriteDialog.objectId === worldId + ) { + this.updateFavoriteDialog(worldId); + } + if (this.worldDialog.visible && this.worldDialog.id === worldId) { + this.worldDialog.isFavorite = + API.cachedFavoritesByObjectId.has(worldId); + } + }; + + $app.methods.getLocalWorldFavorites = async function () { + this.localWorldFavoriteGroups = []; + this.localWorldFavoritesList = []; + this.localWorldFavorites = []; + for (var i = 0; i < this.localWorldFavoritesList.length; ++i) { + // keep reference to fix Vue error + this.localWorldFavoritesList[i] = []; + } + var worldCache = await database.getWorldCache(); + for (var i = 0; i < worldCache.length; ++i) { + var ref = worldCache[i]; + if (!API.cachedWorlds.has(ref.id)) { + API.cachedWorlds.set(ref.id, ref); + } + } + var favorites = await database.getWorldFavorites(); + for (var i = 0; i < favorites.length; ++i) { + var favorite = favorites[i]; + if (!this.localWorldFavoritesList.includes(favorite.worldId)) { + this.localWorldFavoritesList.push(favorite.worldId); + } + if (!this.localWorldFavorites[favorite.groupName]) { + this.localWorldFavorites[favorite.groupName] = []; + } + if (!this.localWorldFavoriteGroups.includes(favorite.groupName)) { + this.localWorldFavoriteGroups.push(favorite.groupName); + } + var ref = API.cachedWorlds.get(favorite.worldId); + this.localWorldFavorites[favorite.groupName].unshift(ref); + } + if (this.localWorldFavoriteGroups.length === 0) { + // default group + this.localWorldFavorites.Favorites = []; + this.localWorldFavoriteGroups.push('Favorites'); + } + this.localWorldFavoriteGroups.sort(); + this.localWorldFavorites.sort(); + }; + + $app.methods.hasLocalWorldFavorite = function (worldId, group) { + var favoriteGroup = this.localWorldFavorites[group]; + if (!favoriteGroup) { + return false; + } + for (var i = 0; i < favoriteGroup.length; ++i) { + if (favoriteGroup[i].id === worldId) { + return true; + } + } + return false; + }; + + $app.methods.promptNewLocalWorldFavoriteGroup = function () { + this.$prompt('Enter a world favorite group name', 'New Group', { + distinguishCancelAndClose: true, + confirmButtonText: 'OK', + cancelButtonText: 'Cancel', + inputPattern: /\S+/, + inputErrorMessage: 'Name is required', + callback: (action, instance) => { + if (action === 'confirm' && instance.inputValue) { + this.newLocalWorldFavoriteGroup(instance.inputValue); + } + } + }); + }; + + $app.methods.newLocalWorldFavoriteGroup = function (group) { + if (this.localWorldFavoriteGroups.includes(group)) { + $app.$message({ + message: `Group already exists with the name ${group}`, + type: 'error' + }); + return; + } + if (!this.localWorldFavorites[group]) { + this.localWorldFavorites[group] = []; + } + if (!this.localWorldFavoriteGroups.includes(group)) { + this.localWorldFavoriteGroups.push(group); + } + this.localWorldFavoriteGroups.sort(); + this.localWorldFavorites.sort(); + }; + + $app.methods.promptLocalWorldFavoriteGroupRename = function (group) { + this.$prompt('Enter a world favorite group name', 'Rename Group', { + distinguishCancelAndClose: true, + confirmButtonText: 'Save', + cancelButtonText: 'Cancel', + inputPattern: /\S+/, + inputErrorMessage: 'Name is required', + inputValue: group, + callback: (action, instance) => { + if (action === 'confirm' && instance.inputValue) { + this.renameLocalWorldFavoriteGroup( + instance.inputValue, + group + ); + } + } + }); + }; + + $app.methods.renameLocalWorldFavoriteGroup = function (newName, group) { + if (this.localWorldFavoriteGroups.includes(newName)) { + $app.$message({ + message: `Group already exists with the name ${newName}`, + type: 'error' + }); + return; + } + this.localWorldFavoriteGroups.push(newName); + this.localWorldFavorites[newName] = this.localWorldFavorites[group]; + + removeFromArray(this.localWorldFavoriteGroups, group); + delete this.localWorldFavorites[group]; + database.renameWorldFavoriteGroup(newName, group); + this.localWorldFavoriteGroups.sort(); + this.localWorldFavorites.sort(); + }; + + $app.methods.promptLocalWorldFavoriteGroupDelete = function (group) { + this.$confirm(`Delete Group? ${group}`, 'Confirm', { + confirmButtonText: 'Confirm', + cancelButtonText: 'Cancel', + type: 'info', + callback: (action) => { + if (action === 'confirm') { + this.deleteLocalWorldFavoriteGroup(group); + } + } + }); + }; + + $app.methods.deleteLocalWorldFavoriteGroup = function (group) { + removeFromArray(this.localWorldFavoriteGroups, group); + delete this.localWorldFavorites[group]; + database.deleteWorldFavoriteGroup(group); + }; + + API.$on('WORLD', function (args) { + if ($app.localWorldFavoritesList.includes(args.ref.id)) { + // update db cache + database.addWorldToCache(args.ref); + } + }); + + API.$on('LOGIN', function () { + $app.getLocalWorldFavorites(); + }); + $app = new Vue($app); window.$app = $app; })(); diff --git a/html/src/index.pug b/html/src/index.pug index 4e4b5491..8fc0c643 100644 --- a/html/src/index.pug +++ b/html/src/index.pug @@ -564,6 +564,7 @@ html el-collapse(style="border:0") el-button(size="small" @click="showWorldExportDialog") Export el-button(size="small" @click="showWorldImportDialog") Import + span(style="display:block;margin-top:20px") VRChat Favorites el-collapse-item(v-for="group in API.favoriteWorldGroups" :key="group.name") template(slot="title") span(v-text="group.displayName" style="font-weight:bold;font-size:14px;margin-left:10px") @@ -599,6 +600,31 @@ 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") + span(style="display:block;margin-top:20px") Local Favorites + el-button(size="small" @click="promptNewLocalWorldFavoriteGroup" style="display:block;margin-top:10px") New Group + el-collapse-item(v-for="group in localWorldFavoriteGroups" v-if="localWorldFavorites[group]" :key="group") + template(slot="title") + span(v-text="group" style="font-weight:bold;font-size:14px;margin-left:10px") + span(style="color:#909399;font-size:12px;margin-left:10px") {{ localWorldFavorites[group].length }} + el-tooltip(placement="top" content="Rename" :disabled="hideTooltips") + el-button(@click.stop="promptLocalWorldFavoriteGroupRename(group)" size="mini" icon="el-icon-edit" circle style="margin-left:5px") + el-tooltip(placement="right" content="Delete" :disabled="hideTooltips") + el-button(@click.stop="promptLocalWorldFavoriteGroupDelete(group)" size="mini" icon="el-icon-delete" circle style="margin-left:5px") + .x-friend-list(style="margin-top:10px") + div(style="display:inline-block;width:300px;margin-right:15px" v-for="favorite in localWorldFavorites[group]" :key="favorite.id" @click="showWorldDialog(favorite.id)") + .x-friend-item + template(v-if="favorite.id") + .avatar + img(v-lazy="favorite.thumbnailImageUrl") + .detail + span.name(v-text="favorite.name") + span.extra(v-if="favorite.occupants") {{ favorite.authorName }} ({{ favorite.occupants }}) + span.extra(v-else v-text="favorite.authorName") + el-tooltip(placement="right" content="Unfavorite" :disabled="hideTooltips") + el-button(@click.stop="removeLocalWorldFavorite(favorite.id, group)" size="mini" icon="el-icon-delete" circle style="margin-left:5px") + template(v-else) + span(v-text="favorite.name || favorite.id") + el-button(type="text" icon="el-icon-close" size="mini" @click.stop="removeLocalWorldFavorite(favorite.id, group)" style="margin-left:5px") el-tab-pane(label="Avatars") el-collapse(style="border:0") el-button(size="small" @click="showAvatarExportDialog") Export @@ -1797,9 +1823,9 @@ html div(style="flex:none;margin-left:10px") el-tooltip(v-if="worldDialog.inCache" placement="top" content="Delete world from cache" :disabled="hideTooltips") el-button(icon="el-icon-delete" circle @click="deleteVRChatCache(worldDialog.ref)" :disabled="isGameRunning && worldDialog.cacheLocked") - el-tooltip(v-if="worldDialog.isFavorite" placement="top" content="Remove from favorites" :disabled="hideTooltips") - el-button(type="warning" icon="el-icon-star-on" circle @click="worldDialogCommand('Delete Favorite')" style="margin-left:5px") - el-tooltip(v-else placement="top" content="Add to favorites" :disabled="hideTooltips") + el-tooltip(v-if="worldDialog.isFavorite" placement="top" content="Favorite/Unfavorite" :disabled="hideTooltips") + el-button(type="default" icon="el-icon-star-on" circle @click="worldDialogCommand('Add Favorite')" style="margin-left:5px") + el-tooltip(v-else placement="top" content="Favorite/Unfavorite" :disabled="hideTooltips") el-button(type="default" icon="el-icon-star-off" circle @click="worldDialogCommand('Add Favorite')" style="margin-left:5px") el-dropdown(trigger="click" @command="worldDialogCommand" size="small" style="margin-left:5px") el-button(type="default" icon="el-icon-more" circle) @@ -2032,7 +2058,16 @@ html //- dialog: favorite el-dialog.x-dialog(:before-close="beforeDialogClose" @mousedown.native="dialogMouseDown" @mouseup.native="dialogMouseUp" ref="favoriteDialog" :visible.sync="favoriteDialog.visible" title="Choose Group" width="300px") div(v-if="favoriteDialog.visible" v-loading="favoriteDialog.loading") - el-button(v-for="group in favoriteDialog.groups" :key="group.name" style="display:block;width:100%;margin:10px 0" @click="addFavorite(group)") {{ group.displayName }} ({{ group.count }} / {{ group.capacity }}) + span(style="display:block;text-align:center") VRChat Favorites + template(v-if="favoriteDialog.currentGroup && favoriteDialog.currentGroup.key") + el-button(style="display:block;width:100%;margin:10px 0" @click="deleteFavorite(favoriteDialog.objectId)") #[i.el-icon-check] {{ favoriteDialog.currentGroup.displayName }} ({{ favoriteDialog.currentGroup.count }} / {{ favoriteDialog.currentGroup.capacity }}) + template(v-else) + el-button(v-for="group in favoriteDialog.groups" :key="group" style="display:block;width:100%;margin:10px 0" @click="addFavorite(group)") {{ group.displayName }} ({{ group.count }} / {{ group.capacity }}) + div(v-if="favoriteDialog.visible && favoriteDialog.type === 'world'" style="margin-top:20px") + span(style="display:block;text-align:center") Local Favorites + template(v-for="group in localWorldFavoriteGroups" :key="group") + el-button(v-if="hasLocalWorldFavorite(favoriteDialog.objectId, group)" style="display:block;width:100%;margin:10px 0" @click="removeLocalWorldFavorite(favoriteDialog.objectId, group)") #[i.el-icon-check] {{ group }} ({{ localWorldFavorites[group].length }}) + el-button(v-else style="display:block;width:100%;margin:10px 0" @click="addLocalWorldFavorite(favoriteDialog.objectId, group)") {{ group }} ({{ localWorldFavorites[group].length }}) //- dialog: invite el-dialog.x-dialog(:before-close="beforeDialogClose" @mousedown.native="dialogMouseDown" @mouseup.native="dialogMouseUp" ref="inviteDialog" :visible.sync="inviteDialog.visible" title="Invite" width="450px") @@ -3032,11 +3067,19 @@ html el-dropdown(@click.native.stop trigger="click" size="small") el-button(size="mini") span(v-if="worldExportFavoriteGroup") {{ worldExportFavoriteGroup.displayName }} ({{ worldExportFavoriteGroup.count }}/{{ worldExportFavoriteGroup.capacity }}) #[i.el-icon-arrow-down.el-icon--right] - span(v-else) All Favorites #[i.el-icon-arrow-down.el-icon--right] + span(v-else) Select Group #[i.el-icon-arrow-down.el-icon--right] el-dropdown-menu(#default="dropdown") - el-dropdown-item(style="display:block;margin:10px 0" @click.native="selectWorldExportGroup(null)") All Favorites + el-dropdown-item(style="display:block;margin:10px 0" @click.native="selectWorldExportGroup(null)") None template(v-for="groupAPI in API.favoriteWorldGroups" :key="groupAPI.name") - el-dropdown-item(style="display:block;margin:10px 0" @click.native="selectWorldExportGroup(groupAPI)" :disabled="groupAPI.count >= groupAPI.capacity") {{ groupAPI.displayName }} ({{ groupAPI.count }}/{{ groupAPI.capacity }}) + el-dropdown-item(style="display:block;margin:10px 0" @click.native="selectWorldExportGroup(groupAPI)") {{ groupAPI.displayName }} ({{ groupAPI.count }}/{{ groupAPI.capacity }}) + el-dropdown(@click.native.stop trigger="click" size="small" style="margin-left:10px") + el-button(size="mini") + span(v-if="worldExportLocalFavoriteGroup") {{ worldExportLocalFavoriteGroup }} ({{ localWorldFavorites[worldExportLocalFavoriteGroup].length }}) #[i.el-icon-arrow-down.el-icon--right] + span(v-else) Select Group #[i.el-icon-arrow-down.el-icon--right] + el-dropdown-menu(#default="dropdown") + el-dropdown-item(style="display:block;margin:10px 0" @click.native="selectWorldExportLocalGroup(null)") None + template(v-for="group in localWorldFavoriteGroups" :key="group") + el-dropdown-item(style="display:block;margin:10px 0" @click.native="selectWorldExportLocalGroup(group)") {{ group }} ({{ localWorldFavorites[group].length }}) br el-input(type="textarea" v-if="worldExportDialogVisible" v-model="worldExportContent" size="mini" rows="15" resize="none" readonly style="margin-top:15px" @click.native="$event.target.tagName === 'TEXTAREA' && $event.target.select()") @@ -3048,13 +3091,20 @@ html el-button(size="small" @click="processWorldImportList" :disabled="!worldImportDialog.input") Process List span(v-if="worldImportDialog.progress" style="margin-top:10px") #[i.el-icon-loading(style="margin-right:5px")] Progress: {{ worldImportDialog.progress }}/{{ worldImportDialog.progressTotal }} br - el-dropdown(@click.native.stop trigger="click" size="small") + el-dropdown(@click.native.stop trigger="click" size="small" style="margin-right:5px") el-button(size="mini") span(v-if="worldImportDialog.worldImportFavoriteGroup") {{ worldImportDialog.worldImportFavoriteGroup.displayName }} ({{ worldImportDialog.worldImportFavoriteGroup.count }}/{{ worldImportDialog.worldImportFavoriteGroup.capacity }}) #[i.el-icon-arrow-down.el-icon--right] span(v-else) Select Group #[i.el-icon-arrow-down.el-icon--right] el-dropdown-menu(#default="dropdown") template(v-for="groupAPI in API.favoriteWorldGroups" :key="groupAPI.name") el-dropdown-item(style="display:block;margin:10px 0" @click.native="selectWorldImportGroup(groupAPI)" :disabled="groupAPI.count >= groupAPI.capacity") {{ groupAPI.displayName }} ({{ groupAPI.count }}/{{ groupAPI.capacity }}) + el-dropdown(@click.native.stop trigger="click" size="small" style="margin:5px") + el-button(size="mini") + span(v-if="worldImportDialog.worldImportLocalFavoriteGroup") {{ worldImportDialog.worldImportLocalFavoriteGroup }} ({{ localWorldFavorites[worldImportDialog.worldImportLocalFavoriteGroup].length }}) #[i.el-icon-arrow-down.el-icon--right] + span(v-else) Select Group #[i.el-icon-arrow-down.el-icon--right] + el-dropdown-menu(#default="dropdown") + template(v-for="group in localWorldFavoriteGroups" :key="group") + el-dropdown-item(style="display:block;margin:10px 0" @click.native="selectWorldImportLocalGroup(group)" ) {{ group }} ({{ localWorldFavorites[group].length }}) el-button(size="small" @click="importWorldImportTable" style="margin:5px" :disabled="worldImportTable.data.length === 0 || !worldImportDialog.worldImportFavoriteGroup") Import Worlds el-button(v-if="worldImportDialog.loading" size="small" @click="cancelWorldImport" style="margin-top:10px") Cancel span(v-if="worldImportDialog.worldImportFavoriteGroup") {{ worldImportTable.data.length }} / {{ worldImportDialog.worldImportFavoriteGroup.capacity - worldImportDialog.worldImportFavoriteGroup.count }} @@ -3095,7 +3145,7 @@ html el-dropdown-menu(#default="dropdown") el-dropdown-item(style="display:block;margin:10px 0" @click.native="selectAvatarExportGroup(null)") All Favorites template(v-for="groupAPI in API.favoriteAvatarGroups" :key="groupAPI.name") - el-dropdown-item(style="display:block;margin:10px 0" @click.native="selectAvatarExportGroup(groupAPI)" :disabled="groupAPI.count >= groupAPI.capacity") {{ groupAPI.displayName }} ({{ groupAPI.count }}/{{ groupAPI.capacity }}) + el-dropdown-item(style="display:block;margin:10px 0" @click.native="selectAvatarExportGroup(groupAPI)") {{ groupAPI.displayName }} ({{ groupAPI.count }}/{{ groupAPI.capacity }}) br el-input(type="textarea" v-if="avatarExportDialogVisible" v-model="avatarExportContent" size="mini" rows="15" resize="none" readonly style="margin-top:15px" @click.native="$event.target.tagName === 'TEXTAREA' && $event.target.select()") @@ -3154,7 +3204,7 @@ html el-dropdown-menu(#default="dropdown") el-dropdown-item(style="display:block;margin:10px 0" @click.native="selectFriendExportGroup(null)") All Favorites template(v-for="groupAPI in API.favoriteFriendGroups" :key="groupAPI.name") - el-dropdown-item(style="display:block;margin:10px 0" @click.native="selectFriendExportGroup(groupAPI)" :disabled="groupAPI.count >= groupAPI.capacity") {{ groupAPI.displayName }} ({{ groupAPI.count }}/{{ groupAPI.capacity }}) + el-dropdown-item(style="display:block;margin:10px 0" @click.native="selectFriendExportGroup(groupAPI)") {{ groupAPI.displayName }} ({{ groupAPI.count }}/{{ groupAPI.capacity }}) br el-input(type="textarea" v-if="friendExportDialogVisible" v-model="friendExportContent" size="mini" rows="15" resize="none" readonly style="margin-top:15px" @click.native="$event.target.tagName === 'TEXTAREA' && $event.target.select()") diff --git a/html/src/repository/database.js b/html/src/repository/database.js index 290da1a0..8705b589 100644 --- a/html/src/repository/database.js +++ b/html/src/repository/database.js @@ -59,6 +59,12 @@ class Database { await sqliteService.executeNonQuery( `CREATE TABLE IF NOT EXISTS cache_avatar (id TEXT PRIMARY KEY, added_at TEXT, author_id TEXT, author_name TEXT, created_at TEXT, description TEXT, image_url TEXT, name TEXT, release_status TEXT, thumbnail_image_url TEXT, updated_at TEXT, version INTEGER)` ); + await sqliteService.executeNonQuery( + `CREATE TABLE IF NOT EXISTS cache_world (id TEXT PRIMARY KEY, added_at TEXT, author_id TEXT, author_name TEXT, created_at TEXT, description TEXT, image_url TEXT, name TEXT, release_status TEXT, thumbnail_image_url TEXT, updated_at TEXT, version INTEGER)` + ); + await sqliteService.executeNonQuery( + `CREATE TABLE IF NOT EXISTS favorite_world (id INTEGER PRIMARY KEY, created_at TEXT, world_id TEXT, group_name TEXT)` + ); } async getFeedDatabase() { @@ -1523,6 +1529,110 @@ class Database { sqliteService.executeNonQuery('DELETE FROM cache_avatar'); } + addWorldToCache(entry) { + sqliteService.executeNonQuery( + `INSERT OR REPLACE INTO cache_world (id, added_at, author_id, author_name, created_at, description, image_url, name, release_status, thumbnail_image_url, updated_at, version) VALUES (@id, @added_at, @author_id, @author_name, @created_at, @description, @image_url, @name, @release_status, @thumbnail_image_url, @updated_at, @version)`, + { + '@id': entry.id, + '@added_at': new Date().toJSON(), + '@author_id': entry.authorId, + '@author_name': entry.authorName, + '@created_at': entry.created_at, + '@description': entry.description, + '@image_url': entry.imageUrl, + '@name': entry.name, + '@release_status': entry.releaseStatus, + '@thumbnail_image_url': entry.thumbnailImageUrl, + '@updated_at': entry.updated_at, + '@version': entry.version + } + ); + } + + addWorldToFavorites(worldId, groupName) { + sqliteService.executeNonQuery( + 'INSERT OR REPLACE INTO favorite_world (world_id, group_name, created_at) VALUES (@world_id, @group_name, @created_at)', + { + '@world_id': worldId, + '@group_name': groupName, + '@created_at': new Date().toJSON() + } + ); + } + + renameWorldFavoriteGroup(newGroupName, groupName) { + sqliteService.executeNonQuery( + `UPDATE favorite_world SET group_name = @new_group_name WHERE group_name = @group_name`, + { + '@new_group_name': newGroupName, + '@group_name': groupName + } + ); + } + + deleteWorldFavoriteGroup(groupName) { + sqliteService.executeNonQuery( + `DELETE FROM favorite_world WHERE group_name = @group_name`, + { + '@group_name': groupName + } + ); + } + + removeWorldFromFavorites(worldId, groupName) { + sqliteService.executeNonQuery( + `DELETE FROM favorite_world WHERE world_id = @world_id AND group_name = @group_name`, + { + '@world_id': worldId, + '@group_name': groupName + } + ); + } + + removeWorldFromCache(worldId) { + sqliteService.executeNonQuery( + `DELETE FROM cache_world WHERE id = @world_id`, + { + '@world_id': worldId + } + ); + } + + async getWorldFavorites() { + var data = []; + await sqliteService.execute((dbRow) => { + var row = { + created_at: dbRow[1], + worldId: dbRow[2], + groupName: dbRow[3] + }; + data.push(row); + }, 'SELECT * FROM favorite_world'); + return data; + } + + async getWorldCache() { + var data = []; + await sqliteService.execute((dbRow) => { + var row = { + id: dbRow[0], + // added_at: dbRow[1], + authorId: dbRow[2], + authorName: dbRow[3], + created_at: dbRow[4], + description: dbRow[5], + imageUrl: dbRow[6], + name: dbRow[7], + releaseStatus: dbRow[8], + thumbnailImageUrl: dbRow[9], + updated_at: dbRow[10], + version: dbRow[11] + }; + data.push(row); + }, 'SELECT * FROM cache_world'); + return data; + } + async fixGameLogTraveling() { var travelingList = []; await sqliteService.execute((dbRow) => {