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) => {