diff --git a/html/src/app.js b/html/src/app.js
index e303f152..21cc024b 100644
--- a/html/src/app.js
+++ b/html/src/app.js
@@ -19542,7 +19542,7 @@ speechSynthesis.getVoices();
D.isQuest = false;
D.isIos = false;
D.hasImposter = false;
- D.isFavorite = API.cachedFavoritesByObjectId.has(avatarId);
+ D.isFavorite = API.cachedFavoritesByObjectId.has(avatarId) || (this.isLocalUserVrcplusSupporter() && this.localAvatarFavoritesList.includes(avatarId));
D.isBlocked = API.cachedAvatarModerations.has(avatarId);
this.ignoreAvatarMemoSave = true;
D.memo = '';
@@ -19639,6 +19639,28 @@ speechSynthesis.getVoices();
});
};
+ $app.methods.selectAvatarWithConfirmation = function (id) {
+ this.$confirm(`Continue? Select Avatar`, 'Confirm', {
+ confirmButtonText: 'Confirm',
+ cancelButtonText: 'Cancel',
+ type: 'info',
+ callback: (action) => {
+ if (action !== 'confirm') {
+ return;
+ }
+ API.selectAvatar({
+ avatarId: id
+ }).then((args) => {
+ this.$message({
+ message: 'Avatar changed',
+ type: 'success'
+ });
+ return args;
+ });
+ }
+ });
+ }
+
$app.methods.avatarDialogCommand = function (command) {
var D = this.avatarDialog;
if (D.visible === false) {
@@ -28153,6 +28175,316 @@ speechSynthesis.getVoices();
this.worldFavoriteSearchResults = results;
};
+ // #endregion
+ // #region | App: Local Avatar Favorites
+
+ $app.methods.isLocalUserVrcplusSupporter = function () {
+ return API.currentUser.$isVRCPlus;
+ }
+
+ $app.data.localAvatarFavoriteGroups = [];
+ $app.data.localAvatarFavoritesList = [];
+ $app.data.localAvatarFavorites = {};
+
+ $app.methods.addLocalAvatarFavorite = function (avatarId, group) {
+ if (this.hasLocalAvatarFavorite(avatarId, group)) {
+ return;
+ }
+ var ref = API.cachedAvatars.get(avatarId);
+ if (typeof ref === 'undefined') {
+ return;
+ }
+ if (!this.localAvatarFavoritesList.includes(avatarId)) {
+ this.localAvatarFavoritesList.push(avatarId);
+ }
+ if (!this.localAvatarFavorites[group]) {
+ this.localAvatarFavorites[group] = [];
+ }
+ if (!this.localAvatarFavoriteGroups.includes(group)) {
+ this.localAvatarFavoriteGroups.push(group);
+ }
+ this.localAvatarFavorites[group].unshift(ref);
+ database.addAvatarToCache(ref);
+ database.addAvatarToFavorites(avatarId, group);
+ if (
+ this.favoriteDialog.visible &&
+ this.favoriteDialog.objectId === avatarId
+ ) {
+ this.updateFavoriteDialog(avatarId);
+ }
+ if (this.avatarDialog.visible && this.avatarDialog.id === avatarId) {
+ this.avatarDialog.isFavorite = true;
+ }
+ };
+
+ $app.methods.removeLocalAvatarFavorite = function (avatarId, group) {
+ var favoriteGroup = this.localAvatarFavorites[group];
+ for (var i = 0; i < favoriteGroup.length; ++i) {
+ if (favoriteGroup[i].id === avatarId) {
+ favoriteGroup.splice(i, 1);
+ }
+ }
+
+ // remove from cache if no longer in favorites
+ var avatarInFavorites = false;
+ for (var i = 0; i < this.localAvatarFavoriteGroups.length; ++i) {
+ var groupName = this.localAvatarFavoriteGroups[i];
+ if (!this.localAvatarFavorites[groupName] || group === groupName) {
+ continue;
+ }
+ for (
+ var j = 0;
+ j < this.localAvatarFavorites[groupName].length;
+ ++j
+ ) {
+ var id = this.localAvatarFavorites[groupName][j].id;
+ if (id === avatarId) {
+ avatarInFavorites = true;
+ break;
+ }
+ }
+ }
+ if (!avatarInFavorites) {
+ removeFromArray(this.localAvatarFavoritesList, avatarId);
+ database.removeAvatarFromCache(avatarId);
+ }
+ database.removeAvatarFromFavorites(avatarId, group);
+ if (
+ this.favoriteDialog.visible &&
+ this.favoriteDialog.objectId === avatarId
+ ) {
+ this.updateFavoriteDialog(avatarId);
+ }
+ if (this.avatarDialog.visible && this.avatarDialog.id === avatarId) {
+ this.avatarDialog.isFavorite =
+ API.cachedFavoritesByObjectId.has(avatarId);
+ }
+
+ // update UI
+ this.sortLocalAvatarFavorites();
+ };
+
+ API.$on('AVATAR', function (args) {
+ if ($app.localAvatarFavoritesList.includes(args.ref.id)) {
+ // update db cache
+ database.addAvatarToCache(args.ref);
+ }
+ });
+
+ API.$on('LOGIN', function () {
+ $app.getLocalAvatarFavorites();
+ });
+
+ $app.methods.getLocalAvatarFavorites = async function () {
+ this.localAvatarFavoriteGroups = [];
+ this.localAvatarFavoritesList = [];
+ this.localAvatarFavorites = {};
+ var avatarCache = await database.getAvatarCache();
+ for (var i = 0; i < avatarCache.length; ++i) {
+ var ref = avatarCache[i];
+ if (!API.cachedAvatars.has(ref.id)) {
+ API.applyAvatar(ref);
+ }
+ }
+ var favorites = await database.getAvatarFavorites();
+ for (var i = 0; i < favorites.length; ++i) {
+ var favorite = favorites[i];
+ if (!this.localAvatarFavoritesList.includes(favorite.avatarId)) {
+ this.localAvatarFavoritesList.push(favorite.avatarId);
+ }
+ if (!this.localAvatarFavorites[favorite.groupName]) {
+ this.localAvatarFavorites[favorite.groupName] = [];
+ }
+ if (!this.localAvatarFavoriteGroups.includes(favorite.groupName)) {
+ this.localAvatarFavoriteGroups.push(favorite.groupName);
+ }
+ var ref = API.cachedAvatars.get(favorite.avatarId);
+ if (typeof ref === 'undefined') {
+ ref = {
+ id: favorite.avatarId
+ };
+ }
+ this.localAvatarFavorites[favorite.groupName].unshift(ref);
+ }
+ if (this.localAvatarFavoriteGroups.length === 0) {
+ // default group
+ this.localAvatarFavorites.Favorites = [];
+ this.localAvatarFavoriteGroups.push('Favorites');
+ }
+ this.sortLocalAvatarFavorites();
+ };
+
+ $app.methods.hasLocalAvatarFavorite = function (avatarId, group) {
+ var favoriteGroup = this.localAvatarFavorites[group];
+ if (!favoriteGroup) {
+ return false;
+ }
+ for (var i = 0; i < favoriteGroup.length; ++i) {
+ if (favoriteGroup[i].id === avatarId) {
+ return true;
+ }
+ }
+ return false;
+ };
+
+ $app.methods.getLocalAvatarFavoriteGroupLength = function (group) {
+ var favoriteGroup = this.localAvatarFavorites[group];
+ if (!favoriteGroup) {
+ return 0;
+ }
+ return favoriteGroup.length;
+ };
+
+ $app.methods.promptNewLocalAvatarFavoriteGroup = function () {
+ this.$prompt(
+ $t('prompt.new_local_favorite_group.description'),
+ $t('prompt.new_local_favorite_group.header'),
+ {
+ distinguishCancelAndClose: true,
+ confirmButtonText: $t('prompt.new_local_favorite_group.ok'),
+ cancelButtonText: $t('prompt.new_local_favorite_group.cancel'),
+ inputPattern: /\S+/,
+ inputErrorMessage: $t(
+ 'prompt.new_local_favorite_group.input_error'
+ ),
+ callback: (action, instance) => {
+ if (action === 'confirm' && instance.inputValue) {
+ this.newLocalAvatarFavoriteGroup(instance.inputValue);
+ }
+ }
+ }
+ );
+ };
+
+ $app.methods.newLocalAvatarFavoriteGroup = function (group) {
+ if (this.localAvatarFavoriteGroups.includes(group)) {
+ $app.$message({
+ message: $t('prompt.new_local_favorite_group.message.error', {
+ name: group
+ }),
+ type: 'error'
+ });
+ return;
+ }
+ if (!this.localAvatarFavorites[group]) {
+ this.localAvatarFavorites[group] = [];
+ }
+ if (!this.localAvatarFavoriteGroups.includes(group)) {
+ this.localAvatarFavoriteGroups.push(group);
+ }
+ this.sortLocalAvatarFavorites();
+ };
+
+ $app.methods.promptLocalAvatarFavoriteGroupRename = function (group) {
+ this.$prompt(
+ $t('prompt.local_favorite_group_rename.description'),
+ $t('prompt.local_favorite_group_rename.header'),
+ {
+ distinguishCancelAndClose: true,
+ confirmButtonText: $t(
+ 'prompt.local_favorite_group_rename.save'
+ ),
+ cancelButtonText: $t(
+ 'prompt.local_favorite_group_rename.cancel'
+ ),
+ inputPattern: /\S+/,
+ inputErrorMessage: $t(
+ 'prompt.local_favorite_group_rename.input_error'
+ ),
+ inputValue: group,
+ callback: (action, instance) => {
+ if (action === 'confirm' && instance.inputValue) {
+ this.renameLocalAvatarFavoriteGroup(
+ instance.inputValue,
+ group
+ );
+ }
+ }
+ }
+ );
+ };
+
+ $app.methods.renameLocalAvatarFavoriteGroup = function (newName, group) {
+ if (this.localAvatarFavoriteGroups.includes(newName)) {
+ $app.$message({
+ message: $t(
+ 'prompt.local_favorite_group_rename.message.error',
+ { name: newName }
+ ),
+ type: 'error'
+ });
+ return;
+ }
+ this.localAvatarFavoriteGroups.push(newName);
+ this.localAvatarFavorites[newName] = this.localAvatarFavorites[group];
+
+ removeFromArray(this.localAvatarFavoriteGroups, group);
+ delete this.localAvatarFavorites[group];
+ database.renameAvatarFavoriteGroup(newName, group);
+ this.sortLocalAvatarFavorites();
+ };
+
+ $app.methods.promptLocalAvatarFavoriteGroupDelete = function (group) {
+ this.$confirm(`Delete Group? ${group}`, 'Confirm', {
+ confirmButtonText: 'Confirm',
+ cancelButtonText: 'Cancel',
+ type: 'info',
+ callback: (action) => {
+ if (action === 'confirm') {
+ this.deleteLocalAvatarFavoriteGroup(group);
+ }
+ }
+ });
+ };
+
+ $app.methods.sortLocalAvatarFavorites = function () {
+ this.localAvatarFavoriteGroups.sort();
+ if (!this.sortFavorites) {
+ for (var i = 0; i < this.localAvatarFavoriteGroups.length; ++i) {
+ var group = this.localAvatarFavoriteGroups[i];
+ if (this.localAvatarFavorites[group]) {
+ this.localAvatarFavorites[group].sort(compareByName);
+ }
+ }
+ }
+ };
+
+ $app.methods.deleteLocalAvatarFavoriteGroup = function (group) {
+ // remove from cache if no longer in favorites
+ var avatarIdRemoveList = new Set();
+ var favoriteGroup = this.localAvatarFavorites[group];
+ for (var i = 0; i < favoriteGroup.length; ++i) {
+ avatarIdRemoveList.add(favoriteGroup[i].id);
+ }
+
+ removeFromArray(this.localAvatarFavoriteGroups, group);
+ delete this.localAvatarFavorites[group];
+ database.deleteAvatarFavoriteGroup(group);
+
+ for (var i = 0; i < this.localAvatarFavoriteGroups.length; ++i) {
+ var groupName = this.localAvatarFavoriteGroups[i];
+ if (!this.localAvatarFavorites[groupName]) {
+ continue;
+ }
+ for (
+ var j = 0;
+ j < this.localAvatarFavorites[groupName].length;
+ ++j
+ ) {
+ var avatarId = this.localAvatarFavorites[groupName][j].id;
+ if (avatarIdRemoveList.has(avatarId)) {
+ avatarIdRemoveList.delete(avatarId);
+ break;
+ }
+ }
+ }
+
+ avatarIdRemoveList.forEach((id) => {
+ removeFromArray(this.localAvatarFavoritesList, id);
+ database.removeAvatarFromCache(id);
+ });
+ };
+
// #endregion
// #region | Local Favorite Friends
diff --git a/html/src/index.pug b/html/src/index.pug
index e41d9187..6fc68106 100644
--- a/html/src/index.pug
+++ b/html/src/index.pug
@@ -877,7 +877,7 @@ html
div(style="flex:none;margin-left:10px")
el-tooltip(v-if="avatarDialog.inCache" placement="top" :content="$t('dialog.avatar.actions.delete_cache_tooltip')" :disabled="hideTooltips")
el-button(icon="el-icon-delete" circle @click="deleteVRChatCache(avatarDialog.ref)" :disabled="isGameRunning && avatarDialog.cacheLocked")
- el-tooltip(v-if="avatarDialog.isFavorite" placement="top" :content="$t('dialog.avatar.actions.unfavorite_tooltip')" :disabled="hideTooltips")
+ el-tooltip(v-if="avatarDialog.isFavorite" placement="top" :content="$t('dialog.avatar.actions.favorite_tooltip')" :disabled="hideTooltips")
el-button(type="warning" icon="el-icon-star-on" circle @click="avatarDialogCommand('Add Favorite')" style="margin-left:5px")
el-tooltip(v-else placement="top" :content="$t('dialog.avatar.actions.favorite_tooltip')" :disabled="hideTooltips")
el-button(type="default" icon="el-icon-star-off" circle @click="avatarDialogCommand('Add Favorite')" style="margin-left:5px")
@@ -1312,6 +1312,11 @@ html
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 }} ({{ getLocalWorldFavoriteGroupLength(group) }})
el-button(v-else style="display:block;width:100%;margin:10px 0" @click="addLocalWorldFavorite(favoriteDialog.objectId, group)") {{ group }} ({{ getLocalWorldFavoriteGroupLength(group) }})
+ div(v-if="favoriteDialog.visible && favoriteDialog.type === 'avatar'" style="margin-top:20px")
+ span(style="display:block;text-align:center") {{ $t('dialog.favorite.local_avatar_favorites') }}
+ template(v-for="group in localAvatarFavoriteGroups" :key="group")
+ el-button(v-if="hasLocalAvatarFavorite(favoriteDialog.objectId, group)" style="display:block;width:100%;margin:10px 0" @click="removeLocalAvatarFavorite(favoriteDialog.objectId, group)") #[i.el-icon-check] {{ group }} ({{ getLocalAvatarFavoriteGroupLength(group) }})
+ el-button(v-else-if="isLocalUserVrcplusSupporter()" style="display:block;width:100%;margin:10px 0" @click="addLocalAvatarFavorite(favoriteDialog.objectId, group)") {{ group }} ({{ getLocalAvatarFavoriteGroupLength(group) }})
//- dialog: invite
el-dialog.x-dialog(:before-close="beforeDialogClose" @mousedown.native="dialogMouseDown" @mouseup.native="dialogMouseUp" ref="inviteDialog" :visible.sync="inviteDialog.visible" :title="$t('dialog.invite.header')" width="500px")
diff --git a/html/src/localization/en/en.json b/html/src/localization/en/en.json
index 796c96e1..5dcb79fd 100644
--- a/html/src/localization/en/en.json
+++ b/html/src/localization/en/en.json
@@ -93,7 +93,9 @@
"new_group": "New Group"
},
"avatars": {
- "header": "Avatars"
+ "header": "Avatars",
+ "local_favorites": "Local Favorites (VRC+ exclusive)",
+ "new_group": "New Group"
},
"bulk_unfavorite_mode": "Bulk Unfavorite Mode",
"bulk_unfavorite_selection": "Bulk Unfavorite Selection",
@@ -108,7 +110,8 @@
"delete_tooltip": "Delete",
"unavailable_tooltip": "Unavailable",
"private": "Private",
- "sort_by": "Sort By"
+ "sort_by": "Sort By",
+ "select_avatar_tooltip": "Select Avatar"
},
"friend_log": {
"filter_placeholder": "Filter",
@@ -774,7 +777,7 @@
},
"actions": {
"delete_cache_tooltip": "Delete avatar from cache",
- "favorite_tooltip": "Add to favorites",
+ "favorite_tooltip": "Favorites",
"unfavorite_tooltip": "Remove from favorites",
"refresh": "Refresh",
"select": "Select Avatar",
@@ -912,7 +915,8 @@
"favorite": {
"header": "Choose Group",
"vrchat_favorites": "VRChat Favorites",
- "local_favorites": "Local Favorites"
+ "local_favorites": "Local Favorites",
+ "local_avatar_favorites": "Local Favorites (VRC+ exclusive)"
},
"invite": {
"header": "Invite",
diff --git a/html/src/localization/es/en.json b/html/src/localization/es/en.json
index df417aac..b0074f45 100644
--- a/html/src/localization/es/en.json
+++ b/html/src/localization/es/en.json
@@ -770,7 +770,7 @@
},
"actions": {
"delete_cache_tooltip": "Eliminar avatar de la caché",
- "favorite_tooltip": "Añadir a favoritos",
+ "favorite_tooltip": "Favoritos",
"unfavorite_tooltip": "Quitar de favoritos",
"refresh": "Refrescar",
"select": "Seleccionar avatar",
diff --git a/html/src/localization/fr/en.json b/html/src/localization/fr/en.json
index f73cf825..4b7a301f 100644
--- a/html/src/localization/fr/en.json
+++ b/html/src/localization/fr/en.json
@@ -774,7 +774,7 @@
},
"actions": {
"delete_cache_tooltip": "Supprimer l'avatar du cache",
- "favorite_tooltip": "Ajouter aux favoris",
+ "favorite_tooltip": "Favoris",
"unfavorite_tooltip": "Supprimer des favoris",
"refresh": "Actualiser",
"select": "Sélectionner un avatar",
diff --git a/html/src/localization/ja/en.json b/html/src/localization/ja/en.json
index b10b7b33..b0ba5d69 100644
--- a/html/src/localization/ja/en.json
+++ b/html/src/localization/ja/en.json
@@ -739,7 +739,7 @@
},
"actions": {
"delete_cache_tooltip": "キャッシュからアバターを削除",
- "favorite_tooltip": "お気に入りに追加",
+ "favorite_tooltip": "お気に入り",
"unfavorite_tooltip": "お気に入りから削除",
"refresh": "更新",
"select": "アバターを使用",
diff --git a/html/src/localization/pl/en.json b/html/src/localization/pl/en.json
index cd1e2d36..8abbae5b 100644
--- a/html/src/localization/pl/en.json
+++ b/html/src/localization/pl/en.json
@@ -739,7 +739,7 @@
},
"actions": {
"delete_cache_tooltip": "Usuń świat z pamięci",
- "favorite_tooltip": "Dodaj do ulubionych",
+ "favorite_tooltip": "Ulubione",
"unfavorite_tooltip": "Usuń z ulubionych",
"refresh": "Odśwież",
"select": "Wybierz awatar",
diff --git a/html/src/localization/ru/en.json b/html/src/localization/ru/en.json
index 751404e0..8bb8a8da 100644
--- a/html/src/localization/ru/en.json
+++ b/html/src/localization/ru/en.json
@@ -770,7 +770,7 @@
},
"actions": {
"delete_cache_tooltip": "Удалить аватар из кэша",
- "favorite_tooltip": "Добавить в избранное",
+ "favorite_tooltip": "Избранное",
"unfavorite_tooltip": "Убрать из Избранного",
"refresh": "Обновить",
"select": "Выбрать аватар",
diff --git a/html/src/localization/vi/en.json b/html/src/localization/vi/en.json
index 7f094493..66e1457f 100644
--- a/html/src/localization/vi/en.json
+++ b/html/src/localization/vi/en.json
@@ -734,7 +734,7 @@
},
"actions": {
"delete_cache_tooltip": "Xóa avatar trong cache",
- "favorite_tooltip": "Thêm vào yêu thích",
+ "favorite_tooltip": "Yêu thích",
"unfavorite_tooltip": "Bỏ khỏi yêu thích",
"refresh": "Làm mới",
"select": "Chọn Avatar",
diff --git a/html/src/localization/zh-CN/en.json b/html/src/localization/zh-CN/en.json
index 40984762..51ba5af2 100644
--- a/html/src/localization/zh-CN/en.json
+++ b/html/src/localization/zh-CN/en.json
@@ -766,7 +766,7 @@
},
"actions": {
"delete_cache_tooltip": "从缓存中删除模型",
- "favorite_tooltip": "添加到我的收藏",
+ "favorite_tooltip": "收藏此世界",
"unfavorite_tooltip": "从我的收藏中移除",
"refresh": "刷新",
"select": "选择模型",
diff --git a/html/src/localization/zh-TW/en.json b/html/src/localization/zh-TW/en.json
index f8e6cf7a..e1a41b74 100644
--- a/html/src/localization/zh-TW/en.json
+++ b/html/src/localization/zh-TW/en.json
@@ -734,7 +734,7 @@
},
"actions": {
"delete_cache_tooltip": "從快取中刪除角色",
- "favorite_tooltip": "新增到我的收藏",
+ "favorite_tooltip": "最愛欄",
"unfavorite_tooltip": "從我的收藏中移除",
"refresh": "重新整理",
"select": "選擇角色",
diff --git a/html/src/mixins/tabs/favorites.pug b/html/src/mixins/tabs/favorites.pug
index 8b51f0b6..92df51b7 100644
--- a/html/src/mixins/tabs/favorites.pug
+++ b/html/src/mixins/tabs/favorites.pug
@@ -182,6 +182,8 @@ mixin favoritesTab()
i.el-icon-warning(style="color:#f56c6c;margin-left:5px")
el-tooltip(v-if="favorite.ref.releaseStatus === 'private'" placement="left" :content="$t('view.favorite.private')")
i.el-icon-warning(style="color:#e6a23c;margin-left:5px")
+ el-tooltip(v-if="favorite.ref.releaseStatus !== 'private' && !favorite.deleted" placement="right" :content="$t('view.favorite.select_avatar_tooltip')" :disabled="hideTooltips")
+ el-button(@click.stop="selectAvatarWithConfirmation(favorite.id)" :disabled="API.currentUser.currentAvatar === favorite.id" size="mini" icon="el-icon-check" circle style="margin-left:5px")
el-tooltip(placement="left" :content="$t('view.favorite.move_tooltip')" :disabled="hideTooltips")
el-dropdown(trigger="click" @click.native.stop size="mini" style="margin-left:5px")
el-button(type="default" icon="el-icon-back" size="mini" circle)
@@ -209,9 +211,39 @@ mixin favoritesTab()
.detail
span.name(v-text="favorite.name")
span.extra(v-text="favorite.authorName")
+ el-tooltip(placement="right" :content="$t('view.favorite.select_avatar_tooltip')" :disabled="hideTooltips")
+ el-button(@click.stop="selectAvatarWithConfirmation(favorite.id)" :disabled="API.currentUser.currentAvatar === favorite.id" size="mini" icon="el-icon-check" circle style="margin-left:5px")
template(v-if="API.cachedFavoritesByObjectId.has(favorite.id)")
el-tooltip(placement="left" content="Unfavorite" :disabled="hideTooltips")
el-button(@click.stop="deleteFavorite(favorite.id)" type="default" icon="el-icon-star-on" size="mini" circle)
template(v-else)
el-tooltip(placement="left" content="Favorite" :disabled="hideTooltips")
el-button(@click.stop="showFavoriteDialog('avatar', favorite.id)" type="default" icon="el-icon-star-off" size="mini" circle)
+ span(style="display:block;margin-top:20px") {{ $t('view.favorite.avatars.local_favorites') }}
+ el-button(size="small" :disabled="!isLocalUserVrcplusSupporter()" @click="promptNewLocalAvatarFavoriteGroup" style="display:block;margin-top:10px") {{ $t('view.favorite.avatars.new_group') }}
+ el-collapse-item(v-for="group in localAvatarFavoriteGroups" v-if="localAvatarFavorites[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") {{ getLocalAvatarFavoriteGroupLength(group) }}
+ el-tooltip(placement="top" :content="$t('view.favorite.rename_tooltip')" :disabled="hideTooltips")
+ el-button(@click.stop="promptLocalAvatarFavoriteGroupRename(group)" size="mini" icon="el-icon-edit" circle style="margin-left:10px")
+ el-tooltip(placement="right" :content="$t('view.favorite.delete_tooltip')" :disabled="hideTooltips")
+ el-button(@click.stop="promptLocalAvatarFavoriteGroupDelete(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 localAvatarFavorites[group]" :key="favorite.id" @click="showAvatarDialog(favorite.id)")
+ .x-friend-item
+ template(v-if="favorite.name")
+ .avatar
+ img(v-lazy="favorite.thumbnailImageUrl")
+ .detail
+ span.name(v-text="favorite.name")
+ span.extra(v-text="favorite.authorName")
+ el-tooltip(placement="right" :content="$t('view.favorite.select_avatar_tooltip')" :disabled="hideTooltips")
+ el-button(@click.stop="selectAvatarWithConfirmation(favorite.id)" :disabled="API.currentUser.currentAvatar === favorite.id" size="mini" icon="el-icon-check" circle style="margin-left:5px")
+ el-tooltip(placement="right" :content="$t('view.favorite.unfavorite_tooltip')" :disabled="hideTooltips")
+ el-button(@click.stop="removeLocalAvatarFavorite(favorite.id, group)" size="mini" icon="el-icon-delete" circle style="margin-left:5px")
+ template(v-else)
+ .avatar
+ .detail
+ span(v-text="favorite.id")
+ el-button(type="text" icon="el-icon-close" size="mini" @click.stop="removeLocalAvatarFavorite(favorite.id, group)" style="margin-left:5px")
diff --git a/html/src/repository/database.js b/html/src/repository/database.js
index 90b3b4ef..d51c0a45 100644
--- a/html/src/repository/database.js
+++ b/html/src/repository/database.js
@@ -80,6 +80,9 @@ class Database {
await sqliteService.executeNonQuery(
`CREATE TABLE IF NOT EXISTS favorite_world (id INTEGER PRIMARY KEY, created_at TEXT, world_id TEXT, group_name TEXT)`
);
+ await sqliteService.executeNonQuery(
+ `CREATE TABLE IF NOT EXISTS favorite_avatar (id INTEGER PRIMARY KEY, created_at TEXT, avatar_id TEXT, group_name TEXT)`
+ );
}
async getFeedDatabase() {
@@ -2216,6 +2219,110 @@ class Database {
sqliteService.executeNonQuery('DELETE FROM cache_avatar');
}
+ addAvatarToFavorites(avatarId, groupName) {
+ sqliteService.executeNonQuery(
+ 'INSERT OR REPLACE INTO favorite_avatar (avatar_id, group_name, created_at) VALUES (@avatar_id, @group_name, @created_at)',
+ {
+ '@avatar_id': avatarId,
+ '@group_name': groupName,
+ '@created_at': new Date().toJSON()
+ }
+ );
+ }
+
+ renameAvatarFavoriteGroup(newGroupName, groupName) {
+ sqliteService.executeNonQuery(
+ `UPDATE favorite_avatar SET group_name = @new_group_name WHERE group_name = @group_name`,
+ {
+ '@new_group_name': newGroupName,
+ '@group_name': groupName
+ }
+ );
+ }
+
+ deleteAvatarFavoriteGroup(groupName) {
+ sqliteService.executeNonQuery(
+ `DELETE FROM favorite_avatar WHERE group_name = @group_name`,
+ {
+ '@group_name': groupName
+ }
+ );
+ }
+
+ removeAvatarFromFavorites(avatarId, groupName) {
+ sqliteService.executeNonQuery(
+ `DELETE FROM favorite_avatar WHERE avatar_id = @avatar_id AND group_name = @group_name`,
+ {
+ '@avatar_id': avatarId,
+ '@group_name': groupName
+ }
+ );
+ }
+
+ async getAvatarFavorites() {
+ var data = [];
+ await sqliteService.execute((dbRow) => {
+ var row = {
+ created_at: dbRow[1],
+ avatarId: dbRow[2],
+ groupName: dbRow[3]
+ };
+ data.push(row);
+ }, 'SELECT * FROM favorite_avatar');
+ return data;
+ }
+
+ removeAvatarFromCache(avatarId) {
+ sqliteService.executeNonQuery(
+ `DELETE FROM cache_avatar WHERE id = @avatar_id`,
+ {
+ '@avatar_id': avatarId
+ }
+ );
+ }
+
+ addAvatarToCache(entry) {
+ sqliteService.executeNonQuery(
+ `INSERT OR REPLACE INTO cache_avatar (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
+ }
+ );
+ }
+
+ async getAvatarCache() {
+ 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_avatar');
+ return data;
+ }
+
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)`,
@@ -2276,15 +2383,6 @@ class Database {
);
}
- removeWorldFromCache(worldId) {
- sqliteService.executeNonQuery(
- `DELETE FROM cache_world WHERE id = @world_id`,
- {
- '@world_id': worldId
- }
- );
- }
-
async getWorldFavorites() {
var data = [];
await sqliteService.execute((dbRow) => {
@@ -2298,6 +2396,15 @@ class Database {
return data;
}
+ removeWorldFromCache(worldId) {
+ sqliteService.executeNonQuery(
+ `DELETE FROM cache_world WHERE id = @world_id`,
+ {
+ '@world_id': worldId
+ }
+ );
+ }
+
async getWorldCache() {
var data = [];
await sqliteService.execute((dbRow) => {