mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-19 06:43:51 +02:00
refactor: dialogs (#1224)
* refactor: dialogs * fix: storeAvatarImage * FriendLog.vue * FriendLog.vue * FriendLog.vue * GameLog.vue * fix: next day button jumping to the wrong date * sync master * fix: launchGame * Notification.vue * Feed.vue * Search.vue * Profile.vue * PlayerList.vue * Login.vue * utils * update dialog * del gameLog.pug * fix * fix: group role cannot be displayed currently * fix: "Hide Friends in Same Instance" hides players in unrelated private instances (#1210) * fix * fix: "Hide Friends in Same Instance" does not work when "Split Favorite Friends" is enabled * fix Notification.vue message * fix: deleteFavoriteNoConfirm * fix: feed status style * fix: infinite loading when deleting note * fix: private players will not be hidden when 'Hide Friends in Same Instance', and 'Hide Friends in Same Instance' will not work when 'Split Favorite Friends'
This commit is contained in:
@@ -49,7 +49,7 @@ const friendReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('FRIEND:REQUEST:CANCEL', args);
|
// window.API.$emit('FRIEND:REQUEST:CANCEL', args);
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ const imageReq = {
|
|||||||
method: 'PUT'
|
method: 'PUT'
|
||||||
});
|
});
|
||||||
window.$app.avatarDialog.loading = false;
|
window.$app.avatarDialog.loading = false;
|
||||||
window.$app.changeAvatarImageDialogLoading = false;
|
// window.$app.changeAvatarImageDialogLoading = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
async uploadAvatarImage(params, fileId) {
|
async uploadAvatarImage(params, fileId) {
|
||||||
@@ -28,14 +28,15 @@ const imageReq = {
|
|||||||
params,
|
params,
|
||||||
fileId
|
fileId
|
||||||
};
|
};
|
||||||
window.API.$emit('AVATARIMAGE:INIT', args);
|
// window.API.$emit('AVATARIMAGE:INIT', args);
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
window.API.uploadAvatarFailCleanup(fileId);
|
imageReq.uploadAvatarFailCleanup(fileId);
|
||||||
|
throw err;
|
||||||
}
|
}
|
||||||
return void 0;
|
// return void 0;
|
||||||
},
|
},
|
||||||
|
|
||||||
async uploadAvatarImageFileStart(params) {
|
async uploadAvatarImageFileStart(params) {
|
||||||
@@ -50,12 +51,12 @@ const imageReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('AVATARIMAGE:FILESTART', args);
|
// window.API.$emit('AVATARIMAGE:FILESTART', args);
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
window.API.uploadAvatarFailCleanup(params.fileId);
|
imageReq.uploadAvatarFailCleanup(params.fileId);
|
||||||
}
|
}
|
||||||
return void 0;
|
return void 0;
|
||||||
},
|
},
|
||||||
@@ -75,7 +76,7 @@ const imageReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('AVATARIMAGE:FILEFINISH', args);
|
// window.API.$emit('AVATARIMAGE:FILEFINISH', args);
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -92,12 +93,12 @@ const imageReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('AVATARIMAGE:SIGSTART', args);
|
// window.API.$emit('AVATARIMAGE:SIGSTART', args);
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
window.API.uploadAvatarFailCleanup(params.fileId);
|
imageReq.uploadAvatarFailCleanup(params.fileId);
|
||||||
}
|
}
|
||||||
return void 0;
|
return void 0;
|
||||||
},
|
},
|
||||||
@@ -117,7 +118,7 @@ const imageReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('AVATARIMAGE:SIGFINISH', args);
|
// window.API.$emit('AVATARIMAGE:SIGFINISH', args);
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -131,7 +132,7 @@ const imageReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('AVATARIMAGE:SET', args);
|
// window.API.$emit('AVATARIMAGE:SET', args);
|
||||||
window.API.$emit('AVATAR', args);
|
window.API.$emit('AVATAR', args);
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
@@ -152,7 +153,7 @@ const imageReq = {
|
|||||||
method: 'PUT'
|
method: 'PUT'
|
||||||
});
|
});
|
||||||
window.$app.worldDialog.loading = false;
|
window.$app.worldDialog.loading = false;
|
||||||
window.$app.changeWorldImageDialogLoading = false;
|
// window.$app.changeWorldImageDialogLoading = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
async uploadWorldImage(params, fileId) {
|
async uploadWorldImage(params, fileId) {
|
||||||
@@ -166,12 +167,12 @@ const imageReq = {
|
|||||||
params,
|
params,
|
||||||
fileId
|
fileId
|
||||||
};
|
};
|
||||||
window.API.$emit('WORLDIMAGE:INIT', args);
|
// window.API.$emit('WORLDIMAGE:INIT', args);
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
window.API.uploadWorldFailCleanup(fileId);
|
imageReq.uploadWorldFailCleanup(fileId);
|
||||||
}
|
}
|
||||||
return void 0;
|
return void 0;
|
||||||
},
|
},
|
||||||
@@ -188,12 +189,12 @@ const imageReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('WORLDIMAGE:FILESTART', args);
|
// window.API.$emit('WORLDIMAGE:FILESTART', args);
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
window.API.uploadWorldFailCleanup(params.fileId);
|
imageReq.uploadWorldFailCleanup(params.fileId);
|
||||||
}
|
}
|
||||||
return void 0;
|
return void 0;
|
||||||
},
|
},
|
||||||
@@ -213,7 +214,7 @@ const imageReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('WORLDIMAGE:FILEFINISH', args);
|
// window.API.$emit('WORLDIMAGE:FILEFINISH', args);
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -230,12 +231,12 @@ const imageReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('WORLDIMAGE:SIGSTART', args);
|
// window.API.$emit('WORLDIMAGE:SIGSTART', args);
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
window.API.uploadWorldFailCleanup(params.fileId);
|
imageReq.uploadWorldFailCleanup(params.fileId);
|
||||||
}
|
}
|
||||||
return void 0;
|
return void 0;
|
||||||
},
|
},
|
||||||
@@ -255,7 +256,7 @@ const imageReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('WORLDIMAGE:SIGFINISH', args);
|
// window.API.$emit('WORLDIMAGE:SIGFINISH', args);
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -269,7 +270,7 @@ const imageReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('WORLDIMAGE:SET', args);
|
// window.API.$emit('WORLDIMAGE:SET', args);
|
||||||
window.API.$emit('WORLD', args);
|
window.API.$emit('WORLD', args);
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
@@ -283,7 +284,7 @@ const imageReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('AVATARIMAGE:GET', args);
|
// window.API.$emit('AVATARIMAGE:GET', args);
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -297,7 +298,7 @@ const imageReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('WORLDIMAGE:GET', args);
|
// window.API.$emit('WORLDIMAGE:GET', args);
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const miscReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('NOTE', args);
|
// window.API.$emit('NOTE', args);
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -46,7 +46,7 @@ const miscReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('FEEDBACK:REPORT:USER', args);
|
// window.API.$emit('FEEDBACK:REPORT:USER', args);
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -81,7 +81,7 @@ const miscReq = {
|
|||||||
const args = {
|
const args = {
|
||||||
json
|
json
|
||||||
};
|
};
|
||||||
window.API.$emit('VRCCREDITS', args);
|
// window.API.$emit('VRCCREDITS', args);
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -170,10 +170,43 @@ const miscReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('BADGE:UPDATE', args);
|
// window.API.$emit('BADGE:UPDATE', args);
|
||||||
|
return args;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getVisits() {
|
||||||
|
return window.API.call('visits', {
|
||||||
|
method: 'GET'
|
||||||
|
}).then((json) => {
|
||||||
|
const args = {
|
||||||
|
json
|
||||||
|
};
|
||||||
|
// window.API.$emit('VISITS', args);
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * @params {{
|
||||||
|
// userId: string,
|
||||||
|
// emojiId: string
|
||||||
|
// }} params
|
||||||
|
// * @returns {Promise<{json: any, params}>}
|
||||||
|
// */
|
||||||
|
// sendBoop(params) {
|
||||||
|
// return window.API.call(`users/${params.userId}/boop`, {
|
||||||
|
// method: 'POST',
|
||||||
|
// params
|
||||||
|
// }).then((json) => {
|
||||||
|
// const args = {
|
||||||
|
// json,
|
||||||
|
// params
|
||||||
|
// };
|
||||||
|
// this.$emit('BOOP:SEND', args);
|
||||||
|
// return args;
|
||||||
|
// });
|
||||||
|
// }
|
||||||
};
|
};
|
||||||
|
|
||||||
export default miscReq;
|
export default miscReq;
|
||||||
|
|||||||
@@ -265,7 +265,8 @@ const notificationReq = {
|
|||||||
notificationId
|
notificationId
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
window.API.$emit('NOTIFICATION:V2:HIDE', args);
|
// useless
|
||||||
|
// window.API.$emit('NOTIFICATION:V2:HIDE', args);
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ const playerModerationReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('PLAYER-MODERATION:SEND', args);
|
// window.API.$emit('PLAYER-MODERATION:SEND', args);
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -126,7 +126,33 @@ const userReq = {
|
|||||||
json,
|
json,
|
||||||
params
|
params
|
||||||
};
|
};
|
||||||
window.API.$emit('USER:FEEDBACK', args);
|
// window.API.$emit('USER:FEEDBACK', args);
|
||||||
|
return args;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {{
|
||||||
|
* status: 'active' | 'offline' | 'busy' | 'ask me' | 'join me',
|
||||||
|
* statusDescription: string
|
||||||
|
* }} SaveCurrentUserParameters
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates current user's status.
|
||||||
|
* @param params {SaveCurrentUserParameters} new status to be set
|
||||||
|
* @returns {Promise<{json: any, params}>}
|
||||||
|
*/
|
||||||
|
saveCurrentUser(params) {
|
||||||
|
return window.API.call(`users/${window.API.currentUser.id}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
params
|
||||||
|
}).then((json) => {
|
||||||
|
var args = {
|
||||||
|
json,
|
||||||
|
params
|
||||||
|
};
|
||||||
|
window.API.$emit('USER:CURRENT:SAVE', args);
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ const vrcPlusImageReq = {
|
|||||||
json,
|
json,
|
||||||
printId
|
printId
|
||||||
};
|
};
|
||||||
window.API.$emit('PRINT:DELETE', args);
|
// window.API.$emit('PRINT:DELETE', args);
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
4994
src/app.js
4994
src/app.js
File diff suppressed because it is too large
Load Diff
126
src/app.pug
126
src/app.pug
@@ -1,8 +1,8 @@
|
|||||||
doctype html
|
doctype html
|
||||||
#x-app.x-app(@dragenter.prevent @dragover.prevent @drop.prevent)
|
#x-app.x-app(@dragenter.prevent @dragover.prevent @drop.prevent)
|
||||||
//- login
|
LoginPage(v-if="!API.isLoggedIn" v-bind="loginPageBind" v-on="loginPageEvent")
|
||||||
include ./mixins/loginPage.pug
|
|
||||||
+loginPage
|
VRCXUpdateDialog(v-bind="vrcxUpdateDialogBind" v-on="vrcxUpdateDialogEvent")
|
||||||
|
|
||||||
//- menu
|
//- menu
|
||||||
.x-menu-container
|
.x-menu-container
|
||||||
@@ -23,47 +23,30 @@ doctype html
|
|||||||
circle
|
circle
|
||||||
style='font-size: 14px; height: 50px; width: 50px')
|
style='font-size: 14px; height: 50px; width: 50px')
|
||||||
|
|
||||||
nav-menu(ref='menu' @select='selectMenu' :menu-active-index='menuActiveIndex')
|
NavMenu(ref='menu' @select='selectMenu' :menu-active-index='menuActiveIndex')
|
||||||
|
|
||||||
//- ### Tabs ###
|
//- ### Tabs ###
|
||||||
template(v-if='API.isLoggedIn')
|
template(v-if='API.isLoggedIn')
|
||||||
//- feed
|
FeedTab(v-bind='feedTabBind' v-on='feedTabEvent')
|
||||||
include ./mixins/tabs/feed.pug
|
|
||||||
+feedTab
|
|
||||||
|
|
||||||
//- gameLog
|
GameLogTab(v-bind='gameLogTabBind' v-on='gameLogTabEvent')
|
||||||
include ./mixins/tabs/gameLog.pug
|
|
||||||
+gameLogTab
|
|
||||||
|
|
||||||
//- playerList
|
PlayerListTab(v-bind='playerListTabBind' v-on='playerListTabEvent')
|
||||||
include ./mixins/tabs/playerList.pug
|
|
||||||
+playerListTab
|
|
||||||
|
|
||||||
//- search
|
SearchTab(v-bind='searchTabBind' v-on='searchTabEvent')
|
||||||
include ./mixins/tabs/search.pug
|
|
||||||
+searchTab
|
|
||||||
|
|
||||||
FavoritesTab(v-bind='favoritesTabBind' v-on='favoritesTabEvent')
|
FavoritesTab(v-bind='favoritesTabBind' v-on='favoritesTabEvent')
|
||||||
|
|
||||||
//- friendLog
|
FriendLogTab(v-bind='friendLogTabBind')
|
||||||
include ./mixins/tabs/friendLog.pug
|
|
||||||
+friendLogTab
|
|
||||||
|
|
||||||
//- moderation
|
|
||||||
ModerationTab(v-bind='moderationTabBind')
|
ModerationTab(v-bind='moderationTabBind')
|
||||||
|
|
||||||
//- notification
|
NotificationTab(v-bind='notificationTabBind' v-on='notificationTabEvent')
|
||||||
include ./mixins/tabs/notifications.pug
|
|
||||||
+notificationsTab
|
|
||||||
|
|
||||||
//- profile
|
ProfileTab(v-bind='profileTabBind' v-on='profileTabEvent')
|
||||||
include ./mixins/tabs/profile.pug
|
|
||||||
+profileTab
|
|
||||||
|
|
||||||
//- friends list
|
|
||||||
FriendListTab(v-bind='friendsListTabBind' v-on='friendsListTabEvent')
|
FriendListTab(v-bind='friendsListTabBind' v-on='friendsListTabEvent')
|
||||||
|
|
||||||
//- charts
|
|
||||||
KeepAlive
|
KeepAlive
|
||||||
ChartsTab(v-if='menuActiveIndex === "charts"' v-bind='chartsTabBind' v-on='chartsTabEvent')
|
ChartsTab(v-if='menuActiveIndex === "charts"' v-bind='chartsTabBind' v-on='chartsTabEvent')
|
||||||
|
|
||||||
@@ -73,89 +56,8 @@ doctype html
|
|||||||
|
|
||||||
SideBar(v-bind='sideBarTabBind' v-on='sideBarTabEvent')
|
SideBar(v-bind='sideBarTabBind' v-on='sideBarTabEvent')
|
||||||
|
|
||||||
//- ## Dialogs ## -\\
|
//- ## Dialogs ## -\\
|
||||||
include ./mixins/dialogs/userDialog.pug
|
include ./mixins/dialogs/dialogs.pug
|
||||||
+userDialog
|
+dialogs
|
||||||
|
|
||||||
include ./mixins/dialogs/images.pug
|
|
||||||
+images
|
|
||||||
|
|
||||||
include ./mixins/dialogs/currentUser.pug
|
|
||||||
+currentUser
|
|
||||||
|
|
||||||
include ./mixins/dialogs/invites.pug
|
|
||||||
+invites
|
|
||||||
|
|
||||||
include ./mixins/dialogs/boops.pug
|
|
||||||
+boops
|
|
||||||
|
|
||||||
//- previous instances
|
|
||||||
PreviousInstancesInfoDialog(v-bind='previousInstancesInfoDialogBind' v-on='previousInstancesInfoDialogEvent')
|
|
||||||
|
|
||||||
PreviousInstancesUserDialog(v-bind='previousInstancesUserDialogBind' v-on='previousInstancesUserDialogEvent')
|
|
||||||
|
|
||||||
//- favorites
|
|
||||||
FriendImportDialog(v-bind='friendImportDialogBind' v-on='friendImportDialogEvent')
|
|
||||||
|
|
||||||
WorldImportDialog(v-bind='worldImportDialogBind' v-on='worldImportDialogEvent')
|
|
||||||
|
|
||||||
AvatarImportDialog(v-bind='avatarImportDialogBind' v-on='avatarImportDialogEvent')
|
|
||||||
|
|
||||||
//- favorites dialog
|
|
||||||
ChooseFavoriteGroupDialog(v-bind='favoriteDialogBind' v-on='favoriteDialogEvent')
|
|
||||||
|
|
||||||
ExportFriendsListDialog(v-bind='exportFriendsListDialogBind' v-on='exportFriendsListDialogEvent')
|
|
||||||
|
|
||||||
ExportAvatarsListDialog(v-bind='exportAvatarsListDialogBind' v-on='exportAvatarsListDialogEvent')
|
|
||||||
|
|
||||||
//- launch
|
|
||||||
LaunchDialog(v-bind='launchDialogBind' v-on='launchDialogEvent')
|
|
||||||
|
|
||||||
//- world
|
|
||||||
WorldDialog(v-bind='worldDialogBind' v-on='worldDialogEvent')
|
|
||||||
|
|
||||||
//- group
|
|
||||||
GroupDialog(v-bind='groupDialogBind' v-on='groupDialogEvent')
|
|
||||||
|
|
||||||
InviteGroupDialog(v-bind='inviteGroupDialogBind' v-on='inviteGroupDialogEvent')
|
|
||||||
|
|
||||||
//- avatar
|
|
||||||
AvatarDialog(v-bind='avatarDialogBind' v-on='avatarDialogEvent')
|
|
||||||
|
|
||||||
//- settings
|
|
||||||
FeedFiltersDialog(v-bind='feedFiltersDialogBind' v-on='feedFiltersDialogEvent')
|
|
||||||
|
|
||||||
LaunchOptionsDialog(:is-launch-options-dialog-visible.sync='isLaunchOptionsDialogVisible')
|
|
||||||
|
|
||||||
OpenSourceSoftwareNoticeDialog(:oss-dialog.sync='ossDialog')
|
|
||||||
|
|
||||||
ChangelogDialog(:change-log-dialog.sync='changeLogDialog')
|
|
||||||
|
|
||||||
VRCXUpdateDialog(v-bind='vrcxUpdateDialogBind' v-on='vrcxUpdateDialogEvent')
|
|
||||||
|
|
||||||
ScreenshotMetadataDialog(v-bind='screenshotMetadataDialogBind' v-on='screenshotMetadataDialogEvent')
|
|
||||||
|
|
||||||
DiscordNamesDialog(:discord-names-dialog-visible.sync='discordNamesDialogVisible' :friends='friends')
|
|
||||||
|
|
||||||
EditInviteMessageDialog(:edit-invite-message-dialog.sync='editInviteMessageDialog')
|
|
||||||
|
|
||||||
NoteExportDialog(:is-note-export-dialog-visible.sync='isNoteExportDialogVisible' :friends='friends')
|
|
||||||
|
|
||||||
VRChatConfigDialog(v-bind='vrchatConfigDialogBind' v-on='vrchatConfigDialogEvent')
|
|
||||||
|
|
||||||
YouTubeApiDialog(v-bind='youTubeApiDialogBind' v-on='youTubeApiDialogEvent')
|
|
||||||
|
|
||||||
NotificationPositionDialog(v-bind='notificationPositionDialogBind' v-on='notificationPositionDialogEvent')
|
|
||||||
|
|
||||||
AvatarProviderDialog(v-bind='avatarProviderDialogBind' v-on='avatarProviderDialogEvent')
|
|
||||||
|
|
||||||
RegistryBackupDialog(
|
|
||||||
:isRegistryBackupDialogVisible.sync='isRegistryBackupDialogVisible'
|
|
||||||
:backupVrcRegistry='backupVrcRegistry')
|
|
||||||
|
|
||||||
PrimaryPasswordDialog(:enablePrimaryPasswordDialog.sync='enablePrimaryPasswordDialog' @setPrimaryPassword="setPrimaryPassword")
|
|
||||||
|
|
||||||
//- player list
|
|
||||||
ChatboxBlacklistDialog(:chatboxBlacklistDialog="chatboxBlacklistDialog" :chatboxUserBlacklist="chatboxUserBlacklist" @deleteChatboxUserBlacklist="deleteChatboxUserBlacklist")
|
|
||||||
|
|
||||||
//- el-dialog.x-dialog(:before-close="beforeDialogClose" @mousedown.native="dialogMouseDown" @mouseup.native="dialogMouseUp" ref="templateDialog" :visible.sync="templateDialog.visible" :title="$t('dialog.template_dialog.header')" width="450px")
|
//- el-dialog.x-dialog(:before-close="beforeDialogClose" @mousedown.native="dialogMouseDown" @mouseup.native="dialogMouseUp" ref="templateDialog" :visible.sync="templateDialog.visible" :title="$t('dialog.template_dialog.header')" width="450px")
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ a {
|
|||||||
display: flex;
|
display: flex;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
overflow: hidden auto;
|
overflow: hidden;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -590,6 +590,10 @@ input[type='number'],
|
|||||||
.el-table table tr td:first-child {
|
.el-table table tr td:first-child {
|
||||||
border-left: none;
|
border-left: none;
|
||||||
}
|
}
|
||||||
|
.feed .el-table .el-table_1_column_5.el-table__cell > div {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
.el-table .el-table__body-wrapper table tr:last-child th,
|
.el-table .el-table__body-wrapper table tr:last-child th,
|
||||||
.el-table table tr:last-child td,
|
.el-table table tr:last-child td,
|
||||||
.el-table tr,
|
.el-table tr,
|
||||||
|
|||||||
@@ -239,13 +239,12 @@ export default class extends baseClass {
|
|||||||
API.websocketDomain = API.websocketDomainVrchat;
|
API.websocketDomain = API.websocketDomainVrchat;
|
||||||
}
|
}
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
this.loginForm.loading = true;
|
||||||
if (this.enablePrimaryPassword) {
|
if (this.enablePrimaryPassword) {
|
||||||
this.checkPrimaryPassword(loginParmas)
|
this.checkPrimaryPassword(loginParmas)
|
||||||
.then((pwd) => {
|
.then((pwd) => {
|
||||||
this.loginForm.loading = true;
|
|
||||||
return API.getConfig()
|
return API.getConfig()
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.loginForm.loading = false;
|
|
||||||
reject(err);
|
reject(err);
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@@ -257,12 +256,10 @@ export default class extends baseClass {
|
|||||||
websocket: loginParmas.websocket
|
websocket: loginParmas.websocket
|
||||||
})
|
})
|
||||||
.catch((err2) => {
|
.catch((err2) => {
|
||||||
this.loginForm.loading = false;
|
|
||||||
// API.logout();
|
// API.logout();
|
||||||
reject(err2);
|
reject(err2);
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.loginForm.loading = false;
|
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -277,7 +274,6 @@ export default class extends baseClass {
|
|||||||
} else {
|
} else {
|
||||||
API.getConfig()
|
API.getConfig()
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.loginForm.loading = false;
|
|
||||||
reject(err);
|
reject(err);
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@@ -288,17 +284,15 @@ export default class extends baseClass {
|
|||||||
websocket: loginParmas.websocket
|
websocket: loginParmas.websocket
|
||||||
})
|
})
|
||||||
.catch((err2) => {
|
.catch((err2) => {
|
||||||
this.loginForm.loading = false;
|
|
||||||
API.logout();
|
API.logout();
|
||||||
reject(err2);
|
reject(err2);
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.loginForm.loading = false;
|
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
}).finally(() => (this.loginForm.loading = false));
|
||||||
},
|
},
|
||||||
|
|
||||||
async deleteSavedLogin(userId) {
|
async deleteSavedLogin(userId) {
|
||||||
@@ -325,100 +319,90 @@ export default class extends baseClass {
|
|||||||
|
|
||||||
async login() {
|
async login() {
|
||||||
await webApiService.clearCookies();
|
await webApiService.clearCookies();
|
||||||
this.$refs.loginForm.validate((valid) => {
|
if (!this.loginForm.loading) {
|
||||||
if (valid && !this.loginForm.loading) {
|
this.loginForm.loading = true;
|
||||||
this.loginForm.loading = true;
|
if (this.loginForm.endpoint) {
|
||||||
if (this.loginForm.endpoint) {
|
API.endpointDomain = this.loginForm.endpoint;
|
||||||
API.endpointDomain = this.loginForm.endpoint;
|
API.websocketDomain = this.loginForm.websocket;
|
||||||
API.websocketDomain = this.loginForm.websocket;
|
} else {
|
||||||
} else {
|
API.endpointDomain = API.endpointDomainVrchat;
|
||||||
API.endpointDomain = API.endpointDomainVrchat;
|
API.websocketDomain = API.websocketDomainVrchat;
|
||||||
API.websocketDomain = API.websocketDomainVrchat;
|
}
|
||||||
}
|
API.getConfig()
|
||||||
API.getConfig()
|
.catch((err) => {
|
||||||
.catch((err) => {
|
this.loginForm.loading = false;
|
||||||
this.loginForm.loading = false;
|
throw err;
|
||||||
throw err;
|
})
|
||||||
})
|
.then((args) => {
|
||||||
.then((args) => {
|
if (
|
||||||
if (
|
this.loginForm.saveCredentials &&
|
||||||
this.loginForm.saveCredentials &&
|
this.enablePrimaryPassword
|
||||||
this.enablePrimaryPassword
|
) {
|
||||||
) {
|
$app.$prompt(
|
||||||
$app.$prompt(
|
$t('prompt.primary_password.description'),
|
||||||
$t('prompt.primary_password.description'),
|
$t('prompt.primary_password.header'),
|
||||||
$t('prompt.primary_password.header'),
|
{
|
||||||
{
|
inputType: 'password',
|
||||||
inputType: 'password',
|
inputPattern: /[\s\S]{1,32}/
|
||||||
inputPattern: /[\s\S]{1,32}/
|
}
|
||||||
}
|
)
|
||||||
)
|
.then(({ value }) => {
|
||||||
.then(({ value }) => {
|
let saveCredential =
|
||||||
let saveCredential =
|
this.loginForm.savedCredentials[
|
||||||
this.loginForm.savedCredentials[
|
Object.keys(
|
||||||
Object.keys(
|
this.loginForm.savedCredentials
|
||||||
this.loginForm
|
)[0]
|
||||||
.savedCredentials
|
];
|
||||||
)[0]
|
security
|
||||||
];
|
.decrypt(
|
||||||
security
|
saveCredential.loginParmas.password,
|
||||||
.decrypt(
|
value
|
||||||
saveCredential.loginParmas
|
)
|
||||||
.password,
|
.then(() => {
|
||||||
value
|
security
|
||||||
)
|
.encrypt(
|
||||||
.then(() => {
|
this.loginForm.password,
|
||||||
security
|
value
|
||||||
.encrypt(
|
)
|
||||||
this.loginForm.password,
|
.then((pwd) => {
|
||||||
value
|
API.login({
|
||||||
)
|
username:
|
||||||
.then((pwd) => {
|
this.loginForm
|
||||||
API.login({
|
.username,
|
||||||
username:
|
password:
|
||||||
this.loginForm
|
this.loginForm
|
||||||
.username,
|
.password,
|
||||||
password:
|
endpoint:
|
||||||
this.loginForm
|
this.loginForm
|
||||||
.password,
|
.endpoint,
|
||||||
endpoint:
|
websocket:
|
||||||
this.loginForm
|
this.loginForm
|
||||||
.endpoint,
|
.websocket,
|
||||||
websocket:
|
saveCredentials:
|
||||||
this.loginForm
|
this.loginForm
|
||||||
.websocket,
|
.saveCredentials,
|
||||||
saveCredentials:
|
cipher: pwd
|
||||||
this.loginForm
|
|
||||||
.saveCredentials,
|
|
||||||
cipher: pwd
|
|
||||||
}).then(() => {
|
|
||||||
this.$refs.loginForm.resetFields();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
.finally(() => {
|
|
||||||
this.loginForm.loading = false;
|
|
||||||
});
|
|
||||||
return args;
|
|
||||||
}
|
|
||||||
API.login({
|
|
||||||
username: this.loginForm.username,
|
|
||||||
password: this.loginForm.password,
|
|
||||||
endpoint: this.loginForm.endpoint,
|
|
||||||
websocket: this.loginForm.websocket,
|
|
||||||
saveCredentials: this.loginForm.saveCredentials
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
this.$refs.loginForm.resetFields();
|
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
this.loginForm.loading = false;
|
this.loginForm.loading = false;
|
||||||
});
|
});
|
||||||
return args;
|
return args;
|
||||||
|
}
|
||||||
|
API.login({
|
||||||
|
username: this.loginForm.username,
|
||||||
|
password: this.loginForm.password,
|
||||||
|
endpoint: this.loginForm.endpoint,
|
||||||
|
websocket: this.loginForm.websocket,
|
||||||
|
saveCredentials: this.loginForm.saveCredentials
|
||||||
|
}).finally(() => {
|
||||||
|
this.loginForm.loading = false;
|
||||||
});
|
});
|
||||||
}
|
return args;
|
||||||
});
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
logout() {
|
logout() {
|
||||||
|
|||||||
@@ -1,104 +0,0 @@
|
|||||||
import { baseClass, $app, API, $t, $utils } from './baseClass.js';
|
|
||||||
import { notificationRequest } from '../api';
|
|
||||||
|
|
||||||
export default class extends baseClass {
|
|
||||||
constructor(_app, _API, _t) {
|
|
||||||
super(_app, _API, _t);
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
/**
|
|
||||||
* @params {{
|
|
||||||
userId: string,
|
|
||||||
emojiId: string
|
|
||||||
}} params
|
|
||||||
* @returns {Promise<{json: any, params}>}
|
|
||||||
*/
|
|
||||||
API.sendBoop = function (params) {
|
|
||||||
return this.call(`users/${params.userId}/boop`, {
|
|
||||||
method: 'POST',
|
|
||||||
params
|
|
||||||
}).then((json) => {
|
|
||||||
var args = {
|
|
||||||
json,
|
|
||||||
params
|
|
||||||
};
|
|
||||||
this.$emit('BOOP:SEND', args);
|
|
||||||
return args;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
_data = {
|
|
||||||
sendBoopDialog: {
|
|
||||||
visible: false,
|
|
||||||
userId: '',
|
|
||||||
fileId: ''
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
_methods = {
|
|
||||||
sendBoop() {
|
|
||||||
var D = this.sendBoopDialog;
|
|
||||||
this.dismissBoop(D.userId);
|
|
||||||
var params = {
|
|
||||||
userId: D.userId
|
|
||||||
};
|
|
||||||
if (D.fileId) {
|
|
||||||
params.emojiId = D.fileId;
|
|
||||||
}
|
|
||||||
API.sendBoop(params);
|
|
||||||
D.visible = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
dismissBoop(userId) {
|
|
||||||
// JANK: This is a hack to remove boop notifications when responding
|
|
||||||
var array = this.notificationTable.data;
|
|
||||||
for (var i = array.length - 1; i >= 0; i--) {
|
|
||||||
var ref = array[i];
|
|
||||||
if (
|
|
||||||
ref.type !== 'boop' ||
|
|
||||||
ref.$isExpired ||
|
|
||||||
ref.senderUserId !== userId
|
|
||||||
) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
notificationRequest.sendNotificationResponse({
|
|
||||||
notificationId: ref.id,
|
|
||||||
responseType: 'delete',
|
|
||||||
responseData: ''
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
showSendBoopDialog(userId) {
|
|
||||||
this.$nextTick(() =>
|
|
||||||
$app.adjustDialogZ(this.$refs.sendBoopDialog.$el)
|
|
||||||
);
|
|
||||||
var D = this.sendBoopDialog;
|
|
||||||
D.userId = userId;
|
|
||||||
D.visible = true;
|
|
||||||
if (this.emojiTable.length === 0 && API.currentUser.$isVRCPlus) {
|
|
||||||
this.refreshEmojiTable();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getEmojiValue(emojiName) {
|
|
||||||
if (!emojiName) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
return `vrchat_${emojiName.replace(/ /g, '_').toLowerCase()}`;
|
|
||||||
},
|
|
||||||
|
|
||||||
getEmojiName(emojiValue) {
|
|
||||||
// uppercase first letter of each word
|
|
||||||
if (!emojiValue) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
return emojiValue
|
|
||||||
.replace('vrchat_', '')
|
|
||||||
.replace(/_/g, ' ')
|
|
||||||
.replace(/\b\w/g, (l) => l.toUpperCase());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { baseClass, $app, API, $t, $utils } from './baseClass.js';
|
import { isRealInstance, parseLocation } from '../composables/instance/utils';
|
||||||
|
import { $app, API, baseClass } from './baseClass.js';
|
||||||
|
|
||||||
export default class extends baseClass {
|
export default class extends baseClass {
|
||||||
constructor(_app, _API, _t) {
|
constructor(_app, _API, _t) {
|
||||||
@@ -83,8 +84,8 @@ export default class extends baseClass {
|
|||||||
args.ref = this.applyCurrentUser(json);
|
args.ref = this.applyCurrentUser(json);
|
||||||
|
|
||||||
// when isGameRunning use gameLog instead of API
|
// when isGameRunning use gameLog instead of API
|
||||||
var $location = $app.parseLocation($app.lastLocation.location);
|
var $location = parseLocation($app.lastLocation.location);
|
||||||
var $travelingLocation = $app.parseLocation(
|
var $travelingLocation = parseLocation(
|
||||||
$app.lastLocationDestination
|
$app.lastLocationDestination
|
||||||
);
|
);
|
||||||
var location = $app.lastLocation.location;
|
var location = $app.lastLocation.location;
|
||||||
@@ -94,12 +95,12 @@ export default class extends baseClass {
|
|||||||
var travelingToWorld = $travelingLocation.worldId;
|
var travelingToWorld = $travelingLocation.worldId;
|
||||||
var travelingToInstance = $travelingLocation.instanceId;
|
var travelingToInstance = $travelingLocation.instanceId;
|
||||||
if (!$app.isGameRunning && json.presence) {
|
if (!$app.isGameRunning && json.presence) {
|
||||||
if ($utils.isRealInstance(json.presence.world)) {
|
if (isRealInstance(json.presence.world)) {
|
||||||
location = `${json.presence.world}:${json.presence.instance}`;
|
location = `${json.presence.world}:${json.presence.instance}`;
|
||||||
} else {
|
} else {
|
||||||
location = json.presence.world;
|
location = json.presence.world;
|
||||||
}
|
}
|
||||||
if ($utils.isRealInstance(json.presence.travelingToWorld)) {
|
if (isRealInstance(json.presence.travelingToWorld)) {
|
||||||
travelingToLocation = `${json.presence.travelingToWorld}:${json.presence.travelingToInstance}`;
|
travelingToLocation = `${json.presence.travelingToWorld}:${json.presence.travelingToInstance}`;
|
||||||
} else {
|
} else {
|
||||||
travelingToLocation = json.presence.travelingToWorld;
|
travelingToLocation = json.presence.travelingToWorld;
|
||||||
@@ -175,7 +176,7 @@ export default class extends baseClass {
|
|||||||
}
|
}
|
||||||
Object.assign(ref, json);
|
Object.assign(ref, json);
|
||||||
if (ref.homeLocation !== ref.$homeLocation.tag) {
|
if (ref.homeLocation !== ref.$homeLocation.tag) {
|
||||||
ref.$homeLocation = $app.parseLocation(ref.homeLocation);
|
ref.$homeLocation = parseLocation(ref.homeLocation);
|
||||||
// apply home location name to user dialog
|
// apply home location name to user dialog
|
||||||
if (
|
if (
|
||||||
$app.userDialog.visible &&
|
$app.userDialog.visible &&
|
||||||
@@ -295,13 +296,12 @@ export default class extends baseClass {
|
|||||||
$languages: [],
|
$languages: [],
|
||||||
$locationTag: '',
|
$locationTag: '',
|
||||||
$travelingToLocation: '',
|
$travelingToLocation: '',
|
||||||
$vrchatcredits: null,
|
|
||||||
...json
|
...json
|
||||||
};
|
};
|
||||||
if ($app.isGameRunning) {
|
if ($app.isGameRunning) {
|
||||||
ref.$previousAvatarSwapTime = Date.now();
|
ref.$previousAvatarSwapTime = Date.now();
|
||||||
}
|
}
|
||||||
ref.$homeLocation = $app.parseLocation(ref.homeLocation);
|
ref.$homeLocation = parseLocation(ref.homeLocation);
|
||||||
ref.$isVRCPlus = ref.tags.includes('system_supporter');
|
ref.$isVRCPlus = ref.tags.includes('system_supporter');
|
||||||
this.applyUserTrustLevel(ref);
|
this.applyUserTrustLevel(ref);
|
||||||
this.applyUserLanguage(ref);
|
this.applyUserLanguage(ref);
|
||||||
@@ -316,32 +316,6 @@ export default class extends baseClass {
|
|||||||
}
|
}
|
||||||
return ref;
|
return ref;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {{
|
|
||||||
* status: 'active' | 'offline' | 'busy' | 'ask me' | 'join me',
|
|
||||||
* statusDescription: string
|
|
||||||
* }} SaveCurrentUserParameters
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates current user's status.
|
|
||||||
* @param params {SaveCurrentUserParameters} new status to be set
|
|
||||||
* @returns {Promise<{json: any, params}>}
|
|
||||||
*/
|
|
||||||
API.saveCurrentUser = 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;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_data = {};
|
_data = {};
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import configRepository from '../service/config.js';
|
|
||||||
import { baseClass, $app, API, $t, $utils } from './baseClass.js';
|
|
||||||
import { worldRequest } from '../api';
|
import { worldRequest } from '../api';
|
||||||
|
import { parseLocation } from '../composables/instance/utils';
|
||||||
|
import { getLaunchURL } from '../composables/shared/utils';
|
||||||
|
import configRepository from '../service/config.js';
|
||||||
|
import { API, baseClass } from './baseClass.js';
|
||||||
|
|
||||||
export default class extends baseClass {
|
export default class extends baseClass {
|
||||||
constructor(_app, _API, _t) {
|
constructor(_app, _API, _t) {
|
||||||
@@ -36,7 +38,7 @@ export default class extends baseClass {
|
|||||||
var L = this.lastLocation$;
|
var L = this.lastLocation$;
|
||||||
if (currentLocation !== this.lastLocation$.tag) {
|
if (currentLocation !== this.lastLocation$.tag) {
|
||||||
Discord.SetTimestamps(timeStamp, 0);
|
Discord.SetTimestamps(timeStamp, 0);
|
||||||
L = $app.parseLocation(currentLocation);
|
L = parseLocation(currentLocation);
|
||||||
L.worldName = '';
|
L.worldName = '';
|
||||||
L.thumbnailImageUrl = '';
|
L.thumbnailImageUrl = '';
|
||||||
L.worldCapacity = 0;
|
L.worldCapacity = 0;
|
||||||
@@ -76,7 +78,7 @@ export default class extends baseClass {
|
|||||||
}
|
}
|
||||||
switch (L.accessType) {
|
switch (L.accessType) {
|
||||||
case 'public':
|
case 'public':
|
||||||
L.joinUrl = $utils.getLaunchURL(L);
|
L.joinUrl = getLaunchURL(L);
|
||||||
L.accessName = `Public #${L.instanceName} (${platform})`;
|
L.accessName = `Public #${L.instanceName} (${platform})`;
|
||||||
break;
|
break;
|
||||||
case 'invite+':
|
case 'invite+':
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import * as workerTimers from 'worker-timers';
|
import * as workerTimers from 'worker-timers';
|
||||||
|
import { parseLocation } from '../composables/instance/utils';
|
||||||
import gameLogService from '../service/gamelog.js';
|
import gameLogService from '../service/gamelog.js';
|
||||||
import configRepository from '../service/config.js';
|
import configRepository from '../service/config.js';
|
||||||
import database from '../service/database.js';
|
import database from '../service/database.js';
|
||||||
import { baseClass, $app, API, $t, $utils } from './baseClass.js';
|
import { baseClass, $app, API, $utils } from './baseClass.js';
|
||||||
import { userRequest } from '../api';
|
import { userRequest } from '../api';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
@@ -80,7 +81,7 @@ export default class extends baseClass {
|
|||||||
this.lastLocation.location,
|
this.lastLocation.location,
|
||||||
gameLog.dt
|
gameLog.dt
|
||||||
);
|
);
|
||||||
var worldName = this.replaceBioSymbols(gameLog.worldName);
|
var worldName = $utils.replaceBioSymbols(gameLog.worldName);
|
||||||
if (this.isGameRunning) {
|
if (this.isGameRunning) {
|
||||||
this.lastLocationReset(gameLog.dt);
|
this.lastLocationReset(gameLog.dt);
|
||||||
this.clearNowPlaying();
|
this.clearNowPlaying();
|
||||||
@@ -100,7 +101,7 @@ export default class extends baseClass {
|
|||||||
this.applyGroupDialogInstances();
|
this.applyGroupDialogInstances();
|
||||||
}
|
}
|
||||||
this.addInstanceJoinHistory(gameLog.location, gameLog.dt);
|
this.addInstanceJoinHistory(gameLog.location, gameLog.dt);
|
||||||
var L = $utils.parseLocation(gameLog.location);
|
var L = parseLocation(gameLog.location);
|
||||||
var entry = {
|
var entry = {
|
||||||
created_at: gameLog.dt,
|
created_at: gameLog.dt,
|
||||||
type: 'Location',
|
type: 'Location',
|
||||||
@@ -789,7 +790,7 @@ export default class extends baseClass {
|
|||||||
var videoPos = Number(data[1]);
|
var videoPos = Number(data[1]);
|
||||||
var videoLength = Number(data[2]);
|
var videoLength = Number(data[2]);
|
||||||
var displayName = data[3];
|
var displayName = data[3];
|
||||||
var videoName = this.replaceBioSymbols(data[4]);
|
var videoName = $utils.replaceBioSymbols(data[4]);
|
||||||
var videoUrl = videoName;
|
var videoUrl = videoName;
|
||||||
var videoId = 'LSMedia';
|
var videoId = 'LSMedia';
|
||||||
if (videoUrl === this.nowPlaying.url) {
|
if (videoUrl === this.nowPlaying.url) {
|
||||||
@@ -981,29 +982,6 @@ export default class extends baseClass {
|
|||||||
this.addGameLogEntry(gameLog, this.lastLocation.location);
|
this.addGameLogEntry(gameLog, this.lastLocation.location);
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteGameLogEntryPrompt(row) {
|
|
||||||
this.$confirm('Continue? Delete Log', 'Confirm', {
|
|
||||||
confirmButtonText: 'Confirm',
|
|
||||||
cancelButtonText: 'Cancel',
|
|
||||||
type: 'info',
|
|
||||||
callback: (action) => {
|
|
||||||
if (action === 'confirm') {
|
|
||||||
this.deleteGameLogEntry(row);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteGameLogEntry(row) {
|
|
||||||
$app.removeFromArray(this.gameLogTable.data, row);
|
|
||||||
database.deleteGameLogEntry(row);
|
|
||||||
console.log(row);
|
|
||||||
database.getGamelogDatabase().then((data) => {
|
|
||||||
this.gameLogSessionTable = data;
|
|
||||||
this.updateSharedFeed(true);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
gameLogSearch(row) {
|
gameLogSearch(row) {
|
||||||
var value = this.gameLogTable.search.toUpperCase();
|
var value = this.gameLogTable.search.toUpperCase();
|
||||||
if (!value) {
|
if (!value) {
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
import * as workerTimers from 'worker-timers';
|
import * as workerTimers from 'worker-timers';
|
||||||
|
import { displayLocation, parseLocation } from '../composables/instance/utils';
|
||||||
|
import { checkVRChatCache } from '../composables/shared/utils';
|
||||||
import configRepository from '../service/config.js';
|
import configRepository from '../service/config.js';
|
||||||
import database from '../service/database.js';
|
import database from '../service/database.js';
|
||||||
import { baseClass, $app, API, $t, $utils } from './baseClass.js';
|
import { baseClass, $app, API, $utils } from './baseClass.js';
|
||||||
import { instanceRequest, userRequest } from '../api';
|
import { instanceRequest, userRequest } from '../api';
|
||||||
|
import {
|
||||||
|
photonEmojis,
|
||||||
|
photonEventType
|
||||||
|
} from '../composables/shared/constants/photon.js';
|
||||||
|
|
||||||
export default class extends baseClass {
|
export default class extends baseClass {
|
||||||
constructor(_app, _API, _t) {
|
constructor(_app, _API, _t) {
|
||||||
@@ -80,111 +86,6 @@ export default class extends baseClass {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
photonEventType: [
|
|
||||||
'MeshVisibility',
|
|
||||||
'AnimationFloat',
|
|
||||||
'AnimationBool',
|
|
||||||
'AnimationTrigger',
|
|
||||||
'AudioTrigger',
|
|
||||||
'PlayAnimation',
|
|
||||||
'SendMessage',
|
|
||||||
'SetParticlePlaying',
|
|
||||||
'TeleportPlayer',
|
|
||||||
'RunConsoleCommand',
|
|
||||||
'SetGameObjectActive',
|
|
||||||
'SetWebPanelURI',
|
|
||||||
'SetWebPanelVolume',
|
|
||||||
'SpawnObject',
|
|
||||||
'SendRPC',
|
|
||||||
'ActivateCustomTrigger',
|
|
||||||
'DestroyObject',
|
|
||||||
'SetLayer',
|
|
||||||
'SetMaterial',
|
|
||||||
'AddHealth',
|
|
||||||
'AddDamage',
|
|
||||||
'SetComponentActive',
|
|
||||||
'AnimationInt',
|
|
||||||
'AnimationIntAdd',
|
|
||||||
'AnimationIntSubtract',
|
|
||||||
'AnimationIntMultiply',
|
|
||||||
'AnimationIntDivide',
|
|
||||||
'AddVelocity',
|
|
||||||
'SetVelocity',
|
|
||||||
'AddAngularVelocity',
|
|
||||||
'SetAngularVelocity',
|
|
||||||
'AddForce',
|
|
||||||
'SetUIText',
|
|
||||||
'CallUdonMethod'
|
|
||||||
],
|
|
||||||
|
|
||||||
photonEmojis: [
|
|
||||||
'Angry',
|
|
||||||
'Blushing',
|
|
||||||
'Crying',
|
|
||||||
'Frown',
|
|
||||||
'Hand Wave',
|
|
||||||
'Hang Ten',
|
|
||||||
'In Love',
|
|
||||||
'Jack O Lantern',
|
|
||||||
'Kiss',
|
|
||||||
'Laugh',
|
|
||||||
'Skull',
|
|
||||||
'Smile',
|
|
||||||
'Spooky Ghost',
|
|
||||||
'Stoic',
|
|
||||||
'Sunglasses',
|
|
||||||
'Thinking',
|
|
||||||
'Thumbs Down',
|
|
||||||
'Thumbs Up',
|
|
||||||
'Tongue Out',
|
|
||||||
'Wow',
|
|
||||||
'Arrow Point',
|
|
||||||
"Can't see",
|
|
||||||
'Hourglass',
|
|
||||||
'Keyboard',
|
|
||||||
'No Headphones',
|
|
||||||
'No Mic',
|
|
||||||
'Portal',
|
|
||||||
'Shush',
|
|
||||||
'Bats',
|
|
||||||
'Cloud',
|
|
||||||
'Fire',
|
|
||||||
'Snow Fall',
|
|
||||||
'Snowball',
|
|
||||||
'Splash',
|
|
||||||
'Web',
|
|
||||||
'Beer',
|
|
||||||
'Candy',
|
|
||||||
'Candy Cane',
|
|
||||||
'Candy Corn',
|
|
||||||
'Champagne',
|
|
||||||
'Drink',
|
|
||||||
'Gingerbread',
|
|
||||||
'Ice Cream',
|
|
||||||
'Pineapple',
|
|
||||||
'Pizza',
|
|
||||||
'Tomato',
|
|
||||||
'Beachball',
|
|
||||||
'Coal',
|
|
||||||
'Confetti',
|
|
||||||
'Gift',
|
|
||||||
'Gifts',
|
|
||||||
'Life Ring',
|
|
||||||
'Mistletoe',
|
|
||||||
'Money',
|
|
||||||
'Neon Shades',
|
|
||||||
'Sun Lotion',
|
|
||||||
'Boo',
|
|
||||||
'Broken Heart',
|
|
||||||
'Exclamation',
|
|
||||||
'Go',
|
|
||||||
'Heart',
|
|
||||||
'Music Note',
|
|
||||||
'Question',
|
|
||||||
'Stop',
|
|
||||||
'Zzz'
|
|
||||||
],
|
|
||||||
|
|
||||||
photonEventTableFilter: '',
|
photonEventTableFilter: '',
|
||||||
photonEventTableTypeFilter: [],
|
photonEventTableTypeFilter: [],
|
||||||
photonEventTableTypeOverlayFilter: [],
|
photonEventTableTypeOverlayFilter: [],
|
||||||
@@ -894,7 +795,7 @@ export default class extends baseClass {
|
|||||||
var imageUrl = '';
|
var imageUrl = '';
|
||||||
if (type === 0) {
|
if (type === 0) {
|
||||||
var emojiId = data.Parameters[245][2];
|
var emojiId = data.Parameters[245][2];
|
||||||
emojiName = this.photonEmojis[emojiId];
|
emojiName = photonEmojis[emojiId];
|
||||||
} else if (type === 1) {
|
} else if (type === 1) {
|
||||||
emojiName = 'Custom';
|
emojiName = 'Custom';
|
||||||
var fileId = data.Parameters[245][1];
|
var fileId = data.Parameters[245][1];
|
||||||
@@ -982,7 +883,7 @@ export default class extends baseClass {
|
|||||||
if (this.debugPhotonLogging) {
|
if (this.debugPhotonLogging) {
|
||||||
var displayName = this.getDisplayNameFromPhotonId(senderId);
|
var displayName = this.getDisplayNameFromPhotonId(senderId);
|
||||||
var feed = `RPC ${displayName} ${
|
var feed = `RPC ${displayName} ${
|
||||||
this.photonEventType[eventData.EventType]
|
photonEventType[eventData.EventType]
|
||||||
}${eventName}`;
|
}${eventName}`;
|
||||||
console.log('VrcRpc:', feed);
|
console.log('VrcRpc:', feed);
|
||||||
}
|
}
|
||||||
@@ -1026,7 +927,7 @@ export default class extends baseClass {
|
|||||||
shortName
|
shortName
|
||||||
});
|
});
|
||||||
var location = instance.json.location;
|
var location = instance.json.location;
|
||||||
var L = $utils.parseLocation(location);
|
var L = parseLocation(location);
|
||||||
var groupName = '';
|
var groupName = '';
|
||||||
if (L.groupId) {
|
if (L.groupId) {
|
||||||
groupName = await this.getGroupName(L.groupId);
|
groupName = await this.getGroupName(L.groupId);
|
||||||
@@ -1040,14 +941,14 @@ export default class extends baseClass {
|
|||||||
// if (shortName === newShortName) {
|
// if (shortName === newShortName) {
|
||||||
// portalType = 'Unlocked';
|
// portalType = 'Unlocked';
|
||||||
// }
|
// }
|
||||||
var displayLocation = this.displayLocation(
|
var _displayLocation = displayLocation(
|
||||||
location,
|
location,
|
||||||
worldName,
|
worldName,
|
||||||
groupName
|
groupName
|
||||||
);
|
);
|
||||||
this.addEntryPhotonEvent({
|
this.addEntryPhotonEvent({
|
||||||
photonId: this.getPhotonIdFromUserId(userId),
|
photonId: this.getPhotonIdFromUserId(userId),
|
||||||
text: `PortalSpawn to ${displayLocation}`,
|
text: `PortalSpawn to ${_displayLocation}`,
|
||||||
type: 'PortalSpawn',
|
type: 'PortalSpawn',
|
||||||
shortName,
|
shortName,
|
||||||
location,
|
location,
|
||||||
@@ -1210,10 +1111,10 @@ export default class extends baseClass {
|
|||||||
type: 'ChangeStatus',
|
type: 'ChangeStatus',
|
||||||
status: photonUser.status,
|
status: photonUser.status,
|
||||||
previousStatus: ref.status,
|
previousStatus: ref.status,
|
||||||
statusDescription: this.replaceBioSymbols(
|
statusDescription: $utils.replaceBioSymbols(
|
||||||
photonUser.statusDescription
|
photonUser.statusDescription
|
||||||
),
|
),
|
||||||
previousStatusDescription: this.replaceBioSymbols(
|
previousStatusDescription: $utils.replaceBioSymbols(
|
||||||
ref.statusDescription
|
ref.statusDescription
|
||||||
),
|
),
|
||||||
created_at: Date.parse(gameLogDate)
|
created_at: Date.parse(gameLogDate)
|
||||||
@@ -1227,8 +1128,8 @@ export default class extends baseClass {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var avatar = user.avatarDict;
|
var avatar = user.avatarDict;
|
||||||
avatar.name = this.replaceBioSymbols(avatar.name);
|
avatar.name = $utils.replaceBioSymbols(avatar.name);
|
||||||
avatar.description = this.replaceBioSymbols(avatar.description);
|
avatar.description = $utils.replaceBioSymbols(avatar.description);
|
||||||
var platform = '';
|
var platform = '';
|
||||||
if (user.last_platform === 'android') {
|
if (user.last_platform === 'android') {
|
||||||
platform = 'Android';
|
platform = 'Android';
|
||||||
@@ -1240,7 +1141,7 @@ export default class extends baseClass {
|
|||||||
platform = 'Desktop';
|
platform = 'Desktop';
|
||||||
}
|
}
|
||||||
this.photonUserSusieCheck(photonId, user, gameLogDate);
|
this.photonUserSusieCheck(photonId, user, gameLogDate);
|
||||||
$utils.checkVRChatCache(avatar).then((cacheInfo) => {
|
checkVRChatCache(avatar).then((cacheInfo) => {
|
||||||
var inCache = false;
|
var inCache = false;
|
||||||
if (cacheInfo.Item1 > 0) {
|
if (cacheInfo.Item1 > 0) {
|
||||||
inCache = true;
|
inCache = true;
|
||||||
@@ -1410,9 +1311,11 @@ export default class extends baseClass {
|
|||||||
oldAvatarId !== avatar.id &&
|
oldAvatarId !== avatar.id &&
|
||||||
photonId !== this.photonLobbyCurrentUser
|
photonId !== this.photonLobbyCurrentUser
|
||||||
) {
|
) {
|
||||||
avatar.name = this.replaceBioSymbols(avatar.name);
|
avatar.name = $utils.replaceBioSymbols(avatar.name);
|
||||||
avatar.description = this.replaceBioSymbols(avatar.description);
|
avatar.description = $utils.replaceBioSymbols(
|
||||||
$utils.checkVRChatCache(avatar).then((cacheInfo) => {
|
avatar.description
|
||||||
|
);
|
||||||
|
checkVRChatCache(avatar).then((cacheInfo) => {
|
||||||
var inCache = false;
|
var inCache = false;
|
||||||
if (cacheInfo.Item1 > 0) {
|
if (cacheInfo.Item1 > 0) {
|
||||||
inCache = true;
|
inCache = true;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
instanceRequest,
|
instanceRequest,
|
||||||
groupRequest
|
groupRequest
|
||||||
} from '../api';
|
} from '../api';
|
||||||
|
import $utils from './utils';
|
||||||
|
|
||||||
export default class extends baseClass {
|
export default class extends baseClass {
|
||||||
constructor(_app, _API, _t) {
|
constructor(_app, _API, _t) {
|
||||||
@@ -171,8 +172,8 @@ export default class extends baseClass {
|
|||||||
var D = $app.groupDialog;
|
var D = $app.groupDialog;
|
||||||
if (D.id === args.params.groupId) {
|
if (D.id === args.params.groupId) {
|
||||||
for (var post of args.posts) {
|
for (var post of args.posts) {
|
||||||
post.title = $app.replaceBioSymbols(post.title);
|
post.title = $utils.replaceBioSymbols(post.title);
|
||||||
post.text = $app.replaceBioSymbols(post.text);
|
post.text = $utils.replaceBioSymbols(post.text);
|
||||||
}
|
}
|
||||||
if (args.posts.length > 0) {
|
if (args.posts.length > 0) {
|
||||||
D.announcement = args.posts[0];
|
D.announcement = args.posts[0];
|
||||||
@@ -189,8 +190,8 @@ export default class extends baseClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var newPost = args.json;
|
var newPost = args.json;
|
||||||
newPost.title = $app.replaceBioSymbols(newPost.title);
|
newPost.title = $utils.replaceBioSymbols(newPost.title);
|
||||||
newPost.text = $app.replaceBioSymbols(newPost.text);
|
newPost.text = $utils.replaceBioSymbols(newPost.text);
|
||||||
var hasPost = false;
|
var hasPost = false;
|
||||||
// update existing post
|
// update existing post
|
||||||
for (var post of D.posts) {
|
for (var post of D.posts) {
|
||||||
@@ -275,9 +276,9 @@ export default class extends baseClass {
|
|||||||
|
|
||||||
API.applyGroup = function (json) {
|
API.applyGroup = function (json) {
|
||||||
var ref = this.cachedGroups.get(json.id);
|
var ref = this.cachedGroups.get(json.id);
|
||||||
json.rules = $app.replaceBioSymbols(json.rules);
|
json.rules = $utils.replaceBioSymbols(json.rules);
|
||||||
json.name = $app.replaceBioSymbols(json.name);
|
json.name = $utils.replaceBioSymbols(json.name);
|
||||||
json.description = $app.replaceBioSymbols(json.description);
|
json.description = $utils.replaceBioSymbols(json.description);
|
||||||
if (typeof ref === 'undefined') {
|
if (typeof ref === 'undefined') {
|
||||||
ref = {
|
ref = {
|
||||||
id: '',
|
id: '',
|
||||||
@@ -912,9 +913,6 @@ export default class extends baseClass {
|
|||||||
case 'Unsubscribe To Announcements':
|
case 'Unsubscribe To Announcements':
|
||||||
this.setGroupSubscription(D.id, false);
|
this.setGroupSubscription(D.id, false);
|
||||||
break;
|
break;
|
||||||
case 'Invite To Group':
|
|
||||||
this.showInviteGroupDialog(D.id, '');
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -1050,17 +1048,6 @@ export default class extends baseClass {
|
|||||||
this.userDialog.representedGroup = args.json;
|
this.userDialog.representedGroup = args.json;
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
},
|
|
||||||
|
|
||||||
showInviteGroupDialog(groupId, userId) {
|
|
||||||
const D = this.inviteGroupDialog;
|
|
||||||
D.userIds = '';
|
|
||||||
D.groups = [];
|
|
||||||
D.groupId = groupId;
|
|
||||||
D.groupName = groupId;
|
|
||||||
D.userId = userId;
|
|
||||||
D.userObject = {};
|
|
||||||
D.visible = true;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,85 +24,9 @@ export default class extends baseClass {
|
|||||||
}
|
}
|
||||||
$app.languageDialog.languages = data;
|
$app.languageDialog.languages = data;
|
||||||
});
|
});
|
||||||
|
|
||||||
API.$on('LOGOUT', function () {
|
|
||||||
$app.languageDialog.visible = false;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_data = {
|
_data = {
|
||||||
// vrchat to famfamfam language mappings
|
|
||||||
languageMappings: {
|
|
||||||
eng: 'us',
|
|
||||||
kor: 'kr',
|
|
||||||
rus: 'ru',
|
|
||||||
spa: 'es',
|
|
||||||
por: 'pt',
|
|
||||||
zho: 'cn',
|
|
||||||
deu: 'de',
|
|
||||||
jpn: 'jp',
|
|
||||||
fra: 'fr',
|
|
||||||
swe: 'se',
|
|
||||||
nld: 'nl',
|
|
||||||
pol: 'pl',
|
|
||||||
dan: 'dk',
|
|
||||||
nor: 'no',
|
|
||||||
ita: 'it',
|
|
||||||
tha: 'th',
|
|
||||||
fin: 'fi',
|
|
||||||
hun: 'hu',
|
|
||||||
ces: 'cz',
|
|
||||||
tur: 'tr',
|
|
||||||
ara: 'ae',
|
|
||||||
ron: 'ro',
|
|
||||||
vie: 'vn',
|
|
||||||
ukr: 'ua',
|
|
||||||
ase: 'us',
|
|
||||||
bfi: 'gb',
|
|
||||||
dse: 'nl',
|
|
||||||
fsl: 'fr',
|
|
||||||
jsl: 'jp',
|
|
||||||
kvk: 'kr',
|
|
||||||
|
|
||||||
mlt: 'mt',
|
|
||||||
ind: 'id',
|
|
||||||
hrv: 'hr',
|
|
||||||
heb: 'he',
|
|
||||||
afr: 'af',
|
|
||||||
ben: 'be',
|
|
||||||
bul: 'bg',
|
|
||||||
cmn: 'cn',
|
|
||||||
cym: 'cy',
|
|
||||||
ell: 'el',
|
|
||||||
est: 'et',
|
|
||||||
fil: 'ph',
|
|
||||||
gla: 'gd',
|
|
||||||
gle: 'ga',
|
|
||||||
hin: 'hi',
|
|
||||||
hmn: 'cn',
|
|
||||||
hye: 'hy',
|
|
||||||
isl: 'is',
|
|
||||||
lav: 'lv',
|
|
||||||
lit: 'lt',
|
|
||||||
ltz: 'lb',
|
|
||||||
mar: 'hi',
|
|
||||||
mkd: 'mk',
|
|
||||||
msa: 'my',
|
|
||||||
sco: 'gd',
|
|
||||||
slk: 'sk',
|
|
||||||
slv: 'sl',
|
|
||||||
tel: 'hi',
|
|
||||||
mri: 'nz',
|
|
||||||
wuu: 'cn',
|
|
||||||
yue: 'cn',
|
|
||||||
tws: 'cn',
|
|
||||||
asf: 'au',
|
|
||||||
nzs: 'nz',
|
|
||||||
gsg: 'de',
|
|
||||||
epo: 'eo',
|
|
||||||
tok: 'tok'
|
|
||||||
},
|
|
||||||
|
|
||||||
subsetOfLanguages: [],
|
subsetOfLanguages: [],
|
||||||
|
|
||||||
languageDialog: {
|
languageDialog: {
|
||||||
@@ -113,54 +37,5 @@ export default class extends baseClass {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
_methods = {
|
_methods = {};
|
||||||
languageClass(language) {
|
|
||||||
var style = {};
|
|
||||||
var mapping = this.languageMappings[language];
|
|
||||||
if (typeof mapping !== 'undefined') {
|
|
||||||
style[mapping] = true;
|
|
||||||
} else {
|
|
||||||
style.unknown = true;
|
|
||||||
}
|
|
||||||
return style;
|
|
||||||
},
|
|
||||||
|
|
||||||
addUserLanguage(language) {
|
|
||||||
if (language !== String(language)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const D = this.languageDialog;
|
|
||||||
D.loading = true;
|
|
||||||
userRequest
|
|
||||||
.addUserTags({
|
|
||||||
tags: [`language_${language}`]
|
|
||||||
})
|
|
||||||
.finally(function () {
|
|
||||||
D.loading = false;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
removeUserLanguage(language) {
|
|
||||||
if (language !== String(language)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const D = this.languageDialog;
|
|
||||||
D.loading = true;
|
|
||||||
userRequest
|
|
||||||
.removeUserTags({
|
|
||||||
tags: [`language_${language}`]
|
|
||||||
})
|
|
||||||
.finally(function () {
|
|
||||||
D.loading = false;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
showLanguageDialog() {
|
|
||||||
this.$nextTick(() =>
|
|
||||||
$app.adjustDialogZ(this.$refs.languageDialog.$el)
|
|
||||||
);
|
|
||||||
var D = this.languageDialog;
|
|
||||||
D.visible = true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,11 +27,6 @@ export default class extends baseClass {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onUserMemoChange() {
|
|
||||||
var D = this.userDialog;
|
|
||||||
this.saveUserMemo(D.id, D.memo);
|
|
||||||
},
|
|
||||||
|
|
||||||
async getUserMemo(userId) {
|
async getUserMemo(userId) {
|
||||||
try {
|
try {
|
||||||
return await database.getUserMemo(userId);
|
return await database.getUserMemo(userId);
|
||||||
|
|||||||
@@ -132,139 +132,6 @@ export default class extends baseClass {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
promptUserIdDialog() {
|
|
||||||
this.$prompt(
|
|
||||||
$t('prompt.direct_access_user_id.description'),
|
|
||||||
$t('prompt.direct_access_user_id.header'),
|
|
||||||
{
|
|
||||||
distinguishCancelAndClose: true,
|
|
||||||
confirmButtonText: $t('prompt.direct_access_user_id.ok'),
|
|
||||||
cancelButtonText: $t('prompt.direct_access_user_id.cancel'),
|
|
||||||
inputPattern: /\S+/,
|
|
||||||
inputErrorMessage: $t(
|
|
||||||
'prompt.direct_access_user_id.input_error'
|
|
||||||
),
|
|
||||||
callback: (action, instance) => {
|
|
||||||
if (action === 'confirm' && instance.inputValue) {
|
|
||||||
var testUrl = instance.inputValue.substring(0, 15);
|
|
||||||
if (testUrl === 'https://vrchat.') {
|
|
||||||
var userId = this.parseUserUrl(
|
|
||||||
instance.inputValue
|
|
||||||
);
|
|
||||||
if (userId) {
|
|
||||||
this.showUserDialog(userId);
|
|
||||||
} else {
|
|
||||||
this.$message({
|
|
||||||
message: $t(
|
|
||||||
'prompt.direct_access_user_id.message.error'
|
|
||||||
),
|
|
||||||
type: 'error'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.showUserDialog(instance.inputValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
promptUsernameDialog() {
|
|
||||||
this.$prompt(
|
|
||||||
$t('prompt.direct_access_username.description'),
|
|
||||||
$t('prompt.direct_access_username.header'),
|
|
||||||
{
|
|
||||||
distinguishCancelAndClose: true,
|
|
||||||
confirmButtonText: $t('prompt.direct_access_username.ok'),
|
|
||||||
cancelButtonText: $t(
|
|
||||||
'prompt.direct_access_username.cancel'
|
|
||||||
),
|
|
||||||
inputPattern: /\S+/,
|
|
||||||
inputErrorMessage: $t(
|
|
||||||
'prompt.direct_access_username.input_error'
|
|
||||||
),
|
|
||||||
callback: (action, instance) => {
|
|
||||||
if (action === 'confirm' && instance.inputValue) {
|
|
||||||
this.lookupUser({
|
|
||||||
displayName: instance.inputValue
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
promptWorldDialog() {
|
|
||||||
this.$prompt(
|
|
||||||
$t('prompt.direct_access_world_id.description'),
|
|
||||||
$t('prompt.direct_access_world_id.header'),
|
|
||||||
{
|
|
||||||
distinguishCancelAndClose: true,
|
|
||||||
confirmButtonText: $t('prompt.direct_access_world_id.ok'),
|
|
||||||
cancelButtonText: $t(
|
|
||||||
'prompt.direct_access_world_id.cancel'
|
|
||||||
),
|
|
||||||
inputPattern: /\S+/,
|
|
||||||
inputErrorMessage: $t(
|
|
||||||
'prompt.direct_access_world_id.input_error'
|
|
||||||
),
|
|
||||||
callback: (action, instance) => {
|
|
||||||
if (action === 'confirm' && instance.inputValue) {
|
|
||||||
if (!this.directAccessWorld(instance.inputValue)) {
|
|
||||||
this.$message({
|
|
||||||
message: $t(
|
|
||||||
'prompt.direct_access_world_id.message.error'
|
|
||||||
),
|
|
||||||
type: 'error'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
promptAvatarDialog() {
|
|
||||||
this.$prompt(
|
|
||||||
$t('prompt.direct_access_avatar_id.description'),
|
|
||||||
$t('prompt.direct_access_avatar_id.header'),
|
|
||||||
{
|
|
||||||
distinguishCancelAndClose: true,
|
|
||||||
confirmButtonText: $t('prompt.direct_access_avatar_id.ok'),
|
|
||||||
cancelButtonText: $t(
|
|
||||||
'prompt.direct_access_avatar_id.cancel'
|
|
||||||
),
|
|
||||||
inputPattern: /\S+/,
|
|
||||||
inputErrorMessage: $t(
|
|
||||||
'prompt.direct_access_avatar_id.input_error'
|
|
||||||
),
|
|
||||||
callback: (action, instance) => {
|
|
||||||
if (action === 'confirm' && instance.inputValue) {
|
|
||||||
var testUrl = instance.inputValue.substring(0, 15);
|
|
||||||
if (testUrl === 'https://vrchat.') {
|
|
||||||
var avatarId = this.parseAvatarUrl(
|
|
||||||
instance.inputValue
|
|
||||||
);
|
|
||||||
if (avatarId) {
|
|
||||||
this.showAvatarDialog(avatarId);
|
|
||||||
} else {
|
|
||||||
this.$message({
|
|
||||||
message: $t(
|
|
||||||
'prompt.direct_access_avatar_id.message.error'
|
|
||||||
),
|
|
||||||
type: 'error'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.showAvatarDialog(instance.inputValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
promptOmniDirectDialog() {
|
promptOmniDirectDialog() {
|
||||||
this.$prompt(
|
this.$prompt(
|
||||||
$t('prompt.direct_access_omni.description'),
|
$t('prompt.direct_access_omni.description'),
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import VueMarkdown from 'vue-markdown';
|
import VueMarkdown from 'vue-markdown';
|
||||||
import { baseClass, $app, API, $t, $utils } from './baseClass.js';
|
|
||||||
import { instanceRequest, userRequest } from '../api';
|
import { instanceRequest, userRequest } from '../api';
|
||||||
import utils from './utils';
|
import { hasGroupPermission } from '../composables/group/utils';
|
||||||
|
import { parseLocation } from '../composables/instance/utils';
|
||||||
|
import { $app, $t, API, baseClass } from './baseClass.js';
|
||||||
|
|
||||||
export default class extends baseClass {
|
export default class extends baseClass {
|
||||||
constructor(_app, _API, _t) {
|
constructor(_app, _API, _t) {
|
||||||
@@ -60,7 +61,7 @@ export default class extends baseClass {
|
|||||||
this.selfInvite(this.location, this.shortname);
|
this.selfInvite(this.location, this.shortname);
|
||||||
},
|
},
|
||||||
selfInvite(location, shortName) {
|
selfInvite(location, shortName) {
|
||||||
const L = utils.parseLocation(location);
|
const L = parseLocation(location);
|
||||||
if (!L.isRealInstance) {
|
if (!L.isRealInstance) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -165,7 +166,7 @@ export default class extends baseClass {
|
|||||||
if (!this.location) {
|
if (!this.location) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var L = $utils.parseLocation(this.location);
|
var L = parseLocation(this.location);
|
||||||
if (!L.groupId) {
|
if (!L.groupId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -320,7 +321,7 @@ export default class extends baseClass {
|
|||||||
// check group perms
|
// check group perms
|
||||||
var groupId = this.instance.ownerId;
|
var groupId = this.instance.ownerId;
|
||||||
var group = API.cachedGroups.get(groupId);
|
var group = API.cachedGroups.get(groupId);
|
||||||
this.canCloseInstance = utils.hasGroupPermission(
|
this.canCloseInstance = hasGroupPermission(
|
||||||
group,
|
group,
|
||||||
'group-instance-moderate'
|
'group-instance-moderate'
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import Noty from 'noty';
|
|
||||||
|
|
||||||
let echarts = null;
|
let echarts = null;
|
||||||
|
|
||||||
// messy here, organize later
|
|
||||||
const _utils = {
|
const _utils = {
|
||||||
removeFromArray(array, item) {
|
removeFromArray(array, item) {
|
||||||
var { length } = array;
|
var { length } = array;
|
||||||
@@ -14,7 +11,6 @@ const _utils = {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
arraysMatch(a, b) {
|
arraysMatch(a, b) {
|
||||||
if (!Array.isArray(a) || !Array.isArray(b)) {
|
if (!Array.isArray(a) || !Array.isArray(b)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -27,12 +23,10 @@ const _utils = {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
escapeTag(tag) {
|
escapeTag(tag) {
|
||||||
var s = String(tag);
|
var s = String(tag);
|
||||||
return s.replace(/["&'<>]/g, (c) => `&#${c.charCodeAt(0)};`);
|
return s.replace(/["&'<>]/g, (c) => `&#${c.charCodeAt(0)};`);
|
||||||
},
|
},
|
||||||
|
|
||||||
escapeTagRecursive(obj) {
|
escapeTagRecursive(obj) {
|
||||||
if (typeof obj === 'string') {
|
if (typeof obj === 'string') {
|
||||||
return this.escapeTag(obj);
|
return this.escapeTag(obj);
|
||||||
@@ -44,7 +38,6 @@ const _utils = {
|
|||||||
}
|
}
|
||||||
return obj;
|
return obj;
|
||||||
},
|
},
|
||||||
|
|
||||||
timeToText(sec, isNeedSeconds = false) {
|
timeToText(sec, isNeedSeconds = false) {
|
||||||
let n = Number(sec);
|
let n = Number(sec);
|
||||||
if (isNaN(n)) {
|
if (isNaN(n)) {
|
||||||
@@ -72,7 +65,6 @@ const _utils = {
|
|||||||
}
|
}
|
||||||
return arr.join(' ');
|
return arr.join(' ');
|
||||||
},
|
},
|
||||||
|
|
||||||
textToHex(text) {
|
textToHex(text) {
|
||||||
var s = String(text);
|
var s = String(text);
|
||||||
return s
|
return s
|
||||||
@@ -80,7 +72,6 @@ const _utils = {
|
|||||||
.map((c) => c.charCodeAt(0).toString(16))
|
.map((c) => c.charCodeAt(0).toString(16))
|
||||||
.join(' ');
|
.join(' ');
|
||||||
},
|
},
|
||||||
|
|
||||||
commaNumber(num) {
|
commaNumber(num) {
|
||||||
if (!num) {
|
if (!num) {
|
||||||
return '0';
|
return '0';
|
||||||
@@ -88,182 +79,6 @@ const _utils = {
|
|||||||
var s = String(Number(num));
|
var s = String(Number(num));
|
||||||
return s.replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,');
|
return s.replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,');
|
||||||
},
|
},
|
||||||
|
|
||||||
isRealInstance(instanceId) {
|
|
||||||
if (!instanceId) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
switch (instanceId) {
|
|
||||||
case ':':
|
|
||||||
case 'offline':
|
|
||||||
case 'offline:offline':
|
|
||||||
case 'private':
|
|
||||||
case 'private:private':
|
|
||||||
case 'traveling':
|
|
||||||
case 'traveling:traveling':
|
|
||||||
case instanceId.startsWith('local'):
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
|
|
||||||
parseLocation(tag) {
|
|
||||||
var _tag = String(tag || '');
|
|
||||||
var ctx = {
|
|
||||||
tag: _tag,
|
|
||||||
isOffline: false,
|
|
||||||
isPrivate: false,
|
|
||||||
isTraveling: false,
|
|
||||||
isRealInstance: false,
|
|
||||||
worldId: '',
|
|
||||||
instanceId: '',
|
|
||||||
instanceName: '',
|
|
||||||
accessType: '',
|
|
||||||
accessTypeName: '',
|
|
||||||
region: '',
|
|
||||||
shortName: '',
|
|
||||||
userId: null,
|
|
||||||
hiddenId: null,
|
|
||||||
privateId: null,
|
|
||||||
friendsId: null,
|
|
||||||
groupId: null,
|
|
||||||
groupAccessType: null,
|
|
||||||
canRequestInvite: false,
|
|
||||||
strict: false,
|
|
||||||
ageGate: false
|
|
||||||
};
|
|
||||||
if (_tag === 'offline' || _tag === 'offline:offline') {
|
|
||||||
ctx.isOffline = true;
|
|
||||||
} else if (_tag === 'private' || _tag === 'private:private') {
|
|
||||||
ctx.isPrivate = true;
|
|
||||||
} else if (_tag === 'traveling' || _tag === 'traveling:traveling') {
|
|
||||||
ctx.isTraveling = true;
|
|
||||||
} else if (!_tag.startsWith('local')) {
|
|
||||||
ctx.isRealInstance = true;
|
|
||||||
var sep = _tag.indexOf(':');
|
|
||||||
// technically not part of instance id, but might be there when coping id from url so why not support it
|
|
||||||
var shortNameQualifier = '&shortName=';
|
|
||||||
var shortNameIndex = _tag.indexOf(shortNameQualifier);
|
|
||||||
if (shortNameIndex >= 0) {
|
|
||||||
ctx.shortName = _tag.substr(
|
|
||||||
shortNameIndex + shortNameQualifier.length
|
|
||||||
);
|
|
||||||
_tag = _tag.substr(0, shortNameIndex);
|
|
||||||
}
|
|
||||||
if (sep >= 0) {
|
|
||||||
ctx.worldId = _tag.substr(0, sep);
|
|
||||||
ctx.instanceId = _tag.substr(sep + 1);
|
|
||||||
ctx.instanceId.split('~').forEach((s, i) => {
|
|
||||||
if (i) {
|
|
||||||
var A = s.indexOf('(');
|
|
||||||
var Z = A >= 0 ? s.lastIndexOf(')') : -1;
|
|
||||||
var key = Z >= 0 ? s.substr(0, A) : s;
|
|
||||||
var value = A < Z ? s.substr(A + 1, Z - A - 1) : '';
|
|
||||||
if (key === 'hidden') {
|
|
||||||
ctx.hiddenId = value;
|
|
||||||
} else if (key === 'private') {
|
|
||||||
ctx.privateId = value;
|
|
||||||
} else if (key === 'friends') {
|
|
||||||
ctx.friendsId = value;
|
|
||||||
} else if (key === 'canRequestInvite') {
|
|
||||||
ctx.canRequestInvite = true;
|
|
||||||
} else if (key === 'region') {
|
|
||||||
ctx.region = value;
|
|
||||||
} else if (key === 'group') {
|
|
||||||
ctx.groupId = value;
|
|
||||||
} else if (key === 'groupAccessType') {
|
|
||||||
ctx.groupAccessType = value;
|
|
||||||
} else if (key === 'strict') {
|
|
||||||
ctx.strict = true;
|
|
||||||
} else if (key === 'ageGate') {
|
|
||||||
ctx.ageGate = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ctx.instanceName = s;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
ctx.accessType = 'public';
|
|
||||||
if (ctx.privateId !== null) {
|
|
||||||
if (ctx.canRequestInvite) {
|
|
||||||
// InvitePlus
|
|
||||||
ctx.accessType = 'invite+';
|
|
||||||
} else {
|
|
||||||
// InviteOnly
|
|
||||||
ctx.accessType = 'invite';
|
|
||||||
}
|
|
||||||
ctx.userId = ctx.privateId;
|
|
||||||
} else if (ctx.friendsId !== null) {
|
|
||||||
// FriendsOnly
|
|
||||||
ctx.accessType = 'friends';
|
|
||||||
ctx.userId = ctx.friendsId;
|
|
||||||
} else if (ctx.hiddenId !== null) {
|
|
||||||
// FriendsOfGuests
|
|
||||||
ctx.accessType = 'friends+';
|
|
||||||
ctx.userId = ctx.hiddenId;
|
|
||||||
} else if (ctx.groupId !== null) {
|
|
||||||
// Group
|
|
||||||
ctx.accessType = 'group';
|
|
||||||
}
|
|
||||||
ctx.accessTypeName = ctx.accessType;
|
|
||||||
if (ctx.groupAccessType !== null) {
|
|
||||||
if (ctx.groupAccessType === 'public') {
|
|
||||||
ctx.accessTypeName = 'groupPublic';
|
|
||||||
} else if (ctx.groupAccessType === 'plus') {
|
|
||||||
ctx.accessTypeName = 'groupPlus';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ctx.worldId = _tag;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ctx;
|
|
||||||
},
|
|
||||||
|
|
||||||
displayLocation(location, worldName, groupName) {
|
|
||||||
var text = worldName;
|
|
||||||
var L = this.parseLocation(location);
|
|
||||||
if (L.isOffline) {
|
|
||||||
text = 'Offline';
|
|
||||||
} else if (L.isPrivate) {
|
|
||||||
text = 'Private';
|
|
||||||
} else if (L.isTraveling) {
|
|
||||||
text = 'Traveling';
|
|
||||||
} else if (L.worldId) {
|
|
||||||
if (groupName) {
|
|
||||||
text = `${worldName} ${L.accessTypeName}(${groupName})`;
|
|
||||||
} else if (L.instanceId) {
|
|
||||||
text = `${worldName} ${L.accessTypeName}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return text;
|
|
||||||
},
|
|
||||||
|
|
||||||
extractFileId(s) {
|
|
||||||
var match = String(s).match(/file_[0-9A-Za-z-]+/);
|
|
||||||
return match ? match[0] : '';
|
|
||||||
},
|
|
||||||
|
|
||||||
extractFileVersion(s) {
|
|
||||||
var match = /(?:\/file_[0-9A-Za-z-]+\/)([0-9]+)/gi.exec(s);
|
|
||||||
return match ? match[1] : '';
|
|
||||||
},
|
|
||||||
|
|
||||||
extractVariantVersion(url) {
|
|
||||||
if (!url) {
|
|
||||||
return '0';
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const params = new URLSearchParams(new URL(url).search);
|
|
||||||
const version = params.get('v');
|
|
||||||
if (version) {
|
|
||||||
return version;
|
|
||||||
}
|
|
||||||
return '0';
|
|
||||||
} catch {
|
|
||||||
return '0';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
buildTreeData(json) {
|
buildTreeData(json) {
|
||||||
var node = [];
|
var node = [];
|
||||||
for (var key in json) {
|
for (var key in json) {
|
||||||
@@ -325,8 +140,6 @@ const _utils = {
|
|||||||
});
|
});
|
||||||
return node;
|
return node;
|
||||||
},
|
},
|
||||||
|
|
||||||
// app.js 4900ln
|
|
||||||
// descending
|
// descending
|
||||||
compareByCreatedAt(a, b) {
|
compareByCreatedAt(a, b) {
|
||||||
if (
|
if (
|
||||||
@@ -406,298 +219,69 @@ const _utils = {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
convertFileUrlToImageUrl(url, resolution = 128) {
|
compareByName(a, b) {
|
||||||
if (!url) {
|
if (typeof a.name !== 'string' || typeof b.name !== 'string') {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return a.name.localeCompare(b.name);
|
||||||
|
},
|
||||||
|
replaceBioSymbols(text) {
|
||||||
|
if (!text) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
/**
|
var symbolList = {
|
||||||
* possible patterns?
|
'@': '@',
|
||||||
* /file/file_fileId/version
|
'#': '#',
|
||||||
* /file/file_fileId/version/
|
$: '$',
|
||||||
* /file/file_fileId/version/file
|
'%': '%',
|
||||||
* /file/file_fileId/version/file/
|
'&': '&',
|
||||||
*/
|
'=': '=',
|
||||||
const pattern = /file\/file_([a-f0-9-]+)\/(\d+)(\/file)?\/?$/;
|
'+': '+',
|
||||||
const match = url.match(pattern);
|
'/': '⁄',
|
||||||
|
'\\': '\',
|
||||||
if (match) {
|
';': ';',
|
||||||
const fileId = match[1];
|
':': '˸',
|
||||||
const version = match[2];
|
',': '‚',
|
||||||
return `https://api.vrchat.cloud/api/1/image/file_${fileId}/${version}/${resolution}`;
|
'?': '?',
|
||||||
|
'!': 'ǃ',
|
||||||
|
'"': '"',
|
||||||
|
'<': '≺',
|
||||||
|
'>': '≻',
|
||||||
|
'.': '․',
|
||||||
|
'^': '^',
|
||||||
|
'{': '{',
|
||||||
|
'}': '}',
|
||||||
|
'[': '[',
|
||||||
|
']': ']',
|
||||||
|
'(': '(',
|
||||||
|
')': ')',
|
||||||
|
'|': '|',
|
||||||
|
'*': '∗'
|
||||||
|
};
|
||||||
|
var newText = text;
|
||||||
|
for (var key in symbolList) {
|
||||||
|
var regex = new RegExp(symbolList[key], 'g');
|
||||||
|
newText = newText.replace(regex, key);
|
||||||
}
|
}
|
||||||
// no match return origin url
|
return newText.replace(/ {1,}/g, ' ').trimRight();
|
||||||
return url;
|
|
||||||
},
|
},
|
||||||
replaceVrcPackageUrl(url) {
|
// descending
|
||||||
if (!url) {
|
compareByUpdatedAt(a, b) {
|
||||||
return '';
|
|
||||||
}
|
|
||||||
return url.replace('https://api.vrchat.cloud/', 'https://vrchat.com/');
|
|
||||||
},
|
|
||||||
getLaunchURL(instance) {
|
|
||||||
var L = instance;
|
|
||||||
if (L.instanceId) {
|
|
||||||
if (L.shortName) {
|
|
||||||
return `https://vrchat.com/home/launch?worldId=${encodeURIComponent(
|
|
||||||
L.worldId
|
|
||||||
)}&instanceId=${encodeURIComponent(
|
|
||||||
L.instanceId
|
|
||||||
)}&shortName=${encodeURIComponent(L.shortName)}`;
|
|
||||||
}
|
|
||||||
return `https://vrchat.com/home/launch?worldId=${encodeURIComponent(
|
|
||||||
L.worldId
|
|
||||||
)}&instanceId=${encodeURIComponent(L.instanceId)}`;
|
|
||||||
}
|
|
||||||
return `https://vrchat.com/home/launch?worldId=${encodeURIComponent(
|
|
||||||
L.worldId
|
|
||||||
)}`;
|
|
||||||
},
|
|
||||||
getFaviconUrl(resource) {
|
|
||||||
try {
|
|
||||||
const url = new URL(resource);
|
|
||||||
return `https://icons.duckduckgo.com/ip2/${url.host}.ico`;
|
|
||||||
} catch (err) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
copyToClipboard(text) {
|
|
||||||
navigator.clipboard
|
|
||||||
.writeText(text)
|
|
||||||
.then(() => {
|
|
||||||
window.$app.$message({
|
|
||||||
message: 'Copied successfully!',
|
|
||||||
type: 'success'
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.error('Copy failed:', err);
|
|
||||||
this.$message.error('Copy failed!');
|
|
||||||
});
|
|
||||||
},
|
|
||||||
hasGroupPermission(ref, permission) {
|
|
||||||
if (
|
if (
|
||||||
ref &&
|
typeof a.updated_at !== 'string' ||
|
||||||
ref.myMember &&
|
typeof b.updated_at !== 'string'
|
||||||
ref.myMember.permissions &&
|
|
||||||
(ref.myMember.permissions.includes('*') ||
|
|
||||||
ref.myMember.permissions.includes(permission))
|
|
||||||
) {
|
) {
|
||||||
return true;
|
return 0;
|
||||||
}
|
}
|
||||||
return false;
|
var A = a.updated_at.toUpperCase();
|
||||||
},
|
var B = b.updated_at.toUpperCase();
|
||||||
|
if (A < B) {
|
||||||
compareUnityVersion(unitySortNumber) {
|
return 1;
|
||||||
if (!window.API.cachedConfig.sdkUnityVersion) {
|
|
||||||
console.error('No cachedConfig.sdkUnityVersion');
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
if (A > B) {
|
||||||
// 2022.3.6f1 2022 03 06 000
|
return -1;
|
||||||
// 2019.4.31f1 2019 04 31 000
|
|
||||||
// 5.3.4p1 5 03 04 010
|
|
||||||
// 2019.4.31f1c1 is a thing
|
|
||||||
var array = API.cachedConfig.sdkUnityVersion.split('.');
|
|
||||||
if (array.length < 3) {
|
|
||||||
console.error('Invalid cachedConfig.sdkUnityVersion');
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
var currentUnityVersion = array[0];
|
return 0;
|
||||||
currentUnityVersion += array[1].padStart(2, '0');
|
|
||||||
var indexFirstLetter = array[2].search(/[a-zA-Z]/);
|
|
||||||
if (indexFirstLetter > -1) {
|
|
||||||
currentUnityVersion += array[2]
|
|
||||||
.substr(0, indexFirstLetter)
|
|
||||||
.padStart(2, '0');
|
|
||||||
currentUnityVersion += '0';
|
|
||||||
var letter = array[2].substr(indexFirstLetter, 1);
|
|
||||||
if (letter === 'p') {
|
|
||||||
currentUnityVersion += '1';
|
|
||||||
} else {
|
|
||||||
// f
|
|
||||||
currentUnityVersion += '0';
|
|
||||||
}
|
|
||||||
currentUnityVersion += '0';
|
|
||||||
} else {
|
|
||||||
// just in case
|
|
||||||
currentUnityVersion += '000';
|
|
||||||
}
|
|
||||||
// just in case
|
|
||||||
currentUnityVersion = currentUnityVersion.replace(/\D/g, '');
|
|
||||||
|
|
||||||
if (
|
|
||||||
parseInt(unitySortNumber, 10) <= parseInt(currentUnityVersion, 10)
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
async checkVRChatCache(ref) {
|
|
||||||
if (!ref.unityPackages) {
|
|
||||||
return { Item1: -1, Item2: false, Item3: '' };
|
|
||||||
}
|
|
||||||
var assetUrl = '';
|
|
||||||
var variant = '';
|
|
||||||
for (var i = ref.unityPackages.length - 1; i > -1; i--) {
|
|
||||||
var unityPackage = ref.unityPackages[i];
|
|
||||||
if (unityPackage.variant && unityPackage.variant !== 'security') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
unityPackage.platform === 'standalonewindows' &&
|
|
||||||
_utils.compareUnityVersion(unityPackage.unitySortNumber)
|
|
||||||
) {
|
|
||||||
assetUrl = unityPackage.assetUrl;
|
|
||||||
if (unityPackage.variant !== 'standard') {
|
|
||||||
variant = unityPackage.variant;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!assetUrl) {
|
|
||||||
assetUrl = ref.assetUrl;
|
|
||||||
}
|
|
||||||
var id = _utils.extractFileId(assetUrl);
|
|
||||||
var version = parseInt(_utils.extractFileVersion(assetUrl), 10);
|
|
||||||
var variantVersion = parseInt(
|
|
||||||
_utils.extractVariantVersion(assetUrl),
|
|
||||||
10
|
|
||||||
);
|
|
||||||
if (!id || !version) {
|
|
||||||
return { Item1: -1, Item2: false, Item3: '' };
|
|
||||||
}
|
|
||||||
|
|
||||||
return AssetBundleManager.CheckVRChatCache(
|
|
||||||
id,
|
|
||||||
version,
|
|
||||||
variant,
|
|
||||||
variantVersion
|
|
||||||
);
|
|
||||||
},
|
|
||||||
async deleteVRChatCache(ref) {
|
|
||||||
var assetUrl = '';
|
|
||||||
var variant = '';
|
|
||||||
for (var i = ref.unityPackages.length - 1; i > -1; i--) {
|
|
||||||
var unityPackage = ref.unityPackages[i];
|
|
||||||
if (
|
|
||||||
unityPackage.variant &&
|
|
||||||
unityPackage.variant !== 'standard' &&
|
|
||||||
unityPackage.variant !== 'security'
|
|
||||||
) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
unityPackage.platform === 'standalonewindows' &&
|
|
||||||
$utils.compareUnityVersion(unityPackage.unitySortNumber)
|
|
||||||
) {
|
|
||||||
assetUrl = unityPackage.assetUrl;
|
|
||||||
if (unityPackage.variant !== 'standard') {
|
|
||||||
variant = unityPackage.variant;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var id = $utils.extractFileId(assetUrl);
|
|
||||||
var version = parseInt($utils.extractFileVersion(assetUrl), 10);
|
|
||||||
var variantVersion = parseInt(
|
|
||||||
$utils.extractVariantVersion(assetUrl),
|
|
||||||
10
|
|
||||||
);
|
|
||||||
await AssetBundleManager.DeleteCache(
|
|
||||||
id,
|
|
||||||
version,
|
|
||||||
variant,
|
|
||||||
variantVersion
|
|
||||||
);
|
|
||||||
},
|
|
||||||
downloadAndSaveJson(fileName, data) {
|
|
||||||
if (!fileName || !data) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
var link = document.createElement('a');
|
|
||||||
link.setAttribute(
|
|
||||||
'href',
|
|
||||||
`data:application/json;charset=utf-8,${encodeURIComponent(
|
|
||||||
JSON.stringify(data, null, 2)
|
|
||||||
)}`
|
|
||||||
);
|
|
||||||
link.setAttribute('download', `${fileName}.json`);
|
|
||||||
document.body.appendChild(link);
|
|
||||||
link.click();
|
|
||||||
document.body.removeChild(link);
|
|
||||||
} catch {
|
|
||||||
new Noty({
|
|
||||||
type: 'error',
|
|
||||||
text: $app.escapeTag('Failed to download JSON.')
|
|
||||||
}).show();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getAvailablePlatforms(unityPackages) {
|
|
||||||
var isPC = false;
|
|
||||||
var isQuest = false;
|
|
||||||
var isIos = false;
|
|
||||||
if (typeof unityPackages === 'object') {
|
|
||||||
for (var unityPackage of unityPackages) {
|
|
||||||
if (
|
|
||||||
unityPackage.variant &&
|
|
||||||
unityPackage.variant !== 'standard' &&
|
|
||||||
unityPackage.variant !== 'security'
|
|
||||||
) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (unityPackage.platform === 'standalonewindows') {
|
|
||||||
isPC = true;
|
|
||||||
} else if (unityPackage.platform === 'android') {
|
|
||||||
isQuest = true;
|
|
||||||
} else if (unityPackage.platform === 'ios') {
|
|
||||||
isIos = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { isPC, isQuest, isIos };
|
|
||||||
},
|
|
||||||
getPlatformInfo(unityPackages) {
|
|
||||||
var pc = {};
|
|
||||||
var android = {};
|
|
||||||
var ios = {};
|
|
||||||
if (typeof unityPackages === 'object') {
|
|
||||||
for (var unityPackage of unityPackages) {
|
|
||||||
if (
|
|
||||||
unityPackage.variant &&
|
|
||||||
unityPackage.variant !== 'standard' &&
|
|
||||||
unityPackage.variant !== 'security'
|
|
||||||
) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (unityPackage.platform === 'standalonewindows') {
|
|
||||||
if (
|
|
||||||
unityPackage.performanceRating === 'None' &&
|
|
||||||
pc.performanceRating
|
|
||||||
) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
pc = unityPackage;
|
|
||||||
} else if (unityPackage.platform === 'android') {
|
|
||||||
if (
|
|
||||||
unityPackage.performanceRating === 'None' &&
|
|
||||||
android.performanceRating
|
|
||||||
) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
android = unityPackage;
|
|
||||||
} else if (unityPackage.platform === 'ios') {
|
|
||||||
if (
|
|
||||||
unityPackage.performanceRating === 'None' &&
|
|
||||||
ios.performanceRating
|
|
||||||
) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
ios = unityPackage;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { pc, android, ios };
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { baseClass, $app, API, $t, $utils } from './baseClass.js';
|
|
||||||
import { userRequest } from '../api';
|
import { userRequest } from '../api';
|
||||||
|
import { displayLocation } from '../composables/instance/utils';
|
||||||
|
import { extractFileId, extractFileVersion } from '../composables/shared/utils';
|
||||||
|
import { $app, API, baseClass } from './baseClass.js';
|
||||||
|
|
||||||
export default class extends baseClass {
|
export default class extends baseClass {
|
||||||
constructor(_app, _API, _t) {
|
constructor(_app, _API, _t) {
|
||||||
@@ -356,8 +358,8 @@ export default class extends baseClass {
|
|||||||
|
|
||||||
async notySaveImage(noty) {
|
async notySaveImage(noty) {
|
||||||
var imageUrl = await this.notyGetImage(noty);
|
var imageUrl = await this.notyGetImage(noty);
|
||||||
var fileId = this.extractFileId(imageUrl);
|
var fileId = extractFileId(imageUrl);
|
||||||
var fileVersion = this.extractFileVersion(imageUrl);
|
var fileVersion = extractFileVersion(imageUrl);
|
||||||
var imageLocation = '';
|
var imageLocation = '';
|
||||||
try {
|
try {
|
||||||
if (fileId && fileVersion) {
|
if (fileId && fileVersion) {
|
||||||
@@ -414,7 +416,7 @@ export default class extends baseClass {
|
|||||||
break;
|
break;
|
||||||
case 'GPS':
|
case 'GPS':
|
||||||
this.speak(
|
this.speak(
|
||||||
`${displayName} is in ${this.displayLocation(
|
`${displayName} is in ${displayLocation(
|
||||||
noty.location,
|
noty.location,
|
||||||
noty.worldName,
|
noty.worldName,
|
||||||
noty.groupName
|
noty.groupName
|
||||||
@@ -424,7 +426,7 @@ export default class extends baseClass {
|
|||||||
case 'Online':
|
case 'Online':
|
||||||
var locationName = '';
|
var locationName = '';
|
||||||
if (noty.worldName) {
|
if (noty.worldName) {
|
||||||
locationName = ` to ${this.displayLocation(
|
locationName = ` to ${displayLocation(
|
||||||
noty.location,
|
noty.location,
|
||||||
noty.worldName,
|
noty.worldName,
|
||||||
noty.groupName
|
noty.groupName
|
||||||
@@ -442,7 +444,7 @@ export default class extends baseClass {
|
|||||||
break;
|
break;
|
||||||
case 'invite':
|
case 'invite':
|
||||||
this.speak(
|
this.speak(
|
||||||
`${displayName} has invited you to ${this.displayLocation(
|
`${displayName} has invited you to ${displayLocation(
|
||||||
noty.details.worldId,
|
noty.details.worldId,
|
||||||
noty.details.worldName,
|
noty.details.worldName,
|
||||||
noty.groupName
|
noty.groupName
|
||||||
@@ -513,7 +515,7 @@ export default class extends baseClass {
|
|||||||
case 'PortalSpawn':
|
case 'PortalSpawn':
|
||||||
if (displayName) {
|
if (displayName) {
|
||||||
this.speak(
|
this.speak(
|
||||||
`${displayName} has spawned a portal to ${this.displayLocation(
|
`${displayName} has spawned a portal to ${displayLocation(
|
||||||
noty.instanceId,
|
noty.instanceId,
|
||||||
noty.worldName,
|
noty.worldName,
|
||||||
noty.groupName
|
noty.groupName
|
||||||
@@ -599,7 +601,7 @@ export default class extends baseClass {
|
|||||||
case 'GPS':
|
case 'GPS':
|
||||||
AppApi.XSNotification(
|
AppApi.XSNotification(
|
||||||
'VRCX',
|
'VRCX',
|
||||||
`${noty.displayName} is in ${this.displayLocation(
|
`${noty.displayName} is in ${displayLocation(
|
||||||
noty.location,
|
noty.location,
|
||||||
noty.worldName,
|
noty.worldName,
|
||||||
noty.groupName
|
noty.groupName
|
||||||
@@ -611,7 +613,7 @@ export default class extends baseClass {
|
|||||||
case 'Online':
|
case 'Online':
|
||||||
var locationName = '';
|
var locationName = '';
|
||||||
if (noty.worldName) {
|
if (noty.worldName) {
|
||||||
locationName = ` to ${this.displayLocation(
|
locationName = ` to ${displayLocation(
|
||||||
noty.location,
|
noty.location,
|
||||||
noty.worldName,
|
noty.worldName,
|
||||||
noty.groupName
|
noty.groupName
|
||||||
@@ -645,7 +647,7 @@ export default class extends baseClass {
|
|||||||
'VRCX',
|
'VRCX',
|
||||||
`${
|
`${
|
||||||
noty.senderUsername
|
noty.senderUsername
|
||||||
} has invited you to ${this.displayLocation(
|
} has invited you to ${displayLocation(
|
||||||
noty.details.worldId,
|
noty.details.worldId,
|
||||||
noty.details.worldName
|
noty.details.worldName
|
||||||
)}${message}`,
|
)}${message}`,
|
||||||
@@ -755,7 +757,7 @@ export default class extends baseClass {
|
|||||||
'VRCX',
|
'VRCX',
|
||||||
`${
|
`${
|
||||||
noty.displayName
|
noty.displayName
|
||||||
} has spawned a portal to ${this.displayLocation(
|
} has spawned a portal to ${displayLocation(
|
||||||
noty.instanceId,
|
noty.instanceId,
|
||||||
noty.worldName,
|
noty.worldName,
|
||||||
noty.groupName
|
noty.groupName
|
||||||
@@ -915,7 +917,7 @@ export default class extends baseClass {
|
|||||||
playOvrtHudNotifications,
|
playOvrtHudNotifications,
|
||||||
playOvrtWristNotifications,
|
playOvrtWristNotifications,
|
||||||
'VRCX',
|
'VRCX',
|
||||||
`${noty.displayName} is in ${this.displayLocation(
|
`${noty.displayName} is in ${displayLocation(
|
||||||
noty.location,
|
noty.location,
|
||||||
noty.worldName,
|
noty.worldName,
|
||||||
noty.groupName
|
noty.groupName
|
||||||
@@ -927,7 +929,7 @@ export default class extends baseClass {
|
|||||||
case 'Online':
|
case 'Online':
|
||||||
var locationName = '';
|
var locationName = '';
|
||||||
if (noty.worldName) {
|
if (noty.worldName) {
|
||||||
locationName = ` to ${this.displayLocation(
|
locationName = ` to ${displayLocation(
|
||||||
noty.location,
|
noty.location,
|
||||||
noty.worldName,
|
noty.worldName,
|
||||||
noty.groupName
|
noty.groupName
|
||||||
@@ -969,7 +971,7 @@ export default class extends baseClass {
|
|||||||
'VRCX',
|
'VRCX',
|
||||||
`${
|
`${
|
||||||
noty.senderUsername
|
noty.senderUsername
|
||||||
} has invited you to ${this.displayLocation(
|
} has invited you to ${displayLocation(
|
||||||
noty.details.worldId,
|
noty.details.worldId,
|
||||||
noty.details.worldName
|
noty.details.worldName
|
||||||
)}${message}`,
|
)}${message}`,
|
||||||
@@ -1155,7 +1157,7 @@ export default class extends baseClass {
|
|||||||
'VRCX',
|
'VRCX',
|
||||||
`${
|
`${
|
||||||
noty.displayName
|
noty.displayName
|
||||||
} has spawned a portal to ${this.displayLocation(
|
} has spawned a portal to ${displayLocation(
|
||||||
noty.instanceId,
|
noty.instanceId,
|
||||||
noty.worldName,
|
noty.worldName,
|
||||||
noty.groupName
|
noty.groupName
|
||||||
@@ -1345,7 +1347,7 @@ export default class extends baseClass {
|
|||||||
case 'GPS':
|
case 'GPS':
|
||||||
this.desktopNotification(
|
this.desktopNotification(
|
||||||
noty.displayName,
|
noty.displayName,
|
||||||
`is in ${this.displayLocation(
|
`is in ${displayLocation(
|
||||||
noty.location,
|
noty.location,
|
||||||
noty.worldName,
|
noty.worldName,
|
||||||
noty.groupName
|
noty.groupName
|
||||||
@@ -1356,7 +1358,7 @@ export default class extends baseClass {
|
|||||||
case 'Online':
|
case 'Online':
|
||||||
var locationName = '';
|
var locationName = '';
|
||||||
if (noty.worldName) {
|
if (noty.worldName) {
|
||||||
locationName = ` to ${this.displayLocation(
|
locationName = ` to ${displayLocation(
|
||||||
noty.location,
|
noty.location,
|
||||||
noty.worldName,
|
noty.worldName,
|
||||||
noty.groupName
|
noty.groupName
|
||||||
@@ -1385,7 +1387,7 @@ export default class extends baseClass {
|
|||||||
case 'invite':
|
case 'invite':
|
||||||
this.desktopNotification(
|
this.desktopNotification(
|
||||||
noty.senderUsername,
|
noty.senderUsername,
|
||||||
`has invited you to ${this.displayLocation(
|
`has invited you to ${displayLocation(
|
||||||
noty.details.worldId,
|
noty.details.worldId,
|
||||||
noty.details.worldName
|
noty.details.worldName
|
||||||
)}${message}`,
|
)}${message}`,
|
||||||
@@ -1515,7 +1517,7 @@ export default class extends baseClass {
|
|||||||
if (noty.displayName) {
|
if (noty.displayName) {
|
||||||
this.desktopNotification(
|
this.desktopNotification(
|
||||||
noty.displayName,
|
noty.displayName,
|
||||||
`has spawned a portal to ${this.displayLocation(
|
`has spawned a portal to ${displayLocation(
|
||||||
noty.instanceId,
|
noty.instanceId,
|
||||||
noty.worldName,
|
noty.worldName,
|
||||||
noty.groupName
|
noty.groupName
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import * as workerTimers from 'worker-timers';
|
import * as workerTimers from 'worker-timers';
|
||||||
import Noty from 'noty';
|
import Noty from 'noty';
|
||||||
|
import { parseLocation } from '../composables/instance/utils';
|
||||||
import { baseClass, $app, API, $utils } from './baseClass.js';
|
import { baseClass, $app, API, $utils } from './baseClass.js';
|
||||||
import { groupRequest } from '../api';
|
import { groupRequest } from '../api';
|
||||||
|
|
||||||
@@ -265,8 +266,8 @@ export default class extends baseClass {
|
|||||||
case 'friend-online':
|
case 'friend-online':
|
||||||
// Where is instanceId, travelingToWorld, travelingToInstance?
|
// Where is instanceId, travelingToWorld, travelingToInstance?
|
||||||
// More JANK, what a mess
|
// More JANK, what a mess
|
||||||
var $location = $utils.parseLocation(content.location);
|
var $location = parseLocation(content.location);
|
||||||
var $travelingToLocation = $utils.parseLocation(
|
var $travelingToLocation = parseLocation(
|
||||||
content.travelingToLocation
|
content.travelingToLocation
|
||||||
);
|
);
|
||||||
if (content?.user?.id) {
|
if (content?.user?.id) {
|
||||||
@@ -367,8 +368,8 @@ export default class extends baseClass {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'friend-location':
|
case 'friend-location':
|
||||||
var $location = $utils.parseLocation(content.location);
|
var $location = parseLocation(content.location);
|
||||||
var $travelingToLocation = $utils.parseLocation(
|
var $travelingToLocation = parseLocation(
|
||||||
content.travelingToLocation
|
content.travelingToLocation
|
||||||
);
|
);
|
||||||
if (!content?.user?.id) {
|
if (!content?.user?.id) {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import utils from '../classes/utils';
|
import { parseLocation } from '../composables/instance/utils';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
// eslint-disable-next-line vue/multi-word-component-names
|
// eslint-disable-next-line vue/multi-word-component-names
|
||||||
@@ -74,7 +74,7 @@
|
|||||||
instanceId = this.traveling;
|
instanceId = this.traveling;
|
||||||
this.isTraveling = true;
|
this.isTraveling = true;
|
||||||
}
|
}
|
||||||
const L = utils.parseLocation(instanceId);
|
const L = parseLocation(instanceId);
|
||||||
if (L.isOffline) {
|
if (L.isOffline) {
|
||||||
this.text = 'Offline';
|
this.text = 'Offline';
|
||||||
} else if (L.isPrivate) {
|
} else if (L.isPrivate) {
|
||||||
@@ -150,7 +150,7 @@
|
|||||||
if (!location || !this.link) {
|
if (!location || !this.link) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const L = utils.parseLocation(location);
|
const L = parseLocation(location);
|
||||||
if (!L.groupId) {
|
if (!L.groupId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<safe-dialog
|
||||||
ref="avatarDialogRef"
|
ref="avatarDialogRef"
|
||||||
class="x-dialog x-avatar-dialog"
|
class="x-dialog x-avatar-dialog"
|
||||||
:before-close="beforeDialogClose"
|
|
||||||
:visible.sync="avatarDialog.visible"
|
:visible.sync="avatarDialog.visible"
|
||||||
:show-close="false"
|
:show-close="false"
|
||||||
width="600px"
|
width="600px">
|
||||||
@mousedown.native="dialogMouseDown"
|
|
||||||
@mouseup.native="dialogMouseUp">
|
|
||||||
<div v-loading="avatarDialog.loading">
|
<div v-loading="avatarDialog.loading">
|
||||||
<div style="display: flex">
|
<div style="display: flex">
|
||||||
<el-popover placement="right" width="500px" trigger="click">
|
<el-popover placement="right" width="500px" trigger="click">
|
||||||
@@ -506,26 +503,40 @@
|
|||||||
</div>
|
</div>
|
||||||
<SetAvatarTagsDialog :set-avatar-tags-dialog="setAvatarTagsDialog" />
|
<SetAvatarTagsDialog :set-avatar-tags-dialog="setAvatarTagsDialog" />
|
||||||
<SetAvatarStylesDialog :set-avatar-styles-dialog="setAvatarStylesDialog" />
|
<SetAvatarStylesDialog :set-avatar-styles-dialog="setAvatarStylesDialog" />
|
||||||
</el-dialog>
|
<ChangeAvatarImageDialog
|
||||||
|
:change-avatar-image-dialog-visible.sync="changeAvatarImageDialogVisible"
|
||||||
|
:previous-images-table="previousImagesTable"
|
||||||
|
:avatar-dialog="avatarDialog"
|
||||||
|
:previous-images-file-id="previousImagesFileId"
|
||||||
|
@refresh="displayPreviousImages" />
|
||||||
|
<PreviousImagesDialog
|
||||||
|
:previous-images-dialog-visible.sync="previousImagesDialogVisible"
|
||||||
|
:previous-images-table="previousImagesTable" />
|
||||||
|
</safe-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { inject, computed, getCurrentInstance, reactive, nextTick, watch, ref } from 'vue';
|
import { computed, getCurrentInstance, inject, nextTick, reactive, ref, watch } from 'vue';
|
||||||
import utils from '../../../classes/utils';
|
|
||||||
import database from '../../../service/database';
|
|
||||||
import { avatarModerationRequest, avatarRequest, favoriteRequest, miscRequest } from '../../../api';
|
|
||||||
import { useI18n } from 'vue-i18n-bridge';
|
import { useI18n } from 'vue-i18n-bridge';
|
||||||
|
import { avatarModerationRequest, avatarRequest, favoriteRequest, imageRequest, miscRequest } from '../../../api';
|
||||||
import SetAvatarTagsDialog from './SetAvatarTagsDialog.vue';
|
import utils from '../../../classes/utils';
|
||||||
|
import { compareUnityVersion, storeAvatarImage } from '../../../composables/avatar/utils';
|
||||||
|
import {
|
||||||
|
copyToClipboard,
|
||||||
|
downloadAndSaveJson,
|
||||||
|
extractFileId,
|
||||||
|
extractFileVersion,
|
||||||
|
replaceVrcPackageUrl
|
||||||
|
} from '../../../composables/shared/utils';
|
||||||
|
import database from '../../../service/database';
|
||||||
|
import PreviousImagesDialog from '../PreviousImagesDialog.vue';
|
||||||
|
import ChangeAvatarImageDialog from './ChangeAvatarImageDialog.vue';
|
||||||
import SetAvatarStylesDialog from './SetAvatarStylesDialog.vue';
|
import SetAvatarStylesDialog from './SetAvatarStylesDialog.vue';
|
||||||
|
import SetAvatarTagsDialog from './SetAvatarTagsDialog.vue';
|
||||||
|
|
||||||
const API = inject('API');
|
const API = inject('API');
|
||||||
const beforeDialogClose = inject('beforeDialogClose');
|
|
||||||
const dialogMouseDown = inject('dialogMouseDown');
|
|
||||||
const dialogMouseUp = inject('dialogMouseUp');
|
|
||||||
const showFullscreenImageDialog = inject('showFullscreenImageDialog');
|
const showFullscreenImageDialog = inject('showFullscreenImageDialog');
|
||||||
const showUserDialog = inject('showUserDialog');
|
const showUserDialog = inject('showUserDialog');
|
||||||
const displayPreviousImages = inject('displayPreviousImages');
|
|
||||||
const showAvatarDialog = inject('showAvatarDialog');
|
const showAvatarDialog = inject('showAvatarDialog');
|
||||||
const showFavoriteDialog = inject('showFavoriteDialog');
|
const showFavoriteDialog = inject('showFavoriteDialog');
|
||||||
const openExternalLink = inject('openExternalLink');
|
const openExternalLink = inject('openExternalLink');
|
||||||
@@ -533,11 +544,9 @@
|
|||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const instance = getCurrentInstance();
|
const instance = getCurrentInstance();
|
||||||
const $message = instance.proxy.$message;
|
const { $message, $confirm, $prompt } = instance.proxy;
|
||||||
const $confirm = instance.proxy.$confirm;
|
|
||||||
const $prompt = instance.proxy.$prompt;
|
|
||||||
|
|
||||||
const emit = defineEmits(['openFolderGeneric', 'deleteVRChatCache']);
|
const emit = defineEmits(['openFolderGeneric', 'deleteVRChatCache', 'openPreviousImagesDialog']);
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
avatarDialog: {
|
avatarDialog: {
|
||||||
@@ -555,6 +564,10 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
const avatarDialogRef = ref(null);
|
const avatarDialogRef = ref(null);
|
||||||
|
const changeAvatarImageDialogVisible = ref(false);
|
||||||
|
const previousImagesFileId = ref('');
|
||||||
|
const previousImagesDialogVisible = ref(false);
|
||||||
|
const previousImagesTable = ref([]);
|
||||||
|
|
||||||
const treeData = ref([]);
|
const treeData = ref([]);
|
||||||
const timeSpent = ref(0);
|
const timeSpent = ref(0);
|
||||||
@@ -671,16 +684,16 @@
|
|||||||
showAvatarDialog(D.id);
|
showAvatarDialog(D.id);
|
||||||
break;
|
break;
|
||||||
case 'Share':
|
case 'Share':
|
||||||
utils.copyToClipboard(D.id);
|
copyToClipboard(D.id);
|
||||||
break;
|
break;
|
||||||
case 'Rename':
|
case 'Rename':
|
||||||
promptRenameAvatar(D);
|
promptRenameAvatar(D);
|
||||||
break;
|
break;
|
||||||
case 'Change Image':
|
case 'Change Image':
|
||||||
displayPreviousImages('Avatar', 'Change');
|
displayPreviousImages('Change');
|
||||||
break;
|
break;
|
||||||
case 'Previous Images':
|
case 'Previous Images':
|
||||||
displayPreviousImages('Avatar', 'Display');
|
displayPreviousImages('Display');
|
||||||
break;
|
break;
|
||||||
case 'Change Description':
|
case 'Change Description':
|
||||||
promptChangeAvatarDescription(D);
|
promptChangeAvatarDescription(D);
|
||||||
@@ -692,7 +705,7 @@
|
|||||||
showSetAvatarStylesDialog(D.id);
|
showSetAvatarStylesDialog(D.id);
|
||||||
break;
|
break;
|
||||||
case 'Download Unity Package':
|
case 'Download Unity Package':
|
||||||
openExternalLink(utils.replaceVrcPackageUrl(props.avatarDialog.ref.unityPackageUrl));
|
openExternalLink(replaceVrcPackageUrl(props.avatarDialog.ref.unityPackageUrl));
|
||||||
break;
|
break;
|
||||||
case 'Add Favorite':
|
case 'Add Favorite':
|
||||||
showFavoriteDialog('avatar', D.id);
|
showFavoriteDialog('avatar', D.id);
|
||||||
@@ -858,6 +871,54 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function displayPreviousImages(command) {
|
||||||
|
previousImagesTable.value = [];
|
||||||
|
previousImagesFileId.value = '';
|
||||||
|
const { imageUrl } = props.avatarDialog.ref;
|
||||||
|
const fileId = extractFileId(imageUrl);
|
||||||
|
if (!fileId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const params = {
|
||||||
|
fileId
|
||||||
|
};
|
||||||
|
if (command === 'Display') {
|
||||||
|
previousImagesDialogVisible.value = true;
|
||||||
|
}
|
||||||
|
if (command === 'Change') {
|
||||||
|
changeAvatarImageDialogVisible.value = true;
|
||||||
|
}
|
||||||
|
imageRequest.getAvatarImages(params).then((args) => {
|
||||||
|
storeAvatarImage(args);
|
||||||
|
previousImagesFileId.value = args.json.id;
|
||||||
|
|
||||||
|
const images = [];
|
||||||
|
args.json.versions.forEach((item) => {
|
||||||
|
if (!item.deleted) {
|
||||||
|
images.unshift(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
checkPreviousImageAvailable(images);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkPreviousImageAvailable(images) {
|
||||||
|
previousImagesTable.value = [];
|
||||||
|
for (const image of images) {
|
||||||
|
if (image.file && image.file.url) {
|
||||||
|
const response = await fetch(image.file.url, {
|
||||||
|
method: 'HEAD',
|
||||||
|
redirect: 'follow'
|
||||||
|
}).catch((error) => {
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
if (response.status === 200) {
|
||||||
|
previousImagesTable.value.push(image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function selectAvatar(id) {
|
function selectAvatar(id) {
|
||||||
avatarRequest
|
avatarRequest
|
||||||
.selectAvatar({
|
.selectAvatar({
|
||||||
@@ -937,11 +998,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function copyAvatarId(id) {
|
function copyAvatarId(id) {
|
||||||
utils.copyToClipboard(id);
|
copyToClipboard(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
function copyAvatarUrl(id) {
|
function copyAvatarUrl(id) {
|
||||||
utils.copyToClipboard(`https://vrchat.com/home/avatar/${id}`);
|
copyToClipboard(`https://vrchat.com/home/avatar/${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function timeToText(time) {
|
function timeToText(time) {
|
||||||
@@ -963,10 +1024,7 @@
|
|||||||
if (unityPackage.variant !== 'security') {
|
if (unityPackage.variant !== 'security') {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (
|
if (unityPackage.platform === 'standalonewindows' && compareUnityVersion(unityPackage.unitySortNumber)) {
|
||||||
unityPackage.platform === 'standalonewindows' &&
|
|
||||||
utils.compareUnityVersion(unityPackage.unitySortNumber)
|
|
||||||
) {
|
|
||||||
assetUrl = unityPackage.assetUrl;
|
assetUrl = unityPackage.assetUrl;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -979,7 +1037,7 @@
|
|||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
unityPackage.platform === 'standalonewindows' &&
|
unityPackage.platform === 'standalonewindows' &&
|
||||||
utils.compareUnityVersion(unityPackage.unitySortNumber)
|
compareUnityVersion(unityPackage.unitySortNumber)
|
||||||
) {
|
) {
|
||||||
variant = 'standard';
|
variant = 'standard';
|
||||||
assetUrl = unityPackage.assetUrl;
|
assetUrl = unityPackage.assetUrl;
|
||||||
@@ -990,8 +1048,8 @@
|
|||||||
if (!assetUrl) {
|
if (!assetUrl) {
|
||||||
assetUrl = D.ref.assetUrl;
|
assetUrl = D.ref.assetUrl;
|
||||||
}
|
}
|
||||||
const fileId = utils.extractFileId(assetUrl);
|
const fileId = extractFileId(assetUrl);
|
||||||
const version = parseInt(utils.extractFileVersion(assetUrl), 10);
|
const version = parseInt(extractFileVersion(assetUrl), 10);
|
||||||
if (!fileId || !version) {
|
if (!fileId || !version) {
|
||||||
$message({
|
$message({
|
||||||
message: 'File Analysis unavailable',
|
message: 'File Analysis unavailable',
|
||||||
@@ -1100,8 +1158,4 @@
|
|||||||
D.loading = false;
|
D.loading = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function downloadAndSaveJson(fileName, data) {
|
|
||||||
utils.downloadAndSaveJson(fileName, data);
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
393
src/components/dialogs/AvatarDialog/ChangeAvatarImageDialog.vue
Normal file
393
src/components/dialogs/AvatarDialog/ChangeAvatarImageDialog.vue
Normal file
@@ -0,0 +1,393 @@
|
|||||||
|
<template>
|
||||||
|
<safe-dialog
|
||||||
|
class="x-dialog"
|
||||||
|
:visible="changeAvatarImageDialogVisible"
|
||||||
|
:title="t('dialog.change_content_image.avatar')"
|
||||||
|
width="850px"
|
||||||
|
append-to-body
|
||||||
|
@close="closeDialog">
|
||||||
|
<div v-loading="changeAvatarImageDialogLoading">
|
||||||
|
<input
|
||||||
|
id="AvatarImageUploadButton"
|
||||||
|
type="file"
|
||||||
|
accept="image/*"
|
||||||
|
style="display: none"
|
||||||
|
@change="onFileChangeAvatarImage" />
|
||||||
|
<span>{{ t('dialog.change_content_image.description') }}</span>
|
||||||
|
<br />
|
||||||
|
<el-button-group style="padding-bottom: 10px; padding-top: 10px">
|
||||||
|
<el-button type="default" size="small" icon="el-icon-refresh" @click="refresh">
|
||||||
|
{{ t('dialog.change_content_image.refresh') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button type="default" size="small" icon="el-icon-upload2" @click="uploadAvatarImage">
|
||||||
|
{{ t('dialog.change_content_image.upload') }}
|
||||||
|
</el-button>
|
||||||
|
</el-button-group>
|
||||||
|
<br />
|
||||||
|
<div
|
||||||
|
v-for="image in previousImagesTable"
|
||||||
|
v-if="image.file"
|
||||||
|
:key="image.version"
|
||||||
|
style="display: inline-block">
|
||||||
|
<div
|
||||||
|
class="x-change-image-item"
|
||||||
|
style="cursor: pointer"
|
||||||
|
:class="{ 'current-image': compareCurrentImage(image) }"
|
||||||
|
@click="setAvatarImage(image)">
|
||||||
|
<img v-lazy="image.file.url" class="image" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</safe-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { getCurrentInstance, inject, ref } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n-bridge';
|
||||||
|
import { imageRequest } from '../../../api';
|
||||||
|
import { extractFileId } from '../../../composables/shared/utils';
|
||||||
|
import webApiService from '../../../service/webapi';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const instance = getCurrentInstance();
|
||||||
|
const $message = instance.proxy.$message;
|
||||||
|
|
||||||
|
const API = inject('API');
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
changeAvatarImageDialogVisible: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
previousImagesTable: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
avatarDialog: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
previousImagesFileId: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const changeAvatarImageDialogLoading = ref(false);
|
||||||
|
const avatarImage = ref({
|
||||||
|
base64File: '',
|
||||||
|
fileMd5: '',
|
||||||
|
base64SignatureFile: '',
|
||||||
|
signatureMd5: '',
|
||||||
|
fileId: '',
|
||||||
|
avatarId: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:changeAvatarImageDialogVisible', 'refresh']);
|
||||||
|
|
||||||
|
function refresh() {
|
||||||
|
emit('refresh', 'Change');
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeDialog() {
|
||||||
|
emit('update:changeAvatarImageDialogVisible', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resizeImageToFitLimits(file) {
|
||||||
|
const response = await AppApi.ResizeImageToFitLimits(file);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function genMd5(file) {
|
||||||
|
const response = await AppApi.MD5File(file);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function genSig(file) {
|
||||||
|
const response = await AppApi.SignFile(file);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function genLength(file) {
|
||||||
|
const response = await AppApi.FileLength(file);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onFileChangeAvatarImage(e) {
|
||||||
|
const clearFile = function () {
|
||||||
|
if (document.querySelector('#AvatarImageUploadButton')) {
|
||||||
|
document.querySelector('#AvatarImageUploadButton').value = '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const files = e.target.files || e.dataTransfer.files;
|
||||||
|
if (!files.length || !props.avatarDialog.visible || props.avatarDialog.loading) {
|
||||||
|
clearFile();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate file
|
||||||
|
if (files[0].size >= 100000000) {
|
||||||
|
// 100MB
|
||||||
|
$message({
|
||||||
|
message: t('message.file.too_large'),
|
||||||
|
type: 'error'
|
||||||
|
});
|
||||||
|
clearFile();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!files[0].type.match(/image.*/)) {
|
||||||
|
$message({
|
||||||
|
message: t('message.file.not_image'),
|
||||||
|
type: 'error'
|
||||||
|
});
|
||||||
|
clearFile();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const r = new FileReader();
|
||||||
|
r.onload = async function (file) {
|
||||||
|
try {
|
||||||
|
const base64File = await resizeImageToFitLimits(btoa(r.result));
|
||||||
|
// 10MB
|
||||||
|
const fileMd5 = await genMd5(base64File);
|
||||||
|
const fileSizeInBytes = parseInt(file.total, 10);
|
||||||
|
const base64SignatureFile = await genSig(base64File);
|
||||||
|
const signatureMd5 = await genMd5(base64SignatureFile);
|
||||||
|
const signatureSizeInBytes = parseInt(await genLength(base64SignatureFile), 10);
|
||||||
|
|
||||||
|
const avatarId = props.avatarDialog.id;
|
||||||
|
const { imageUrl } = props.avatarDialog.ref;
|
||||||
|
|
||||||
|
const fileId = extractFileId(imageUrl);
|
||||||
|
if (!fileId) {
|
||||||
|
$message({
|
||||||
|
message: t('message.avatar.image_invalid'),
|
||||||
|
type: 'error'
|
||||||
|
});
|
||||||
|
clearFile();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
avatarImage.value = {
|
||||||
|
base64File,
|
||||||
|
fileMd5,
|
||||||
|
base64SignatureFile,
|
||||||
|
signatureMd5,
|
||||||
|
fileId,
|
||||||
|
avatarId
|
||||||
|
};
|
||||||
|
const params = {
|
||||||
|
fileMd5,
|
||||||
|
fileSizeInBytes,
|
||||||
|
signatureMd5,
|
||||||
|
signatureSizeInBytes
|
||||||
|
};
|
||||||
|
|
||||||
|
// Upload chaining
|
||||||
|
await initiateUpload(params, fileId);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Avatar image upload process failed:', error);
|
||||||
|
} finally {
|
||||||
|
changeAvatarImageDialogLoading.value = false;
|
||||||
|
clearFile();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
changeAvatarImageDialogLoading.value = true;
|
||||||
|
r.readAsBinaryString(files[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------ Upload Process Start ------------
|
||||||
|
|
||||||
|
async function initiateUpload(params, fileId) {
|
||||||
|
const res = await imageRequest.uploadAvatarImage(params, fileId);
|
||||||
|
return avatarImageInit(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function avatarImageInit(args) {
|
||||||
|
// API.$on('AVATARIMAGE:INIT')
|
||||||
|
const fileId = args.json.id;
|
||||||
|
const fileVersion = args.json.versions[args.json.versions.length - 1].version;
|
||||||
|
const params = {
|
||||||
|
fileId,
|
||||||
|
fileVersion
|
||||||
|
};
|
||||||
|
const res = await imageRequest.uploadAvatarImageFileStart(params);
|
||||||
|
return avatarImageFileStart(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function avatarImageFileStart(args) {
|
||||||
|
// API.$on('AVATARIMAGE:FILESTART')
|
||||||
|
const { url } = args.json;
|
||||||
|
const { fileId, fileVersion } = args.params;
|
||||||
|
const params = {
|
||||||
|
url,
|
||||||
|
fileId,
|
||||||
|
fileVersion
|
||||||
|
};
|
||||||
|
return uploadAvatarImageFileAWS(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function uploadAvatarImageFileAWS(params) {
|
||||||
|
const json = await webApiService.execute({
|
||||||
|
url: params.url,
|
||||||
|
uploadFilePUT: true,
|
||||||
|
fileData: avatarImage.value.base64File,
|
||||||
|
fileMIME: 'image/png',
|
||||||
|
headers: {
|
||||||
|
'Content-MD5': avatarImage.value.fileMd5
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (json.status !== 200) {
|
||||||
|
changeAvatarImageDialogLoading.value = false;
|
||||||
|
API.$throw('Avatar image upload failed', json, params.url);
|
||||||
|
}
|
||||||
|
const args = {
|
||||||
|
json,
|
||||||
|
params
|
||||||
|
};
|
||||||
|
return avatarImageFileAWS(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function avatarImageFileAWS(args) {
|
||||||
|
// API.$on('AVATARIMAGE:FILEAWS')
|
||||||
|
const { fileId, fileVersion } = args.params;
|
||||||
|
const params = {
|
||||||
|
fileId,
|
||||||
|
fileVersion
|
||||||
|
};
|
||||||
|
const res = await imageRequest.uploadAvatarImageFileFinish(params);
|
||||||
|
return avatarImageFileFinish(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function avatarImageFileFinish(args) {
|
||||||
|
// API.$on('AVATARIMAGE:FILEFINISH')
|
||||||
|
const { fileId, fileVersion } = args.params;
|
||||||
|
const params = {
|
||||||
|
fileId,
|
||||||
|
fileVersion
|
||||||
|
};
|
||||||
|
const res = await imageRequest.uploadAvatarImageSigStart(params);
|
||||||
|
return avatarImageSigStart(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function avatarImageSigStart(args) {
|
||||||
|
// API.$on('AVATARIMAGE:SIGSTART')
|
||||||
|
const { url } = args.json;
|
||||||
|
const { fileId, fileVersion } = args.params;
|
||||||
|
const params = {
|
||||||
|
url,
|
||||||
|
fileId,
|
||||||
|
fileVersion
|
||||||
|
};
|
||||||
|
return uploadAvatarImageSigAWS(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function uploadAvatarImageSigAWS(params) {
|
||||||
|
const json = await webApiService.execute({
|
||||||
|
url: params.url,
|
||||||
|
uploadFilePUT: true,
|
||||||
|
fileData: avatarImage.value.base64SignatureFile,
|
||||||
|
fileMIME: 'application/x-rsync-signature',
|
||||||
|
headers: {
|
||||||
|
'Content-MD5': avatarImage.value.signatureMd5
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (json.status !== 200) {
|
||||||
|
changeAvatarImageDialogLoading.value = false;
|
||||||
|
API.$throw('Avatar image upload failed', json, params.url);
|
||||||
|
}
|
||||||
|
const args = {
|
||||||
|
json,
|
||||||
|
params
|
||||||
|
};
|
||||||
|
return avatarImageSigAWS(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function avatarImageSigAWS(args) {
|
||||||
|
// API.$on('AVATARIMAGE:SIGAWS')
|
||||||
|
const { fileId, fileVersion } = args.params;
|
||||||
|
const params = {
|
||||||
|
fileId,
|
||||||
|
fileVersion
|
||||||
|
};
|
||||||
|
const res = await imageRequest.uploadAvatarImageSigFinish(params);
|
||||||
|
return avatarImageSigFinish(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function avatarImageSigFinish(args) {
|
||||||
|
// API.$on('AVATARIMAGE:SIGFINISH')
|
||||||
|
const { fileId, fileVersion } = args.params;
|
||||||
|
const parmas = {
|
||||||
|
id: avatarImage.value.avatarId,
|
||||||
|
imageUrl: `${API.endpointDomain}/file/${fileId}/${fileVersion}/file`
|
||||||
|
};
|
||||||
|
const res = await imageRequest.setAvatarImage(parmas);
|
||||||
|
return avatarImageSet(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function avatarImageSet(args) {
|
||||||
|
// API.$on('AVATARIMAGE:SET')
|
||||||
|
changeAvatarImageDialogLoading.value = false;
|
||||||
|
if (args.json.imageUrl === args.params.imageUrl) {
|
||||||
|
$message({
|
||||||
|
message: t('message.avatar.image_changed'),
|
||||||
|
type: 'success'
|
||||||
|
});
|
||||||
|
refresh();
|
||||||
|
} else {
|
||||||
|
API.$throw(0, 'Avatar image change failed', args.params.imageUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------ Upload Process End ------------
|
||||||
|
|
||||||
|
function uploadAvatarImage() {
|
||||||
|
document.getElementById('AvatarImageUploadButton').click();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setAvatarImage(image) {
|
||||||
|
changeAvatarImageDialogLoading.value = true;
|
||||||
|
const parmas = {
|
||||||
|
id: props.avatarDialog.id,
|
||||||
|
imageUrl: `${API.endpointDomain}/file/${props.previousImagesFileId}/${image.version}/file`
|
||||||
|
};
|
||||||
|
imageRequest.setAvatarImage(parmas).finally(() => {
|
||||||
|
changeAvatarImageDialogLoading.value = false;
|
||||||
|
closeDialog();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function compareCurrentImage(image) {
|
||||||
|
return (
|
||||||
|
`${API.endpointDomain}/file/${props.previousImagesFileId}/${image.version}/file` ===
|
||||||
|
props.avatarDialog.ref.imageUrl
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// $app.methods.deleteAvatarImage = function () {
|
||||||
|
// this.changeAvatarImageDialogLoading = true;
|
||||||
|
// var parmas = {
|
||||||
|
// fileId: this.previousImagesFileId,
|
||||||
|
// version: this.previousImagesTable[0].version
|
||||||
|
// };
|
||||||
|
// vrcPlusIconRequest
|
||||||
|
// .deleteFileVersion(parmas)
|
||||||
|
// .then((args) => {
|
||||||
|
// this.previousImagesFileId = args.json.id;
|
||||||
|
// var images = [];
|
||||||
|
// args.json.versions.forEach((item) => {
|
||||||
|
// if (!item.deleted) {
|
||||||
|
// images.unshift(item);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// this.checkPreviousImageAvailable(images);
|
||||||
|
// })
|
||||||
|
// .finally(() => {
|
||||||
|
// this.changeAvatarImageDialogLoading = false;
|
||||||
|
// });
|
||||||
|
// };
|
||||||
|
</script>
|
||||||
@@ -1,14 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<safe-dialog
|
||||||
ref="setAvatarStylesDialog"
|
ref="setAvatarStylesDialog"
|
||||||
class="x-dialog"
|
class="x-dialog"
|
||||||
:before-close="beforeDialogClose"
|
|
||||||
:visible.sync="setAvatarStylesDialog.visible"
|
:visible.sync="setAvatarStylesDialog.visible"
|
||||||
:title="t('dialog.set_avatar_styles.header')"
|
:title="t('dialog.set_avatar_styles.header')"
|
||||||
width="400px"
|
width="400px"
|
||||||
append-to-body
|
append-to-body>
|
||||||
@mousedown.native="dialogMouseDown"
|
|
||||||
@mouseup.native="dialogMouseUp">
|
|
||||||
<template v-if="setAvatarStylesDialog.visible">
|
<template v-if="setAvatarStylesDialog.visible">
|
||||||
<div>
|
<div>
|
||||||
<span>{{ t('dialog.set_avatar_styles.primary_style') }}</span>
|
<span>{{ t('dialog.set_avatar_styles.primary_style') }}</span>
|
||||||
@@ -50,19 +47,15 @@
|
|||||||
t('dialog.set_avatar_styles.save')
|
t('dialog.set_avatar_styles.save')
|
||||||
}}</el-button>
|
}}</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</safe-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { inject, watch, getCurrentInstance } from 'vue';
|
import { watch, getCurrentInstance } from 'vue';
|
||||||
|
|
||||||
import { useI18n } from 'vue-i18n-bridge';
|
import { useI18n } from 'vue-i18n-bridge';
|
||||||
import { avatarRequest } from '../../../api';
|
import { avatarRequest } from '../../../api';
|
||||||
|
|
||||||
const beforeDialogClose = inject('beforeDialogClose');
|
|
||||||
const dialogMouseDown = inject('dialogMouseDown');
|
|
||||||
const dialogMouseUp = inject('dialogMouseUp');
|
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const instance = getCurrentInstance();
|
const instance = getCurrentInstance();
|
||||||
const $message = instance.proxy.$message;
|
const $message = instance.proxy.$message;
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<safe-dialog
|
||||||
ref="setAvatarTagsDialog"
|
ref="setAvatarTagsDialog"
|
||||||
class="x-dialog"
|
class="x-dialog"
|
||||||
:before-close="beforeDialogClose"
|
|
||||||
:visible.sync="setAvatarTagsDialog.visible"
|
:visible.sync="setAvatarTagsDialog.visible"
|
||||||
:title="t('dialog.set_avatar_tags.header')"
|
:title="t('dialog.set_avatar_tags.header')"
|
||||||
width="770px"
|
width="770px"
|
||||||
append-to-body
|
append-to-body>
|
||||||
@mousedown.native="dialogMouseDown"
|
|
||||||
@mouseup.native="dialogMouseUp">
|
|
||||||
<template v-if="setAvatarTagsDialog.visible">
|
<template v-if="setAvatarTagsDialog.visible">
|
||||||
<el-checkbox v-model="setAvatarTagsDialog.contentHorror" @change="updateSelectedAvatarTags">{{
|
<el-checkbox v-model="setAvatarTagsDialog.contentHorror" @change="updateSelectedAvatarTags">{{
|
||||||
t('dialog.set_avatar_tags.content_horror')
|
t('dialog.set_avatar_tags.content_horror')
|
||||||
@@ -93,7 +90,7 @@
|
|||||||
t('dialog.set_avatar_tags.save')
|
t('dialog.set_avatar_tags.save')
|
||||||
}}</el-button>
|
}}</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</safe-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
@@ -102,9 +99,6 @@
|
|||||||
import { useI18n } from 'vue-i18n-bridge';
|
import { useI18n } from 'vue-i18n-bridge';
|
||||||
import { avatarRequest } from '../../../api';
|
import { avatarRequest } from '../../../api';
|
||||||
|
|
||||||
const beforeDialogClose = inject('beforeDialogClose');
|
|
||||||
const dialogMouseDown = inject('dialogMouseDown');
|
|
||||||
const dialogMouseUp = inject('dialogMouseUp');
|
|
||||||
const showAvatarDialog = inject('showAvatarDialog');
|
const showAvatarDialog = inject('showAvatarDialog');
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|||||||
@@ -1,12 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<safe-dialog ref="favoriteDialog" :visible.sync="isVisible" :title="$t('dialog.favorite.header')" width="300px">
|
||||||
ref="favoriteDialog"
|
|
||||||
:before-close="beforeDialogClose"
|
|
||||||
:visible.sync="isVisible"
|
|
||||||
:title="$t('dialog.favorite.header')"
|
|
||||||
width="300px"
|
|
||||||
@mousedown.native="dialogMouseDown"
|
|
||||||
@mouseup.native="dialogMouseUp">
|
|
||||||
<div v-loading="loading">
|
<div v-loading="loading">
|
||||||
<span style="display: block; text-align: center">{{ $t('dialog.favorite.vrchat_favorites') }}</span>
|
<span style="display: block; text-align: center">{{ $t('dialog.favorite.vrchat_favorites') }}</span>
|
||||||
<template v-if="favoriteDialog.currentGroup && favoriteDialog.currentGroup.key">
|
<template v-if="favoriteDialog.currentGroup && favoriteDialog.currentGroup.key">
|
||||||
@@ -69,7 +62,7 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</el-dialog>
|
</safe-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -78,7 +71,7 @@
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ChooseFavoriteGroupDialog',
|
name: 'ChooseFavoriteGroupDialog',
|
||||||
inject: ['API', 'beforeDialogClose', 'dialogMouseDown', 'dialogMouseUp', 'adjustDialogZ'],
|
inject: ['API', 'adjustDialogZ'],
|
||||||
props: {
|
props: {
|
||||||
favoriteDialog: {
|
favoriteDialog: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -186,7 +179,7 @@
|
|||||||
this.$emit('remove-local-avatar-favorite', ...args);
|
this.$emit('remove-local-avatar-favorite', ...args);
|
||||||
},
|
},
|
||||||
deleteFavoriteNoConfirm(...args) {
|
deleteFavoriteNoConfirm(...args) {
|
||||||
this.$emit('delete-favorite-no-confirm', ...args);
|
this.$emit('deleteFavoriteNoConfirm', ...args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
91
src/components/dialogs/FullscreenImageDialog.vue
Normal file
91
src/components/dialogs/FullscreenImageDialog.vue
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
<template>
|
||||||
|
<safe-dialog
|
||||||
|
ref="fullscreenImageDialog"
|
||||||
|
class="x-dialog"
|
||||||
|
:visible.sync="fullscreenImageDialog.visible"
|
||||||
|
append-to-body
|
||||||
|
top="1vh"
|
||||||
|
width="97vw">
|
||||||
|
<div>
|
||||||
|
<div style="margin: 0 0 5px 5px">
|
||||||
|
<el-button
|
||||||
|
size="mini"
|
||||||
|
icon="el-icon-s-order"
|
||||||
|
circle
|
||||||
|
@click="copyImageUrl(fullscreenImageDialog.imageUrl)"></el-button>
|
||||||
|
<el-button
|
||||||
|
type="default"
|
||||||
|
size="mini"
|
||||||
|
icon="el-icon-download"
|
||||||
|
circle
|
||||||
|
style="margin-left: 5px"
|
||||||
|
@click="
|
||||||
|
downloadAndSaveImage(fullscreenImageDialog.imageUrl, fullscreenImageDialog.fileName)
|
||||||
|
"></el-button>
|
||||||
|
</div>
|
||||||
|
<img v-lazy="fullscreenImageDialog.imageUrl" style="width: 100%; height: 85vh; object-fit: contain" />
|
||||||
|
</div>
|
||||||
|
</safe-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { getCurrentInstance } from 'vue';
|
||||||
|
import utils from '../../classes/utils';
|
||||||
|
import { copyToClipboard, extractFileId } from '../../composables/shared/utils';
|
||||||
|
import webApiService from '../../service/webapi';
|
||||||
|
import Noty from 'noty';
|
||||||
|
|
||||||
|
const { proxy } = getCurrentInstance();
|
||||||
|
const { $message } = proxy;
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
fullscreenImageDialog: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function copyImageUrl(imageUrl) {
|
||||||
|
copyToClipboard(imageUrl, 'ImageUrl copied to clipboard');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function downloadAndSaveImage(url, fileName) {
|
||||||
|
if (!url) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$message({
|
||||||
|
message: 'Downloading image...',
|
||||||
|
type: 'info'
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
const response = await webApiService.execute({
|
||||||
|
url,
|
||||||
|
method: 'GET'
|
||||||
|
});
|
||||||
|
if (response.status !== 200 || !response.data.startsWith('data:image/png')) {
|
||||||
|
throw new Error(`Error: ${response.data}`);
|
||||||
|
}
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = response.data;
|
||||||
|
const fileId = extractFileId(url);
|
||||||
|
if (!fileName && fileId) {
|
||||||
|
fileName = `${fileId}.png`;
|
||||||
|
}
|
||||||
|
if (!fileName) {
|
||||||
|
fileName = `${url.split('/').pop()}.png`;
|
||||||
|
}
|
||||||
|
if (!fileName) {
|
||||||
|
fileName = 'image.png';
|
||||||
|
}
|
||||||
|
link.setAttribute('download', fileName);
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
} catch {
|
||||||
|
new Noty({
|
||||||
|
type: 'error',
|
||||||
|
text: utils.escapeTag(`Failed to download image. ${url}`)
|
||||||
|
}).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
136
src/components/dialogs/GroupDialog/GallerySelectDialog.vue
Normal file
136
src/components/dialogs/GroupDialog/GallerySelectDialog.vue
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
<template>
|
||||||
|
<safe-dialog
|
||||||
|
class="x-dialog"
|
||||||
|
:visible.sync="gallerySelectDialog.visible"
|
||||||
|
:title="t('dialog.gallery_select.header')"
|
||||||
|
width="100%"
|
||||||
|
append-to-body>
|
||||||
|
<div>
|
||||||
|
<span>{{ t('dialog.gallery_select.gallery') }}</span>
|
||||||
|
<span style="color: #909399; font-size: 12px; margin-left: 5px">{{ galleryTable.length }}/64</span>
|
||||||
|
<br />
|
||||||
|
<input
|
||||||
|
id="GalleryUploadButton"
|
||||||
|
type="file"
|
||||||
|
accept="image/*"
|
||||||
|
style="display: none"
|
||||||
|
@change="onFileChangeGallery" />
|
||||||
|
<el-button-group>
|
||||||
|
<el-button type="default" size="small" icon="el-icon-close" @click="selectImageGallerySelect('', '')">{{
|
||||||
|
t('dialog.gallery_select.none')
|
||||||
|
}}</el-button>
|
||||||
|
<el-button type="default" size="small" icon="el-icon-refresh" @click="refreshGalleryTable">{{
|
||||||
|
t('dialog.gallery_select.refresh')
|
||||||
|
}}</el-button>
|
||||||
|
<el-button
|
||||||
|
type="default"
|
||||||
|
size="small"
|
||||||
|
icon="el-icon-upload2"
|
||||||
|
:disabled="!API.currentUser.$isVRCPlus"
|
||||||
|
@click="displayGalleryUpload"
|
||||||
|
>{{ t('dialog.gallery_select.upload') }}</el-button
|
||||||
|
>
|
||||||
|
</el-button-group>
|
||||||
|
<br />
|
||||||
|
<div
|
||||||
|
v-for="image in galleryTable"
|
||||||
|
v-if="image.versions && image.versions.length > 0"
|
||||||
|
:key="image.id"
|
||||||
|
class="x-friend-item"
|
||||||
|
style="display: inline-block; margin-top: 10px; width: unset; cursor: default">
|
||||||
|
<div
|
||||||
|
v-if="image.versions[image.versions.length - 1].file.url"
|
||||||
|
class="vrcplus-icon"
|
||||||
|
@click="selectImageGallerySelect(image.versions[image.versions.length - 1].file.url, image.id)">
|
||||||
|
<img v-lazy="image.versions[image.versions.length - 1].file.url" class="avatar" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</safe-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { inject, getCurrentInstance } from 'vue';
|
||||||
|
|
||||||
|
import { useI18n } from 'vue-i18n-bridge';
|
||||||
|
import { vrcPlusImageRequest } from '../../../api';
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const { proxy } = getCurrentInstance();
|
||||||
|
const { $message } = proxy;
|
||||||
|
|
||||||
|
const API = inject('API');
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
gallerySelectDialog: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
galleryTable: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['refreshGalleryTable']);
|
||||||
|
|
||||||
|
function selectImageGallerySelect(imageUrl, fileId) {
|
||||||
|
const D = props.gallerySelectDialog;
|
||||||
|
D.selectedFileId = fileId;
|
||||||
|
D.selectedImageUrl = imageUrl;
|
||||||
|
D.visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayGalleryUpload() {
|
||||||
|
document.getElementById('GalleryUploadButton').click();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onFileChangeGallery(e) {
|
||||||
|
const clearFile = function () {
|
||||||
|
if (document.querySelector('#GalleryUploadButton')) {
|
||||||
|
document.querySelector('#GalleryUploadButton').value = '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const files = e.target.files || e.dataTransfer.files;
|
||||||
|
if (!files.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (files[0].size >= 100000000) {
|
||||||
|
// 100MB
|
||||||
|
$message({
|
||||||
|
message: t('message.file.too_large'),
|
||||||
|
type: 'error'
|
||||||
|
});
|
||||||
|
clearFile();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!files[0].type.match(/image.*/)) {
|
||||||
|
$message({
|
||||||
|
message: t('message.file.not_image'),
|
||||||
|
type: 'error'
|
||||||
|
});
|
||||||
|
clearFile();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const r = new FileReader();
|
||||||
|
r.onload = function () {
|
||||||
|
const base64Body = btoa(r.result);
|
||||||
|
vrcPlusImageRequest.uploadGalleryImage(base64Body).then((args) => {
|
||||||
|
$message({
|
||||||
|
message: t('message.gallery.uploaded'),
|
||||||
|
type: 'success'
|
||||||
|
});
|
||||||
|
// API.$on('GALLERYIMAGE:ADD')
|
||||||
|
if (Object.keys(props.galleryTable).length !== 0) {
|
||||||
|
props.galleryTable.unshift(args.json);
|
||||||
|
}
|
||||||
|
return args;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
r.readAsBinaryString(files[0]);
|
||||||
|
clearFile();
|
||||||
|
}
|
||||||
|
function refreshGalleryTable() {
|
||||||
|
emit('refreshGalleryTable');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -1,14 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<safe-dialog
|
||||||
ref="groupDialogRef"
|
ref="groupDialogRef"
|
||||||
:before-close="beforeDialogClose"
|
|
||||||
:visible.sync="groupDialog.visible"
|
:visible.sync="groupDialog.visible"
|
||||||
:show-close="false"
|
:show-close="false"
|
||||||
width="770px"
|
width="770px"
|
||||||
top="10vh"
|
top="10vh"
|
||||||
class="x-dialog x-group-dialog"
|
class="x-dialog x-group-dialog">
|
||||||
@mousedown.native="dialogMouseDown"
|
|
||||||
@mouseup.native="dialogMouseUp">
|
|
||||||
<div class="group-banner-image">
|
<div class="group-banner-image">
|
||||||
<el-popover placement="right" width="500px" trigger="click">
|
<el-popover placement="right" width="500px" trigger="click">
|
||||||
<img
|
<img
|
||||||
@@ -1151,10 +1148,7 @@
|
|||||||
</el-tabs>
|
</el-tabs>
|
||||||
</div>
|
</div>
|
||||||
<!--Nested-->
|
<!--Nested-->
|
||||||
<GroupPostEditDialog
|
<GroupPostEditDialog :dialog-data.sync="groupPostEditDialog" :selected-gallery-file="selectedGalleryFile" />
|
||||||
:gallery-select-dialog="gallerySelectDialog"
|
|
||||||
:dialog-data.sync="groupPostEditDialog"
|
|
||||||
@clear-image-gallery-select="clearImageGallerySelect" />
|
|
||||||
<GroupMemberModerationDialog
|
<GroupMemberModerationDialog
|
||||||
:group-dialog="groupDialog"
|
:group-dialog="groupDialog"
|
||||||
:is-group-members-loading.sync="isGroupMembersLoading"
|
:is-group-members-loading.sync="isGroupMembersLoading"
|
||||||
@@ -1167,25 +1161,32 @@
|
|||||||
@load-all-group-members="loadAllGroupMembers"
|
@load-all-group-members="loadAllGroupMembers"
|
||||||
@set-group-member-filter="setGroupMemberFilter"
|
@set-group-member-filter="setGroupMemberFilter"
|
||||||
@set-group-member-sort-order="setGroupMemberSortOrder" />
|
@set-group-member-sort-order="setGroupMemberSortOrder" />
|
||||||
</el-dialog>
|
<InviteGroupDialog
|
||||||
|
:dialog-data.sync="inviteGroupDialog"
|
||||||
|
:vip-friends="vipFriends"
|
||||||
|
:online-friends="onlineFriends"
|
||||||
|
:offline-friends="offlineFriends"
|
||||||
|
:active-friends="activeFriends" />
|
||||||
|
</safe-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { getCurrentInstance, nextTick, reactive, ref, watch, inject } from 'vue';
|
import { getCurrentInstance, inject, nextTick, reactive, ref, watch } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n-bridge';
|
import { useI18n } from 'vue-i18n-bridge';
|
||||||
import utils from '../../../classes/utils';
|
|
||||||
import { groupRequest } from '../../../api';
|
|
||||||
import Location from '../../Location.vue';
|
|
||||||
import GroupPostEditDialog from './GroupPostEditDialog.vue';
|
|
||||||
import GroupMemberModerationDialog from './GroupMemberModerationDialog.vue';
|
|
||||||
import * as workerTimers from 'worker-timers';
|
import * as workerTimers from 'worker-timers';
|
||||||
|
import { groupRequest } from '../../../api';
|
||||||
|
import utils from '../../../classes/utils';
|
||||||
|
import { hasGroupPermission } from '../../../composables/group/utils';
|
||||||
|
import { refreshInstancePlayerCount } from '../../../composables/instance/utils';
|
||||||
|
import { copyToClipboard, downloadAndSaveJson, getFaviconUrl } from '../../../composables/shared/utils';
|
||||||
|
import { languageClass } from '../../../composables/user/utils';
|
||||||
|
import Location from '../../Location.vue';
|
||||||
|
import InviteGroupDialog from '../InviteGroupDialog.vue';
|
||||||
|
import GroupMemberModerationDialog from './GroupMemberModerationDialog.vue';
|
||||||
|
import GroupPostEditDialog from './GroupPostEditDialog.vue';
|
||||||
|
|
||||||
const API = inject('API');
|
const API = inject('API');
|
||||||
const beforeDialogClose = inject('beforeDialogClose');
|
|
||||||
const dialogMouseDown = inject('dialogMouseDown');
|
|
||||||
const dialogMouseUp = inject('dialogMouseUp');
|
|
||||||
const showFullscreenImageDialog = inject('showFullscreenImageDialog');
|
const showFullscreenImageDialog = inject('showFullscreenImageDialog');
|
||||||
const languageClass = inject('languageClass');
|
|
||||||
const showUserDialog = inject('showUserDialog');
|
const showUserDialog = inject('showUserDialog');
|
||||||
const userStatusClass = inject('userStatusClass');
|
const userStatusClass = inject('userStatusClass');
|
||||||
const userImage = inject('userImage');
|
const userImage = inject('userImage');
|
||||||
@@ -1222,28 +1223,33 @@
|
|||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
gallerySelectDialog: {
|
|
||||||
type: Object,
|
|
||||||
default: () => ({})
|
|
||||||
},
|
|
||||||
randomUserColours: {
|
randomUserColours: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true
|
default: true
|
||||||
|
},
|
||||||
|
vipFriends: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
onlineFriends: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
offlineFriends: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
activeFriends: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits([
|
const emit = defineEmits([
|
||||||
'update:group-dialog',
|
'update:group-dialog',
|
||||||
'update:gallery-select-dialog',
|
'groupDialogCommand',
|
||||||
'update:group-member-moderation',
|
|
||||||
'group-dialog-command',
|
|
||||||
'update:group-dialog',
|
|
||||||
'get-group-dialog-group',
|
'get-group-dialog-group',
|
||||||
'get-group-dialog-group-members',
|
'updateGroupPostSearch'
|
||||||
'refresh-instance-player-count',
|
|
||||||
'update-group-post-search',
|
|
||||||
'set-group-member-sort-order',
|
|
||||||
'clear-image-gallery-select'
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const groupDialogRef = ref(null);
|
const groupDialogRef = ref(null);
|
||||||
@@ -1254,6 +1260,10 @@
|
|||||||
const groupDialogGalleryCurrentName = ref('0');
|
const groupDialogGalleryCurrentName = ref('0');
|
||||||
const groupDialogTabCurrentName = ref('0');
|
const groupDialogTabCurrentName = ref('0');
|
||||||
const isGroupGalleryLoading = ref(false);
|
const isGroupGalleryLoading = ref(false);
|
||||||
|
const selectedGalleryFile = ref({
|
||||||
|
selectedFileId: '',
|
||||||
|
selectedImageUrl: ''
|
||||||
|
});
|
||||||
const groupPostEditDialog = reactive({
|
const groupPostEditDialog = reactive({
|
||||||
visible: false,
|
visible: false,
|
||||||
groupRef: {},
|
groupRef: {},
|
||||||
@@ -1273,6 +1283,16 @@
|
|||||||
auditLogTypes: []
|
auditLogTypes: []
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const inviteGroupDialog = ref({
|
||||||
|
visible: false,
|
||||||
|
loading: false,
|
||||||
|
groupId: '',
|
||||||
|
groupName: '',
|
||||||
|
userId: '',
|
||||||
|
userIds: [],
|
||||||
|
userObject: {}
|
||||||
|
});
|
||||||
|
|
||||||
let loadMoreGroupMembersParams = {};
|
let loadMoreGroupMembersParams = {};
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
@@ -1293,8 +1313,15 @@
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
function getFaviconUrl(link) {
|
function showInviteGroupDialog(groupId, userId) {
|
||||||
return utils.getFaviconUrl(link);
|
const D = inviteGroupDialog.value;
|
||||||
|
D.userIds = '';
|
||||||
|
D.groups = [];
|
||||||
|
D.groupId = groupId;
|
||||||
|
D.groupName = groupId;
|
||||||
|
D.userId = userId;
|
||||||
|
D.userObject = {};
|
||||||
|
D.visible = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setGroupRepresentation(groupId) {
|
function setGroupRepresentation(groupId) {
|
||||||
@@ -1439,9 +1466,7 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function copyToClipboard(text) {
|
|
||||||
utils.copyToClipboard(text);
|
|
||||||
}
|
|
||||||
function groupGalleryStatus(gallery) {
|
function groupGalleryStatus(gallery) {
|
||||||
const style = {};
|
const style = {};
|
||||||
if (!gallery.membersOnly) {
|
if (!gallery.membersOnly) {
|
||||||
@@ -1455,6 +1480,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function groupDialogCommand(command) {
|
function groupDialogCommand(command) {
|
||||||
|
const D = props.groupDialog;
|
||||||
|
if (D.visible === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
switch (command) {
|
switch (command) {
|
||||||
case 'Share':
|
case 'Share':
|
||||||
copyToClipboard(props.groupDialog.ref.$url);
|
copyToClipboard(props.groupDialog.ref.$url);
|
||||||
@@ -1465,8 +1494,11 @@
|
|||||||
case 'Moderation Tools':
|
case 'Moderation Tools':
|
||||||
showGroupMemberModerationDialog(props.groupDialog.id);
|
showGroupMemberModerationDialog(props.groupDialog.id);
|
||||||
break;
|
break;
|
||||||
|
case 'Invite To Group':
|
||||||
|
showInviteGroupDialog(D.id, '');
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
emit('group-dialog-command', command);
|
emit('groupDialogCommand', command);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1481,7 +1513,7 @@
|
|||||||
D.auditLogTypes = [];
|
D.auditLogTypes = [];
|
||||||
API.getCachedGroup({ groupId }).then((args) => {
|
API.getCachedGroup({ groupId }).then((args) => {
|
||||||
D.groupRef = args.ref;
|
D.groupRef = args.ref;
|
||||||
if (utils.hasGroupPermission(D.groupRef, 'group-audit-view')) {
|
if (hasGroupPermission(D.groupRef, 'group-audit-view')) {
|
||||||
groupRequest.getGroupAuditLogTypes({ groupId }).then((args) => {
|
groupRequest.getGroupAuditLogTypes({ groupId }).then((args) => {
|
||||||
// API.$on('GROUP:AUDITLOGTYPES', function (args) {
|
// API.$on('GROUP:AUDITLOGTYPES', function (args) {
|
||||||
if (groupMemberModeration.id !== args.params.groupId) {
|
if (groupMemberModeration.id !== args.params.groupId) {
|
||||||
@@ -1569,18 +1601,21 @@
|
|||||||
D.roleIds = [];
|
D.roleIds = [];
|
||||||
D.postId = '';
|
D.postId = '';
|
||||||
D.groupId = groupId;
|
D.groupId = groupId;
|
||||||
emit('update:gallery-select-dialog', { ...D, selectedFileId: '', selectedImageUrl: '' });
|
selectedGalleryFile.value = {
|
||||||
|
selectedFileId: '',
|
||||||
|
selectedImageUrl: ''
|
||||||
|
};
|
||||||
|
|
||||||
if (post) {
|
if (post) {
|
||||||
D.title = post.title;
|
D.title = post.title;
|
||||||
D.text = post.text;
|
D.text = post.text;
|
||||||
D.visibility = post.visibility;
|
D.visibility = post.visibility;
|
||||||
D.roleIds = post.roleIds;
|
D.roleIds = post.roleIds;
|
||||||
D.postId = post.id;
|
D.postId = post.id;
|
||||||
emit('update:gallery-select-dialog', {
|
selectedGalleryFile.value = {
|
||||||
...D,
|
|
||||||
selectedFileId: post.imageId,
|
selectedFileId: post.imageId,
|
||||||
selectedImageUrl: post.imageUrl
|
selectedImageUrl: post.imageUrl
|
||||||
});
|
};
|
||||||
}
|
}
|
||||||
API.getCachedGroup({ groupId }).then((args) => {
|
API.getCachedGroup({ groupId }).then((args) => {
|
||||||
D.groupRef = args.ref;
|
D.groupRef = args.ref;
|
||||||
@@ -1763,9 +1798,6 @@
|
|||||||
await getGroupDialogGroupMembers();
|
await getGroupDialogGroupMembers();
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasGroupPermission(ref, permission) {
|
|
||||||
return utils.hasGroupPermission(ref, permission);
|
|
||||||
}
|
|
||||||
function updateGroupDialogData(obj) {
|
function updateGroupDialogData(obj) {
|
||||||
// Be careful with the deep merge
|
// Be careful with the deep merge
|
||||||
emit('update:group-dialog', obj);
|
emit('update:group-dialog', obj);
|
||||||
@@ -1773,16 +1805,7 @@
|
|||||||
function getGroupDialogGroup(groupId) {
|
function getGroupDialogGroup(groupId) {
|
||||||
emit('get-group-dialog-group', groupId);
|
emit('get-group-dialog-group', groupId);
|
||||||
}
|
}
|
||||||
function refreshInstancePlayerCount(tag) {
|
|
||||||
emit('refresh-instance-player-count', tag);
|
|
||||||
}
|
|
||||||
function updateGroupPostSearch() {
|
function updateGroupPostSearch() {
|
||||||
emit('update-group-post-search');
|
emit('updateGroupPostSearch');
|
||||||
}
|
|
||||||
function downloadAndSaveJson(fileName, data) {
|
|
||||||
utils.downloadAndSaveJson(fileName, data);
|
|
||||||
}
|
|
||||||
function clearImageGallerySelect() {
|
|
||||||
emit('clear-image-gallery-select');
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<safe-dialog
|
||||||
class="x-dialog"
|
class="x-dialog"
|
||||||
:before-close="beforeDialogClose"
|
|
||||||
:visible="groupMemberModeration.visible"
|
:visible="groupMemberModeration.visible"
|
||||||
:title="t('dialog.group_member_moderation.header')"
|
:title="t('dialog.group_member_moderation.header')"
|
||||||
append-to-body
|
append-to-body
|
||||||
top="5vh"
|
top="5vh"
|
||||||
width="90vw"
|
width="90vw"
|
||||||
@close="closeDialog"
|
@close="closeDialog">
|
||||||
@mousedown.native="dialogMouseDown"
|
|
||||||
@mouseup.native="dialogMouseUp">
|
|
||||||
<div>
|
<div>
|
||||||
<h3>{{ groupMemberModeration.groupRef.name }}</h3>
|
<h3>{{ groupMemberModeration.groupRef.name }}</h3>
|
||||||
<el-tabs type="card" style="height: 100%">
|
<el-tabs type="card" style="height: 100%">
|
||||||
@@ -806,21 +803,18 @@
|
|||||||
<group-member-moderation-export-dialog
|
<group-member-moderation-export-dialog
|
||||||
:is-group-logs-export-dialog-visible.sync="isGroupLogsExportDialogVisible"
|
:is-group-logs-export-dialog-visible.sync="isGroupLogsExportDialogVisible"
|
||||||
:group-logs-moderation-table="groupLogsModerationTable" />
|
:group-logs-moderation-table="groupLogsModerationTable" />
|
||||||
</el-dialog>
|
</safe-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { getCurrentInstance, inject, ref, watch } from 'vue';
|
import { getCurrentInstance, inject, ref, watch } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n-bridge';
|
import { useI18n } from 'vue-i18n-bridge';
|
||||||
import utils from '../../../classes/utils';
|
|
||||||
import { groupRequest, userRequest } from '../../../api';
|
import { groupRequest, userRequest } from '../../../api';
|
||||||
|
import { useModerationTable, useSelectedUsers } from '../../../composables/group/useGroupMemberModeration';
|
||||||
|
import { hasGroupPermission } from '../../../composables/group/utils';
|
||||||
import GroupMemberModerationExportDialog from './GroupMemberModerationExportDialog.vue';
|
import GroupMemberModerationExportDialog from './GroupMemberModerationExportDialog.vue';
|
||||||
import { useModerationTable, useSelectedUsers } from '../../../composables/groups/useGroupMemberModeration';
|
|
||||||
|
|
||||||
const API = inject('API');
|
const API = inject('API');
|
||||||
const beforeDialogClose = inject('beforeDialogClose');
|
|
||||||
const dialogMouseDown = inject('dialogMouseDown');
|
|
||||||
const dialogMouseUp = inject('dialogMouseUp');
|
|
||||||
const showUserDialog = inject('showUserDialog');
|
const showUserDialog = inject('showUserDialog');
|
||||||
const userImage = inject('userImage');
|
const userImage = inject('userImage');
|
||||||
const userImageFull = inject('userImageFull');
|
const userImageFull = inject('userImageFull');
|
||||||
@@ -1687,8 +1681,4 @@
|
|||||||
.replace(/\./g, ' ')
|
.replace(/\./g, ' ')
|
||||||
.replace(/\b\w/g, (l) => l.toUpperCase());
|
.replace(/\b\w/g, (l) => l.toUpperCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasGroupPermission(ref, permission) {
|
|
||||||
return utils.hasGroupPermission(ref, permission);
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<safe-dialog
|
||||||
class="x-dialog"
|
class="x-dialog"
|
||||||
:before-close="beforeDialogClose"
|
|
||||||
:visible="isGroupLogsExportDialogVisible"
|
:visible="isGroupLogsExportDialogVisible"
|
||||||
:title="t('dialog.group_member_moderation.export_logs')"
|
:title="t('dialog.group_member_moderation.export_logs')"
|
||||||
width="650px"
|
width="650px"
|
||||||
append-to-body
|
append-to-body
|
||||||
@close="setIsGroupLogsExportDialogVisible"
|
@close="setIsGroupLogsExportDialogVisible">
|
||||||
@mousedown.native="dialogMouseDown"
|
|
||||||
@mouseup.native="dialogMouseUp">
|
|
||||||
<el-checkbox-group
|
<el-checkbox-group
|
||||||
v-model="checkedGroupLogsExportLogsOptions"
|
v-model="checkedGroupLogsExportLogsOptions"
|
||||||
style="margin-bottom: 10px"
|
style="margin-bottom: 10px"
|
||||||
@@ -29,17 +26,14 @@
|
|||||||
readonly
|
readonly
|
||||||
style="margin-top: 15px"
|
style="margin-top: 15px"
|
||||||
@click.native="handleCopyGroupLogsExportContent" />
|
@click.native="handleCopyGroupLogsExportContent" />
|
||||||
</el-dialog>
|
</safe-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { inject, ref, watch } from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n-bridge';
|
import { useI18n } from 'vue-i18n-bridge';
|
||||||
import utils from '../../../classes/utils';
|
import { copyToClipboard } from '../../../composables/shared/utils';
|
||||||
|
|
||||||
const beforeDialogClose = inject('beforeDialogClose');
|
|
||||||
const dialogMouseDown = inject('dialogMouseDown');
|
|
||||||
const dialogMouseUp = inject('dialogMouseUp');
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -101,7 +95,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleCopyGroupLogsExportContent() {
|
function handleCopyGroupLogsExportContent() {
|
||||||
utils.copyToClipboard(groupLogsExportContent.value);
|
copyToClipboard(groupLogsExportContent.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setIsGroupLogsExportDialogVisible() {
|
function setIsGroupLogsExportDialogVisible() {
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<safe-dialog
|
||||||
ref="groupPostEditDialog"
|
|
||||||
:before-close="beforeDialogClose"
|
|
||||||
:visible.sync="groupPostEditDialog.visible"
|
:visible.sync="groupPostEditDialog.visible"
|
||||||
:title="$t('dialog.group_post_edit.header')"
|
:title="$t('dialog.group_post_edit.header')"
|
||||||
width="650px"
|
width="650px"
|
||||||
append-to-body
|
append-to-body>
|
||||||
@mousedown.native="dialogMouseDown"
|
|
||||||
@mouseup.native="dialogMouseUp">
|
|
||||||
<div v-if="groupPostEditDialog.visible">
|
<div v-if="groupPostEditDialog.visible">
|
||||||
<h3 v-text="groupPostEditDialog.groupRef.name"></h3>
|
<h3 v-text="groupPostEditDialog.groupRef.name"></h3>
|
||||||
<el-form :model="groupPostEditDialog" label-width="150px">
|
<el-form :model="groupPostEditDialog" label-width="150px">
|
||||||
@@ -107,30 +103,39 @@
|
|||||||
{{ $t('dialog.group_post_edit.create_post') }}
|
{{ $t('dialog.group_post_edit.create_post') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
<GallerySelectDialog
|
||||||
|
:gallery-select-dialog="gallerySelectDialog"
|
||||||
|
:gallery-table="galleryTable"
|
||||||
|
@refresh-gallery-table="refreshGalleryTable" />
|
||||||
|
</safe-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { groupRequest } from '../../../api';
|
import { groupRequest, vrcPlusIconRequest } from '../../../api';
|
||||||
|
import GallerySelectDialog from './GallerySelectDialog.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'GroupPostEditDialog',
|
name: 'GroupPostEditDialog',
|
||||||
inject: [
|
components: {
|
||||||
'beforeDialogClose',
|
GallerySelectDialog
|
||||||
'showFullscreenImageDialog',
|
},
|
||||||
'dialogMouseDown',
|
inject: ['showFullscreenImageDialog'],
|
||||||
'dialogMouseUp',
|
|
||||||
'showGallerySelectDialog'
|
|
||||||
],
|
|
||||||
props: {
|
props: {
|
||||||
dialogData: {
|
dialogData: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
gallerySelectDialog: {
|
selectedGalleryFile: { type: Object, default: () => ({}) }
|
||||||
type: Object,
|
},
|
||||||
required: true
|
data() {
|
||||||
}
|
return {
|
||||||
|
gallerySelectDialog: {
|
||||||
|
visible: false,
|
||||||
|
selectedFileId: '',
|
||||||
|
selectedImageUrl: ''
|
||||||
|
},
|
||||||
|
galleryTable: []
|
||||||
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
groupPostEditDialog: {
|
groupPostEditDialog: {
|
||||||
@@ -143,6 +148,22 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
showGallerySelectDialog() {
|
||||||
|
const D = this.gallerySelectDialog;
|
||||||
|
D.visible = true;
|
||||||
|
this.refreshGalleryTable();
|
||||||
|
},
|
||||||
|
async refreshGalleryTable() {
|
||||||
|
const params = {
|
||||||
|
n: 100,
|
||||||
|
tag: 'gallery'
|
||||||
|
};
|
||||||
|
const args = await vrcPlusIconRequest.getFileList(params);
|
||||||
|
// API.$on('FILES:LIST')
|
||||||
|
if (args.params.tag === 'gallery') {
|
||||||
|
this.galleryTable = args.json.reverse();
|
||||||
|
}
|
||||||
|
},
|
||||||
editGroupPost() {
|
editGroupPost() {
|
||||||
const D = this.groupPostEditDialog;
|
const D = this.groupPostEditDialog;
|
||||||
if (!D.groupId || !D.postId) {
|
if (!D.groupId || !D.postId) {
|
||||||
@@ -193,7 +214,9 @@
|
|||||||
D.visible = false;
|
D.visible = false;
|
||||||
},
|
},
|
||||||
clearImageGallerySelect() {
|
clearImageGallerySelect() {
|
||||||
this.$emit('clear-image-gallery-select');
|
const D = this.gallerySelectDialog;
|
||||||
|
D.selectedFileId = '';
|
||||||
|
D.selectedImageUrl = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
212
src/components/dialogs/InviteDialog/EditAndSendInviteDialog.vue
Normal file
212
src/components/dialogs/InviteDialog/EditAndSendInviteDialog.vue
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
<template>
|
||||||
|
<safe-dialog
|
||||||
|
class="x-dialog"
|
||||||
|
:visible="editAndSendInviteDialog.visible"
|
||||||
|
:title="t('dialog.edit_send_invite_message.header')"
|
||||||
|
width="400px"
|
||||||
|
append-to-body
|
||||||
|
@close="cancelEditAndSendInvite">
|
||||||
|
<div style="font-size: 12px">
|
||||||
|
<span>{{ t('dialog.edit_send_invite_message.description') }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-input
|
||||||
|
v-model="editAndSendInviteDialog.newMessage"
|
||||||
|
type="textarea"
|
||||||
|
size="mini"
|
||||||
|
maxlength="64"
|
||||||
|
show-word-limit
|
||||||
|
:autosize="{ minRows: 2, maxRows: 5 }"
|
||||||
|
placeholder=""
|
||||||
|
style="margin-top: 10px"></el-input>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<el-button type="small" @click="cancelEditAndSendInvite">
|
||||||
|
{{ t('dialog.edit_send_invite_message.cancel') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button type="primary" size="small" @click="saveEditAndSendInvite">
|
||||||
|
{{ t('dialog.edit_send_invite_message.send') }}
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</safe-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { getCurrentInstance, inject } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n-bridge';
|
||||||
|
import { instanceRequest, inviteMessagesRequest, notificationRequest } from '../../../api';
|
||||||
|
import { parseLocation } from '../../../composables/instance/utils';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const instance = getCurrentInstance();
|
||||||
|
const $message = instance.proxy.$message;
|
||||||
|
|
||||||
|
const API = inject('API');
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
editAndSendInviteDialog: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
sendInviteDialog: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
inviteDialog: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
uploadImage: {
|
||||||
|
type: String
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:editAndSendInviteDialog', 'closeInviteDialog']);
|
||||||
|
|
||||||
|
function cancelEditAndSendInvite() {
|
||||||
|
emit('update:editAndSendInviteDialog', { ...props.editAndSendInviteDialog, visible: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveEditAndSendInvite() {
|
||||||
|
const D = props.editAndSendInviteDialog;
|
||||||
|
D.visible = false;
|
||||||
|
const messageType = D.messageType;
|
||||||
|
const slot = D.inviteMessage.slot;
|
||||||
|
if (D.inviteMessage.message !== D.newMessage) {
|
||||||
|
const params = {
|
||||||
|
message: D.newMessage
|
||||||
|
};
|
||||||
|
await inviteMessagesRequest
|
||||||
|
.editInviteMessage(params, messageType, slot)
|
||||||
|
.catch((err) => {
|
||||||
|
throw err;
|
||||||
|
})
|
||||||
|
.then((args) => {
|
||||||
|
API.$emit(`INVITE:${messageType.toUpperCase()}`, args);
|
||||||
|
if (args.json[slot].message === D.inviteMessage.message) {
|
||||||
|
$message({
|
||||||
|
message: "VRChat API didn't update message, try again",
|
||||||
|
type: 'error'
|
||||||
|
});
|
||||||
|
throw new Error("VRChat API didn't update message, try again");
|
||||||
|
} else {
|
||||||
|
$message('Invite message updated');
|
||||||
|
}
|
||||||
|
return args;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const I = props.sendInviteDialog;
|
||||||
|
const J = props.inviteDialog;
|
||||||
|
if (J?.visible) {
|
||||||
|
const inviteLoop = () => {
|
||||||
|
if (J.userIds.length > 0) {
|
||||||
|
const receiverUserId = J.userIds.shift();
|
||||||
|
if (receiverUserId === API.currentUser.id) {
|
||||||
|
// can't invite self!?
|
||||||
|
const L = parseLocation(J.worldId);
|
||||||
|
instanceRequest
|
||||||
|
.selfInvite({
|
||||||
|
instanceId: L.instanceId,
|
||||||
|
worldId: L.worldId
|
||||||
|
})
|
||||||
|
.finally(inviteLoop);
|
||||||
|
} else if (props.uploadImage) {
|
||||||
|
notificationRequest
|
||||||
|
.sendInvitePhoto(
|
||||||
|
{
|
||||||
|
instanceId: J.worldId,
|
||||||
|
worldId: J.worldId,
|
||||||
|
worldName: J.worldName,
|
||||||
|
messageSlot: slot
|
||||||
|
},
|
||||||
|
receiverUserId
|
||||||
|
)
|
||||||
|
.finally(inviteLoop);
|
||||||
|
} else {
|
||||||
|
notificationRequest
|
||||||
|
.sendInvite(
|
||||||
|
{
|
||||||
|
instanceId: J.worldId,
|
||||||
|
worldId: J.worldId,
|
||||||
|
worldName: J.worldName,
|
||||||
|
messageSlot: slot
|
||||||
|
},
|
||||||
|
receiverUserId
|
||||||
|
)
|
||||||
|
.finally(inviteLoop);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
J.loading = false;
|
||||||
|
J.visible = false;
|
||||||
|
$message({
|
||||||
|
message: 'Invite sent',
|
||||||
|
type: 'success'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
inviteLoop();
|
||||||
|
} else if (I.messageType === 'invite') {
|
||||||
|
I.params.messageSlot = slot;
|
||||||
|
if (props.uploadImage) {
|
||||||
|
notificationRequest
|
||||||
|
.sendInvitePhoto(I.params, I.userId)
|
||||||
|
.catch((err) => {
|
||||||
|
throw err;
|
||||||
|
})
|
||||||
|
.then((args) => {
|
||||||
|
$message({
|
||||||
|
message: 'Invite photo message sent',
|
||||||
|
type: 'success'
|
||||||
|
});
|
||||||
|
return args;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
notificationRequest
|
||||||
|
.sendInvite(I.params, I.userId)
|
||||||
|
.catch((err) => {
|
||||||
|
throw err;
|
||||||
|
})
|
||||||
|
.then((args) => {
|
||||||
|
$message({
|
||||||
|
message: 'Invite message sent',
|
||||||
|
type: 'success'
|
||||||
|
});
|
||||||
|
return args;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (I.messageType === 'requestInvite') {
|
||||||
|
I.params.requestSlot = slot;
|
||||||
|
if (props.uploadImage) {
|
||||||
|
notificationRequest
|
||||||
|
.sendRequestInvitePhoto(I.params, I.userId)
|
||||||
|
.catch((err) => {
|
||||||
|
this.clearInviteImageUpload();
|
||||||
|
throw err;
|
||||||
|
})
|
||||||
|
.then((args) => {
|
||||||
|
$message({
|
||||||
|
message: 'Request invite photo message sent',
|
||||||
|
type: 'success'
|
||||||
|
});
|
||||||
|
return args;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
notificationRequest
|
||||||
|
.sendRequestInvite(I.params, I.userId)
|
||||||
|
.catch((err) => {
|
||||||
|
throw err;
|
||||||
|
})
|
||||||
|
.then((args) => {
|
||||||
|
$message({
|
||||||
|
message: 'Request invite message sent',
|
||||||
|
type: 'success'
|
||||||
|
});
|
||||||
|
return args;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('closeInviteDialog');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
309
src/components/dialogs/InviteDialog/InviteDialog.vue
Normal file
309
src/components/dialogs/InviteDialog/InviteDialog.vue
Normal file
@@ -0,0 +1,309 @@
|
|||||||
|
<template>
|
||||||
|
<safe-dialog
|
||||||
|
class="x-dialog"
|
||||||
|
:visible.sync="inviteDialog.visible"
|
||||||
|
:title="t('dialog.invite.header')"
|
||||||
|
width="500px"
|
||||||
|
append-to-body>
|
||||||
|
<div v-if="inviteDialog.visible" v-loading="inviteDialog.loading">
|
||||||
|
<location :location="inviteDialog.worldId" :link="false"></location>
|
||||||
|
<br />
|
||||||
|
<el-button size="mini" style="margin-top: 10px" @click="addSelfToInvite">{{
|
||||||
|
t('dialog.invite.add_self')
|
||||||
|
}}</el-button>
|
||||||
|
<el-button
|
||||||
|
size="mini"
|
||||||
|
:disabled="inviteDialog.friendsInInstance.length === 0"
|
||||||
|
style="margin-top: 10px"
|
||||||
|
@click="addFriendsInInstanceToInvite"
|
||||||
|
>{{ t('dialog.invite.add_friends_in_instance') }}</el-button
|
||||||
|
>
|
||||||
|
<el-button
|
||||||
|
size="mini"
|
||||||
|
:disabled="vipFriends.length === 0"
|
||||||
|
style="margin-top: 10px"
|
||||||
|
@click="addFavoriteFriendsToInvite"
|
||||||
|
>{{ t('dialog.invite.add_favorite_friends') }}</el-button
|
||||||
|
>
|
||||||
|
|
||||||
|
<el-select
|
||||||
|
v-model="inviteDialog.userIds"
|
||||||
|
multiple
|
||||||
|
clearable
|
||||||
|
:placeholder="t('dialog.invite.select_placeholder')"
|
||||||
|
filterable
|
||||||
|
:disabled="inviteDialog.loading"
|
||||||
|
style="width: 100%; margin-top: 15px">
|
||||||
|
<el-option-group v-if="API.currentUser" :label="t('side_panel.me')">
|
||||||
|
<el-option
|
||||||
|
class="x-friend-item"
|
||||||
|
:label="API.currentUser.displayName"
|
||||||
|
:value="API.currentUser.id"
|
||||||
|
style="height: auto">
|
||||||
|
<div :class="['avatar', userStatusClass(API.currentUser)]">
|
||||||
|
<img v-lazy="userImage(API.currentUser)" />
|
||||||
|
</div>
|
||||||
|
<div class="detail">
|
||||||
|
<span class="name">{{ API.currentUser.displayName }}</span>
|
||||||
|
</div>
|
||||||
|
</el-option>
|
||||||
|
</el-option-group>
|
||||||
|
|
||||||
|
<el-option-group
|
||||||
|
v-if="inviteDialog.friendsInInstance.length"
|
||||||
|
:label="t('dialog.invite.friends_in_instance')">
|
||||||
|
<el-option
|
||||||
|
v-for="friend in inviteDialog.friendsInInstance"
|
||||||
|
:key="friend.id"
|
||||||
|
class="x-friend-item"
|
||||||
|
:label="friend.name"
|
||||||
|
:value="friend.id"
|
||||||
|
style="height: auto">
|
||||||
|
<template v-if="friend.ref">
|
||||||
|
<div :class="['avatar', userStatusClass(friend.ref)]">
|
||||||
|
<img v-lazy="userImage(friend.ref)" />
|
||||||
|
</div>
|
||||||
|
<div class="detail">
|
||||||
|
<span class="name" :style="{ color: friend.ref.$userColour }">{{
|
||||||
|
friend.ref.displayName
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<span v-else>{{ friend.id }}</span>
|
||||||
|
</el-option>
|
||||||
|
</el-option-group>
|
||||||
|
|
||||||
|
<el-option-group v-if="vipFriends.length" :label="t('side_panel.favorite')">
|
||||||
|
<el-option
|
||||||
|
v-for="friend in vipFriends"
|
||||||
|
:key="friend.id"
|
||||||
|
class="x-friend-item"
|
||||||
|
:label="friend.name"
|
||||||
|
:value="friend.id"
|
||||||
|
style="height: auto">
|
||||||
|
<template v-if="friend.ref">
|
||||||
|
<div :class="['avatar', userStatusClass(friend.ref)]">
|
||||||
|
<img v-lazy="userImage(friend.ref)" />
|
||||||
|
</div>
|
||||||
|
<div class="detail">
|
||||||
|
<span class="name" :style="{ color: friend.ref.$userColour }">{{
|
||||||
|
friend.ref.displayName
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<span v-else>{{ friend.id }}</span>
|
||||||
|
</el-option>
|
||||||
|
</el-option-group>
|
||||||
|
|
||||||
|
<el-option-group v-if="onlineFriends.length" :label="t('side_panel.online')">
|
||||||
|
<el-option
|
||||||
|
v-for="friend in onlineFriends"
|
||||||
|
:key="friend.id"
|
||||||
|
class="x-friend-item"
|
||||||
|
:label="friend.name"
|
||||||
|
:value="friend.id"
|
||||||
|
style="height: auto">
|
||||||
|
<template v-if="friend.ref">
|
||||||
|
<div :class="['avatar', userStatusClass(friend.ref)]">
|
||||||
|
<img v-lazy="userImage(friend.ref)" />
|
||||||
|
</div>
|
||||||
|
<div class="detail">
|
||||||
|
<span class="name" :style="{ color: friend.ref.$userColour }">{{
|
||||||
|
friend.ref.displayName
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<span v-else>{{ friend.id }}</span>
|
||||||
|
</el-option>
|
||||||
|
</el-option-group>
|
||||||
|
|
||||||
|
<el-option-group v-if="activeFriends.length" :label="t('side_panel.active')">
|
||||||
|
<el-option
|
||||||
|
v-for="friend in activeFriends"
|
||||||
|
:key="friend.id"
|
||||||
|
class="x-friend-item"
|
||||||
|
:label="friend.name"
|
||||||
|
:value="friend.id"
|
||||||
|
style="height: auto">
|
||||||
|
<template v-if="friend.ref">
|
||||||
|
<div class="avatar"><img v-lazy="userImage(friend.ref)" /></div>
|
||||||
|
<div class="detail">
|
||||||
|
<span class="name" :style="{ color: friend.ref.$userColour }">{{
|
||||||
|
friend.ref.displayName
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<span v-else>{{ friend.id }}</span>
|
||||||
|
</el-option>
|
||||||
|
</el-option-group>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<el-button
|
||||||
|
size="small"
|
||||||
|
:disabled="inviteDialog.loading || !inviteDialog.userIds.length"
|
||||||
|
@click="showSendInviteDialog"
|
||||||
|
>{{ t('dialog.invite.invite_with_message') }}</el-button
|
||||||
|
>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
:disabled="inviteDialog.loading || !inviteDialog.userIds.length"
|
||||||
|
@click="sendInvite"
|
||||||
|
>{{ t('dialog.invite.invite') }}</el-button
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
<SendInviteDialog
|
||||||
|
:send-invite-dialog-visible.sync="sendInviteDialogVisible"
|
||||||
|
:invite-message-table="inviteMessageTable"
|
||||||
|
:send-invite-dialog="sendInviteDialog"
|
||||||
|
:invite-dialog="inviteDialog"
|
||||||
|
:upload-image="uploadImage"
|
||||||
|
@close-invite-dialog="closeInviteDialog" />
|
||||||
|
</safe-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { getCurrentInstance, inject, ref } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n-bridge';
|
||||||
|
import { instanceRequest, inviteMessagesRequest, notificationRequest } from '../../../api';
|
||||||
|
import { parseLocation } from '../../../composables/instance/utils';
|
||||||
|
import Location from '../../Location.vue';
|
||||||
|
import SendInviteDialog from './SendInviteDialog.vue';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const instance = getCurrentInstance();
|
||||||
|
const $message = instance.proxy.$message;
|
||||||
|
const $confirm = instance.proxy.$confirm;
|
||||||
|
|
||||||
|
const userStatusClass = inject('userStatusClass');
|
||||||
|
const userImage = inject('userImage');
|
||||||
|
const API = inject('API');
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
inviteDialog: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
vipFriends: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
onlineFriends: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
activeFriends: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
// SendInviteDialog
|
||||||
|
inviteMessageTable: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
uploadImage: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['clearInviteImageUpload', 'inviteImageUpload', 'closeInviteDialog']);
|
||||||
|
|
||||||
|
const sendInviteDialogVisible = ref(false);
|
||||||
|
const sendInviteDialog = ref({ message: '', messageSlot: 0, userId: '', messageType: '', params: {} });
|
||||||
|
|
||||||
|
function closeInviteDialog() {
|
||||||
|
emit('closeInviteDialog');
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSendInviteDialog(params, userId) {
|
||||||
|
sendInviteDialog.value = {
|
||||||
|
params,
|
||||||
|
userId,
|
||||||
|
messageType: 'invite'
|
||||||
|
};
|
||||||
|
inviteMessagesRequest.refreshInviteMessageTableData('message');
|
||||||
|
clearInviteImageUpload();
|
||||||
|
sendInviteDialogVisible.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearInviteImageUpload() {
|
||||||
|
emit('clearInviteImageUpload');
|
||||||
|
}
|
||||||
|
|
||||||
|
function addSelfToInvite() {
|
||||||
|
const D = props.inviteDialog;
|
||||||
|
if (!D.userIds.includes(API.currentUser.id)) {
|
||||||
|
D.userIds.push(API.currentUser.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addFriendsInInstanceToInvite() {
|
||||||
|
const D = props.inviteDialog;
|
||||||
|
for (const friend of D.friendsInInstance) {
|
||||||
|
if (!D.userIds.includes(friend.id)) {
|
||||||
|
D.userIds.push(friend.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addFavoriteFriendsToInvite() {
|
||||||
|
const D = props.inviteDialog;
|
||||||
|
for (const friend of props.vipFriends) {
|
||||||
|
if (!D.userIds.includes(friend.id)) {
|
||||||
|
D.userIds.push(friend.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendInvite() {
|
||||||
|
$confirm('Continue? Invite', 'Confirm', {
|
||||||
|
confirmButtonText: 'Confirm',
|
||||||
|
cancelButtonText: 'Cancel',
|
||||||
|
type: 'info',
|
||||||
|
callback: (action) => {
|
||||||
|
const D = props.inviteDialog;
|
||||||
|
if (action !== 'confirm' || D.loading === true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
D.loading = true;
|
||||||
|
const inviteLoop = () => {
|
||||||
|
if (D.userIds.length > 0) {
|
||||||
|
const receiverUserId = D.userIds.shift();
|
||||||
|
if (receiverUserId === API.currentUser.id) {
|
||||||
|
// can't invite self!?
|
||||||
|
const L = parseLocation(D.worldId);
|
||||||
|
instanceRequest
|
||||||
|
.selfInvite({
|
||||||
|
instanceId: L.instanceId,
|
||||||
|
worldId: L.worldId
|
||||||
|
})
|
||||||
|
.finally(inviteLoop);
|
||||||
|
} else {
|
||||||
|
notificationRequest
|
||||||
|
.sendInvite(
|
||||||
|
{
|
||||||
|
instanceId: D.worldId,
|
||||||
|
worldId: D.worldId,
|
||||||
|
worldName: D.worldName
|
||||||
|
},
|
||||||
|
receiverUserId
|
||||||
|
)
|
||||||
|
.finally(inviteLoop);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
D.loading = false;
|
||||||
|
D.visible = false;
|
||||||
|
$message({
|
||||||
|
message: 'Invite sent',
|
||||||
|
type: 'success'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
inviteLoop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
176
src/components/dialogs/InviteDialog/SendInviteConfirmDialog.vue
Normal file
176
src/components/dialogs/InviteDialog/SendInviteConfirmDialog.vue
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
<template>
|
||||||
|
<safe-dialog
|
||||||
|
class="x-dialog"
|
||||||
|
:visible="visible"
|
||||||
|
:title="t('dialog.invite_message.header')"
|
||||||
|
width="400px"
|
||||||
|
append-to-body
|
||||||
|
@close="cancelInviteConfirm">
|
||||||
|
<div style="font-size: 12px">
|
||||||
|
<span>{{ t('dialog.invite_message.confirmation') }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<el-button type="small" @click="cancelInviteConfirm">
|
||||||
|
{{ t('dialog.invite_message.cancel') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button type="primary" size="small" @click="sendInviteConfirm">
|
||||||
|
{{ t('dialog.invite_message.confirm') }}
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</safe-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { getCurrentInstance, inject } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n-bridge';
|
||||||
|
import { instanceRequest, notificationRequest } from '../../../api';
|
||||||
|
import { parseLocation } from '../../../composables/instance/utils';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const instance = getCurrentInstance();
|
||||||
|
const $message = instance.proxy.$message;
|
||||||
|
|
||||||
|
const API = inject('API');
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
visible: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
sendInviteDialog: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
inviteDialog: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
uploadImage: {
|
||||||
|
type: String
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:visible', 'closeInviteDialog']);
|
||||||
|
|
||||||
|
function cancelInviteConfirm() {
|
||||||
|
emit('update:visible', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendInviteConfirm() {
|
||||||
|
const D = props.sendInviteDialog;
|
||||||
|
const J = props.inviteDialog;
|
||||||
|
if (J?.visible) {
|
||||||
|
const inviteLoop = () => {
|
||||||
|
if (J.userIds.length > 0) {
|
||||||
|
const receiverUserId = J.userIds.shift();
|
||||||
|
if (receiverUserId === API.currentUser.id) {
|
||||||
|
// can't invite self!?
|
||||||
|
const L = parseLocation(J.worldId);
|
||||||
|
instanceRequest
|
||||||
|
.selfInvite({
|
||||||
|
instanceId: L.instanceId,
|
||||||
|
worldId: L.worldId
|
||||||
|
})
|
||||||
|
.finally(inviteLoop);
|
||||||
|
} else if (props.uploadImage) {
|
||||||
|
notificationRequest
|
||||||
|
.sendInvitePhoto(
|
||||||
|
{
|
||||||
|
instanceId: J.worldId,
|
||||||
|
worldId: J.worldId,
|
||||||
|
worldName: J.worldName,
|
||||||
|
messageSlot: D.messageSlot
|
||||||
|
},
|
||||||
|
receiverUserId
|
||||||
|
)
|
||||||
|
.finally(inviteLoop);
|
||||||
|
} else {
|
||||||
|
notificationRequest
|
||||||
|
.sendInvite(
|
||||||
|
{
|
||||||
|
instanceId: J.worldId,
|
||||||
|
worldId: J.worldId,
|
||||||
|
worldName: J.worldName,
|
||||||
|
messageSlot: D.messageSlot
|
||||||
|
},
|
||||||
|
receiverUserId
|
||||||
|
)
|
||||||
|
.finally(inviteLoop);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
J.loading = false;
|
||||||
|
J.visible = false;
|
||||||
|
$message({
|
||||||
|
message: 'Invite message sent',
|
||||||
|
type: 'success'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
inviteLoop();
|
||||||
|
} else if (D.messageType === 'invite') {
|
||||||
|
D.params.messageSlot = D.messageSlot;
|
||||||
|
if (props.uploadImage) {
|
||||||
|
notificationRequest
|
||||||
|
.sendInvitePhoto(D.params, D.userId)
|
||||||
|
.catch((err) => {
|
||||||
|
throw err;
|
||||||
|
})
|
||||||
|
.then((args) => {
|
||||||
|
$message({
|
||||||
|
message: 'Invite photo message sent',
|
||||||
|
type: 'success'
|
||||||
|
});
|
||||||
|
return args;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
notificationRequest
|
||||||
|
.sendInvite(D.params, D.userId)
|
||||||
|
.catch((err) => {
|
||||||
|
throw err;
|
||||||
|
})
|
||||||
|
.then((args) => {
|
||||||
|
$message({
|
||||||
|
message: 'Invite message sent',
|
||||||
|
type: 'success'
|
||||||
|
});
|
||||||
|
return args;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (D.messageType === 'requestInvite') {
|
||||||
|
D.params.requestSlot = D.messageSlot;
|
||||||
|
if (props.uploadImage) {
|
||||||
|
notificationRequest
|
||||||
|
.sendRequestInvitePhoto(D.params, D.userId)
|
||||||
|
.catch((err) => {
|
||||||
|
this.clearInviteImageUpload();
|
||||||
|
throw err;
|
||||||
|
})
|
||||||
|
.then((args) => {
|
||||||
|
$message({
|
||||||
|
message: 'Request invite photo message sent',
|
||||||
|
type: 'success'
|
||||||
|
});
|
||||||
|
return args;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
notificationRequest
|
||||||
|
.sendRequestInvite(D.params, D.userId)
|
||||||
|
.catch((err) => {
|
||||||
|
throw err;
|
||||||
|
})
|
||||||
|
.then((args) => {
|
||||||
|
$message({
|
||||||
|
message: 'Request invite message sent',
|
||||||
|
type: 'success'
|
||||||
|
});
|
||||||
|
return args;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cancelInviteConfirm();
|
||||||
|
emit('closeInviteDialog');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
165
src/components/dialogs/InviteDialog/SendInviteDialog.vue
Normal file
165
src/components/dialogs/InviteDialog/SendInviteDialog.vue
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
<template>
|
||||||
|
<safe-dialog
|
||||||
|
class="x-dialog"
|
||||||
|
:visible.sync="sendInviteDialogVisible"
|
||||||
|
:title="t('dialog.invite_message.header')"
|
||||||
|
width="800px"
|
||||||
|
append-to-body
|
||||||
|
@close="cancelSendInvite">
|
||||||
|
<template v-if="API.currentUser.$isVRCPlus">
|
||||||
|
<!-- <template v-if="gallerySelectDialog.selectedFileId">-->
|
||||||
|
<!-- <div style="display: inline-block; flex: none; margin-right: 5px">-->
|
||||||
|
<!-- <el-popover placement="right" width="500px" trigger="click">-->
|
||||||
|
<!-- <template #reference>-->
|
||||||
|
<!-- <img-->
|
||||||
|
<!-- class="x-link"-->
|
||||||
|
<!-- v-lazy="gallerySelectDialog.selectedImageUrl"-->
|
||||||
|
<!-- style="flex: none; width: 60px; height: 60px; border-radius: 4px; object-fit: cover" />-->
|
||||||
|
<!-- </template>-->
|
||||||
|
<!-- <img-->
|
||||||
|
<!-- class="x-link"-->
|
||||||
|
<!-- v-lazy="gallerySelectDialog.selectedImageUrl"-->
|
||||||
|
<!-- style="height: 500px"-->
|
||||||
|
<!-- @click="showFullscreenImageDialog(gallerySelectDialog.selectedImageUrl)" />-->
|
||||||
|
<!-- </el-popover>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- <el-button size="mini" @click="clearImageGallerySelect" style="vertical-align: top">-->
|
||||||
|
<!-- {{ t('dialog.invite_message.clear_selected_image') }}-->
|
||||||
|
<!-- </el-button>-->
|
||||||
|
<!-- </template>-->
|
||||||
|
<!-- <template v-else>-->
|
||||||
|
<!-- <el-button size="mini" @click="showGallerySelectDialog" style="margin-right: 5px">-->
|
||||||
|
<!-- {{ t('dialog.invite_message.select_image') }}-->
|
||||||
|
<!-- </el-button>-->
|
||||||
|
<!-- </template>-->
|
||||||
|
<input class="inviteImageUploadButton" type="file" accept="image/*" @change="inviteImageUpload" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<data-tables
|
||||||
|
v-bind="inviteMessageTable"
|
||||||
|
style="margin-top: 10px; cursor: pointer"
|
||||||
|
@row-click="showSendInviteConfirmDialog">
|
||||||
|
<el-table-column
|
||||||
|
:label="t('table.profile.invite_messages.slot')"
|
||||||
|
prop="slot"
|
||||||
|
sortable="custom"
|
||||||
|
width="70"></el-table-column>
|
||||||
|
<el-table-column :label="t('table.profile.invite_messages.message')" prop="message"></el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
:label="t('table.profile.invite_messages.cool_down')"
|
||||||
|
prop="updatedAt"
|
||||||
|
sortable="custom"
|
||||||
|
width="110"
|
||||||
|
align="right">
|
||||||
|
<template #default="scope">
|
||||||
|
<countdown-timer :datetime="scope.row.updatedAt" :hours="1"></countdown-timer>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column :label="t('table.profile.invite_messages.action')" width="70" align="right">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button
|
||||||
|
type="text"
|
||||||
|
icon="el-icon-edit"
|
||||||
|
size="mini"
|
||||||
|
@click.stop="showEditAndSendInviteDialog('message', scope.row)"></el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</data-tables>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<el-button type="small" @click="cancelSendInvite">
|
||||||
|
{{ t('dialog.invite_message.cancel') }}
|
||||||
|
</el-button>
|
||||||
|
<el-button type="small" @click="API.refreshInviteMessageTableData('message')">
|
||||||
|
{{ t('dialog.invite_message.refresh') }}
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
<SendInviteConfirmDialog
|
||||||
|
:visible.sync="isSendInviteConfirmDialogVisible"
|
||||||
|
:send-invite-dialog="sendInviteDialog"
|
||||||
|
:invite-dialog="inviteDialog"
|
||||||
|
:upload-image="uploadImage"
|
||||||
|
@closeInviteDialog="closeInviteDialog" />
|
||||||
|
<EditAndSendInviteDialog
|
||||||
|
:edit-and-send-invite-dialog.sync="editAndSendInviteDialog"
|
||||||
|
:send-invite-dialog="sendInviteDialog"
|
||||||
|
:invite-dialog="inviteDialog"
|
||||||
|
:upload-image="uploadImage"
|
||||||
|
@closeInviteDialog="closeInviteDialog" />
|
||||||
|
</safe-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { inject, ref } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n-bridge';
|
||||||
|
import EditAndSendInviteDialog from './EditAndSendInviteDialog.vue';
|
||||||
|
import SendInviteConfirmDialog from './SendInviteConfirmDialog.vue';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const API = inject('API');
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
sendInviteDialogVisible: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
inviteMessageTable: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
sendInviteDialog: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
inviteDialog: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
uploadImage: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['inviteImageUpload', 'update:sendInviteDialogVisible', 'closeInviteDialog']);
|
||||||
|
|
||||||
|
const isSendInviteConfirmDialogVisible = ref(false);
|
||||||
|
|
||||||
|
const editAndSendInviteDialog = ref({
|
||||||
|
visible: false,
|
||||||
|
messageType: '',
|
||||||
|
newMessage: '',
|
||||||
|
inviteMessage: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
function inviteImageUpload(event) {
|
||||||
|
emit('inviteImageUpload', event);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSendInviteConfirmDialog(val) {
|
||||||
|
isSendInviteConfirmDialogVisible.value = true;
|
||||||
|
//
|
||||||
|
props.sendInviteDialog.messageSlot = val.slot;
|
||||||
|
}
|
||||||
|
|
||||||
|
function showEditAndSendInviteDialog(messageType, inviteMessage) {
|
||||||
|
// todo
|
||||||
|
editAndSendInviteDialog.value = {
|
||||||
|
newMessage: inviteMessage.message,
|
||||||
|
visible: true,
|
||||||
|
messageType,
|
||||||
|
inviteMessage
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelSendInvite() {
|
||||||
|
emit('update:sendInviteDialogVisible', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeInviteDialog() {
|
||||||
|
cancelSendInvite();
|
||||||
|
emit('closeInviteDialog');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -1,12 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<safe-dialog
|
||||||
ref="inviteGroupDialog"
|
ref="inviteGroupDialog"
|
||||||
:visible.sync="inviteGroupDialog.visible"
|
:visible.sync="inviteGroupDialog.visible"
|
||||||
:before-close="beforeDialogClose"
|
|
||||||
:title="$t('dialog.invite_to_group.header')"
|
:title="$t('dialog.invite_to_group.header')"
|
||||||
width="450px"
|
width="450px"
|
||||||
@mousedown.native="dialogMouseDown"
|
append-to-body>
|
||||||
@mouseup.native="dialogMouseUp">
|
|
||||||
<div v-if="inviteGroupDialog.visible" v-loading="inviteGroupDialog.loading">
|
<div v-if="inviteGroupDialog.visible" v-loading="inviteGroupDialog.loading">
|
||||||
<span>{{ $t('dialog.invite_to_group.description') }}</span>
|
<span>{{ $t('dialog.invite_to_group.description') }}</span>
|
||||||
<br />
|
<br />
|
||||||
@@ -165,24 +163,16 @@
|
|||||||
Invite
|
Invite
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</safe-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { groupRequest, userRequest } from '../../../api';
|
import { groupRequest, userRequest } from '../../api';
|
||||||
import utils from '../../../classes/utils';
|
import { hasGroupPermission } from '../../composables/group/utils';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'InviteGroupDialog',
|
name: 'InviteGroupDialog',
|
||||||
inject: [
|
inject: ['API', 'userStatusClass', 'userImage', 'adjustDialogZ'],
|
||||||
'API',
|
|
||||||
'dialogMouseDown',
|
|
||||||
'dialogMouseUp',
|
|
||||||
'beforeDialogClose',
|
|
||||||
'userStatusClass',
|
|
||||||
'userImage',
|
|
||||||
'adjustDialogZ'
|
|
||||||
],
|
|
||||||
props: {
|
props: {
|
||||||
dialogData: {
|
dialogData: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -256,7 +246,7 @@
|
|||||||
groupRequest
|
groupRequest
|
||||||
.getGroup({ groupId })
|
.getGroup({ groupId })
|
||||||
.then((args) => {
|
.then((args) => {
|
||||||
if (utils.hasGroupPermission(args.ref, 'group-invites-manage')) {
|
if (hasGroupPermission(args.ref, 'group-invites-manage')) {
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
// not allowed to invite
|
// not allowed to invite
|
||||||
@@ -1,12 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<safe-dialog ref="launchDialog" :visible.sync="isVisible" :title="$t('dialog.launch.header')" width="450px">
|
||||||
ref="launchDialog"
|
|
||||||
:before-close="beforeDialogClose"
|
|
||||||
:visible.sync="isVisible"
|
|
||||||
:title="$t('dialog.launch.header')"
|
|
||||||
width="450px"
|
|
||||||
@mousedown.native="dialogMouseDown"
|
|
||||||
@mouseup.native="dialogMouseUp">
|
|
||||||
<el-form :model="launchDialog" label-width="100px">
|
<el-form :model="launchDialog" label-width="100px">
|
||||||
<el-form-item :label="$t('dialog.launch.url')">
|
<el-form-item :label="$t('dialog.launch.url')">
|
||||||
<el-input
|
<el-input
|
||||||
@@ -84,30 +77,58 @@
|
|||||||
{{ $t('dialog.launch.launch') }}
|
{{ $t('dialog.launch.launch') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
<InviteDialog
|
||||||
|
:invite-dialog="inviteDialog"
|
||||||
|
:vip-friends="vipFriends"
|
||||||
|
:online-friends="onlineFriends"
|
||||||
|
:active-friends="activeFriends"
|
||||||
|
:invite-message-table="inviteMessageTable"
|
||||||
|
:upload-image="uploadImage"
|
||||||
|
@close-invite-dialog="closeInviteDialog" />
|
||||||
|
</safe-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import utils from '../classes/utils';
|
import { instanceRequest, worldRequest } from '../../api';
|
||||||
import configRepository from '../service/config';
|
import { isRealInstance, parseLocation } from '../../composables/instance/utils';
|
||||||
import { instanceRequest } from '../api';
|
import { getLaunchURL } from '../../composables/shared/utils';
|
||||||
|
import configRepository from '../../service/config';
|
||||||
|
import InviteDialog from './InviteDialog/InviteDialog.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'LaunchDialog',
|
name: 'LaunchDialog',
|
||||||
inject: [
|
components: { InviteDialog },
|
||||||
'beforeDialogClose',
|
inject: ['showPreviousInstancesInfoDialog', 'adjustDialogZ'],
|
||||||
'dialogMouseDown',
|
|
||||||
'dialogMouseUp',
|
|
||||||
'showPreviousInstancesInfoDialog',
|
|
||||||
'showInviteDialog',
|
|
||||||
'adjustDialogZ'
|
|
||||||
],
|
|
||||||
props: {
|
props: {
|
||||||
hideTooltips: Boolean,
|
hideTooltips: Boolean,
|
||||||
launchDialogData: { type: Object, required: true },
|
launchDialogData: { type: Object, required: true },
|
||||||
checkCanInvite: {
|
checkCanInvite: {
|
||||||
type: Function,
|
type: Function,
|
||||||
required: true
|
required: true
|
||||||
|
},
|
||||||
|
vipFriends: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
onlineFriends: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
activeFriends: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
inviteMessageTable: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
uploadImage: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
lastLocation: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@@ -121,6 +142,14 @@
|
|||||||
shortName: '',
|
shortName: '',
|
||||||
shortUrl: '',
|
shortUrl: '',
|
||||||
secureOrShortName: ''
|
secureOrShortName: ''
|
||||||
|
},
|
||||||
|
inviteDialog: {
|
||||||
|
visible: false,
|
||||||
|
loading: false,
|
||||||
|
worldId: '',
|
||||||
|
worldName: '',
|
||||||
|
userIds: [],
|
||||||
|
friendsInInstance: []
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -147,8 +176,37 @@
|
|||||||
this.getConfig();
|
this.getConfig();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
closeInviteDialog() {
|
||||||
|
this.inviteDialog.visible = false;
|
||||||
|
},
|
||||||
|
showInviteDialog(tag) {
|
||||||
|
if (!isRealInstance(tag)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const L = parseLocation(tag);
|
||||||
|
worldRequest
|
||||||
|
.getCachedWorld({
|
||||||
|
worldId: L.worldId
|
||||||
|
})
|
||||||
|
.then((args) => {
|
||||||
|
const D = this.inviteDialog;
|
||||||
|
D.userIds = [];
|
||||||
|
D.worldId = L.tag;
|
||||||
|
D.worldName = args.ref.name;
|
||||||
|
D.friendsInInstance = [];
|
||||||
|
const friendsInCurrentInstance = this.lastLocation.friendList;
|
||||||
|
for (const friend of friendsInCurrentInstance.values()) {
|
||||||
|
const ctx = this.friends.get(friend.userId);
|
||||||
|
if (typeof ctx.ref === 'undefined') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
D.friendsInInstance.push(ctx);
|
||||||
|
}
|
||||||
|
D.visible = true;
|
||||||
|
});
|
||||||
|
},
|
||||||
launchGame(location, shortName, desktop) {
|
launchGame(location, shortName, desktop) {
|
||||||
this.$emit('launch-game', location, shortName, desktop);
|
this.$emit('launchGame', location, shortName, desktop);
|
||||||
this.isVisible = false;
|
this.isVisible = false;
|
||||||
},
|
},
|
||||||
getConfig() {
|
getConfig() {
|
||||||
@@ -159,7 +217,7 @@
|
|||||||
},
|
},
|
||||||
async initLaunchDialog() {
|
async initLaunchDialog() {
|
||||||
const { tag, shortName } = this.launchDialogData;
|
const { tag, shortName } = this.launchDialogData;
|
||||||
if (!utils.isRealInstance(tag)) {
|
if (!isRealInstance(tag)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.$nextTick(() => this.adjustDialogZ(this.$refs.launchDialog.$el));
|
this.$nextTick(() => this.adjustDialogZ(this.$refs.launchDialog.$el));
|
||||||
@@ -168,7 +226,7 @@
|
|||||||
D.secureOrShortName = shortName;
|
D.secureOrShortName = shortName;
|
||||||
D.shortUrl = '';
|
D.shortUrl = '';
|
||||||
D.shortName = shortName;
|
D.shortName = shortName;
|
||||||
const L = utils.parseLocation(tag);
|
const L = parseLocation(tag);
|
||||||
L.shortName = shortName;
|
L.shortName = shortName;
|
||||||
if (shortName) {
|
if (shortName) {
|
||||||
D.shortUrl = `https://vrch.at/${shortName}`;
|
D.shortUrl = `https://vrch.at/${shortName}`;
|
||||||
@@ -178,7 +236,7 @@
|
|||||||
} else {
|
} else {
|
||||||
D.location = L.worldId;
|
D.location = L.worldId;
|
||||||
}
|
}
|
||||||
D.url = utils.getLaunchURL(L);
|
D.url = getLaunchURL(L);
|
||||||
if (!shortName) {
|
if (!shortName) {
|
||||||
const res = await instanceRequest.getInstanceShortName({
|
const res = await instanceRequest.getInstanceShortName({
|
||||||
worldId: L.worldId,
|
worldId: L.worldId,
|
||||||
@@ -193,14 +251,14 @@
|
|||||||
if (resLocation === this.launchDialog.tag) {
|
if (resLocation === this.launchDialog.tag) {
|
||||||
const resShortName = res.json.shortName;
|
const resShortName = res.json.shortName;
|
||||||
const secureOrShortName = res.json.shortName || res.json.secureName;
|
const secureOrShortName = res.json.shortName || res.json.secureName;
|
||||||
const parsedL = utils.parseLocation(resLocation);
|
const parsedL = parseLocation(resLocation);
|
||||||
parsedL.shortName = resShortName;
|
parsedL.shortName = resShortName;
|
||||||
this.launchDialog.shortName = resShortName;
|
this.launchDialog.shortName = resShortName;
|
||||||
this.launchDialog.secureOrShortName = secureOrShortName;
|
this.launchDialog.secureOrShortName = secureOrShortName;
|
||||||
if (resShortName) {
|
if (resShortName) {
|
||||||
this.launchDialog.shortUrl = `https://vrch.at/${resShortName}`;
|
this.launchDialog.shortUrl = `https://vrch.at/${resShortName}`;
|
||||||
}
|
}
|
||||||
this.launchDialog.url = utils.getLaunchURL(parsedL);
|
this.launchDialog.url = getLaunchURL(parsedL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1,13 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<safe-dialog
|
||||||
ref="newInstanceDialog"
|
ref="newInstanceDialog"
|
||||||
:before-close="beforeDialogClose"
|
|
||||||
:visible.sync="newInstanceDialog.visible"
|
:visible.sync="newInstanceDialog.visible"
|
||||||
:title="$t('dialog.new_instance.header')"
|
:title="$t('dialog.new_instance.header')"
|
||||||
width="650px"
|
width="650px"
|
||||||
append-to-body
|
append-to-body>
|
||||||
@mousedown.native="dialogMouseDown"
|
|
||||||
@mouseup.native="dialogMouseUp">
|
|
||||||
<el-tabs v-model="newInstanceDialog.selectedTab" type="card" @tab-click="newInstanceTabClick">
|
<el-tabs v-model="newInstanceDialog.selectedTab" type="card" @tab-click="newInstanceTabClick">
|
||||||
<el-tab-pane :label="$t('dialog.new_instance.normal')">
|
<el-tab-pane :label="$t('dialog.new_instance.normal')">
|
||||||
<el-form :model="newInstanceDialog" label-width="150px">
|
<el-form :model="newInstanceDialog" label-width="150px">
|
||||||
@@ -486,27 +483,30 @@
|
|||||||
>{{ $t('dialog.new_instance.launch') }}</el-button
|
>{{ $t('dialog.new_instance.launch') }}</el-button
|
||||||
>
|
>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
<InviteDialog
|
||||||
|
:invite-dialog="inviteDialog"
|
||||||
|
:vip-friends="vipFriends"
|
||||||
|
:online-friends="onlineFriends"
|
||||||
|
:active-friends="activeFriends"
|
||||||
|
:invite-message-table="inviteMessageTable"
|
||||||
|
:upload-image="uploadImage"
|
||||||
|
@close-invite-dialog="closeInviteDialog" />
|
||||||
|
</safe-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { groupRequest, instanceRequest } from '../../api';
|
import { groupRequest, instanceRequest, worldRequest } from '../../api';
|
||||||
import utils from '../../classes/utils';
|
import utils from '../../classes/utils';
|
||||||
|
import { hasGroupPermission as _hasGroupPermission } from '../../composables/group/utils';
|
||||||
|
import { isRealInstance, parseLocation } from '../../composables/instance/utils';
|
||||||
|
import { getLaunchURL } from '../../composables/shared/utils';
|
||||||
import configRepository from '../../service/config';
|
import configRepository from '../../service/config';
|
||||||
|
import InviteDialog from './InviteDialog/InviteDialog.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'NewInstanceDialog',
|
name: 'NewInstanceDialog',
|
||||||
inject: [
|
components: { InviteDialog },
|
||||||
'API',
|
inject: ['API', 'userImage', 'userStatusClass', 'showLaunchDialog', 'adjustDialogZ'],
|
||||||
'userImage',
|
|
||||||
'userStatusClass',
|
|
||||||
'beforeDialogClose',
|
|
||||||
'dialogMouseDown',
|
|
||||||
'dialogMouseUp',
|
|
||||||
'showInviteDialog',
|
|
||||||
'showLaunchDialog',
|
|
||||||
'adjustDialogZ'
|
|
||||||
],
|
|
||||||
props: {
|
props: {
|
||||||
vipFriends: {
|
vipFriends: {
|
||||||
type: Array,
|
type: Array,
|
||||||
@@ -535,6 +535,18 @@
|
|||||||
newInstanceDialogLocationTag: {
|
newInstanceDialogLocationTag: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
|
},
|
||||||
|
inviteMessageTable: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
uploadImage: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
lastLocation: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@@ -566,6 +578,14 @@
|
|||||||
groupRef: {},
|
groupRef: {},
|
||||||
contentSettings: this.instanceContentSettings,
|
contentSettings: this.instanceContentSettings,
|
||||||
selectedContentSettings: []
|
selectedContentSettings: []
|
||||||
|
},
|
||||||
|
inviteDialog: {
|
||||||
|
visible: false,
|
||||||
|
loading: false,
|
||||||
|
worldId: '',
|
||||||
|
worldName: '',
|
||||||
|
userIds: [],
|
||||||
|
friendsInInstance: []
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -578,13 +598,42 @@
|
|||||||
this.initializeNewInstanceDialog();
|
this.initializeNewInstanceDialog();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
closeInviteDialog() {
|
||||||
|
this.inviteDialog.visible = false;
|
||||||
|
},
|
||||||
|
showInviteDialog(tag) {
|
||||||
|
if (!isRealInstance(tag)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const L = parseLocation(tag);
|
||||||
|
worldRequest
|
||||||
|
.getCachedWorld({
|
||||||
|
worldId: L.worldId
|
||||||
|
})
|
||||||
|
.then((args) => {
|
||||||
|
const D = this.inviteDialog;
|
||||||
|
D.userIds = [];
|
||||||
|
D.worldId = L.tag;
|
||||||
|
D.worldName = args.ref.name;
|
||||||
|
D.friendsInInstance = [];
|
||||||
|
const friendsInCurrentInstance = this.lastLocation.friendList;
|
||||||
|
for (const friend of friendsInCurrentInstance.values()) {
|
||||||
|
const ctx = this.friends.get(friend.userId);
|
||||||
|
if (typeof ctx.ref === 'undefined') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
D.friendsInInstance.push(ctx);
|
||||||
|
}
|
||||||
|
D.visible = true;
|
||||||
|
});
|
||||||
|
},
|
||||||
initNewInstanceDialog(tag) {
|
initNewInstanceDialog(tag) {
|
||||||
if (!utils.isRealInstance(tag)) {
|
if (!isRealInstance(tag)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.$nextTick(() => this.adjustDialogZ(this.$refs.newInstanceDialog.$el));
|
this.$nextTick(() => this.adjustDialogZ(this.$refs.newInstanceDialog.$el));
|
||||||
const D = this.newInstanceDialog;
|
const D = this.newInstanceDialog;
|
||||||
const L = utils.parseLocation(tag);
|
const L = parseLocation(tag);
|
||||||
if (D.worldId === L.worldId) {
|
if (D.worldId === L.worldId) {
|
||||||
// reopening dialog, keep last open instance
|
// reopening dialog, keep last open instance
|
||||||
D.visible = true;
|
D.visible = true;
|
||||||
@@ -682,16 +731,16 @@
|
|||||||
} else {
|
} else {
|
||||||
D.location = D.worldId;
|
D.location = D.worldId;
|
||||||
}
|
}
|
||||||
const L = utils.parseLocation(D.location);
|
const L = parseLocation(D.location);
|
||||||
if (noChanges) {
|
if (noChanges) {
|
||||||
L.shortName = D.shortName;
|
L.shortName = D.shortName;
|
||||||
} else {
|
} else {
|
||||||
D.shortName = '';
|
D.shortName = '';
|
||||||
}
|
}
|
||||||
D.url = utils.getLaunchURL(L);
|
D.url = getLaunchURL(L);
|
||||||
},
|
},
|
||||||
selfInvite(location) {
|
selfInvite(location) {
|
||||||
const L = utils.parseLocation(location);
|
const L = parseLocation(location);
|
||||||
if (!L.isRealInstance) {
|
if (!L.isRealInstance) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -831,7 +880,7 @@
|
|||||||
this.saveNewInstanceDialog();
|
this.saveNewInstanceDialog();
|
||||||
},
|
},
|
||||||
async copyInstanceUrl(location) {
|
async copyInstanceUrl(location) {
|
||||||
const L = utils.parseLocation(location);
|
const L = parseLocation(location);
|
||||||
const args = await instanceRequest.getInstanceShortName({
|
const args = await instanceRequest.getInstanceShortName({
|
||||||
worldId: L.worldId,
|
worldId: L.worldId,
|
||||||
instanceId: L.instanceId
|
instanceId: L.instanceId
|
||||||
@@ -870,7 +919,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
hasGroupPermission(ref, permission) {
|
hasGroupPermission(ref, permission) {
|
||||||
return utils.hasGroupPermission(ref, permission);
|
return _hasGroupPermission(ref, permission);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
51
src/components/dialogs/PreviousImagesDialog.vue
Normal file
51
src/components/dialogs/PreviousImagesDialog.vue
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<template>
|
||||||
|
<safe-dialog
|
||||||
|
class="x-dialog"
|
||||||
|
:visible="previousImagesDialogVisible"
|
||||||
|
:title="t('dialog.previous_images.header')"
|
||||||
|
width="800px"
|
||||||
|
append-to-body
|
||||||
|
@close="closeDialog">
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
v-for="image in previousImagesTable"
|
||||||
|
v-if="image.file"
|
||||||
|
:key="image.version"
|
||||||
|
style="display: inline-block">
|
||||||
|
<el-popover class="x-change-image-item" placement="right" width="500px" trigger="click">
|
||||||
|
<img slot="reference" v-lazy="image.file.url" class="x-link" />
|
||||||
|
<img
|
||||||
|
v-lazy="image.file.url"
|
||||||
|
class="x-link"
|
||||||
|
style="width: 500px; height: 375px"
|
||||||
|
@click="showFullscreenImageDialog(image.file.url)" />
|
||||||
|
</el-popover>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</safe-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { inject } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n-bridge';
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const showFullscreenImageDialog = inject('showFullscreenImageDialog');
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
previousImagesDialogVisible: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
previousImagesTable: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:previousImagesDialogVisible']);
|
||||||
|
|
||||||
|
function closeDialog() {
|
||||||
|
emit('update:previousImagesDialogVisible', false);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -1,14 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<safe-dialog
|
||||||
ref="dialog"
|
ref="dialog"
|
||||||
:before-close="beforeDialogClose"
|
|
||||||
:visible="visible"
|
:visible="visible"
|
||||||
:title="$t('dialog.previous_instances.info')"
|
:title="$t('dialog.previous_instances.info')"
|
||||||
width="800px"
|
width="800px"
|
||||||
:fullscreen="fullscreen"
|
:fullscreen="fullscreen"
|
||||||
destroy-on-close
|
destroy-on-close
|
||||||
@mousedown.native="dialogMouseDown"
|
|
||||||
@mouseup.native="dialogMouseUp"
|
|
||||||
@close="$emit('update:visible', false)">
|
@close="$emit('update:visible', false)">
|
||||||
<div style="display: flex; align-items: center; justify-content: space-between">
|
<div style="display: flex; align-items: center; justify-content: space-between">
|
||||||
<location :location="location.tag" style="font-size: 14px"></location>
|
<location :location="location.tag" style="font-size: 14px"></location>
|
||||||
@@ -57,13 +54,14 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</data-tables>
|
</data-tables>
|
||||||
</el-dialog>
|
</safe-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import utils from '../../../classes/utils';
|
|
||||||
import database from '../../../service/database';
|
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
import utils from '../../../classes/utils';
|
||||||
|
import { parseLocation } from '../../../composables/instance/utils';
|
||||||
|
import database from '../../../service/database';
|
||||||
import Location from '../../Location.vue';
|
import Location from '../../Location.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -71,7 +69,7 @@
|
|||||||
components: {
|
components: {
|
||||||
Location
|
Location
|
||||||
},
|
},
|
||||||
inject: ['adjustDialogZ', 'beforeDialogClose', 'dialogMouseDown', 'dialogMouseUp'],
|
inject: ['adjustDialogZ'],
|
||||||
props: {
|
props: {
|
||||||
visible: {
|
visible: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
@@ -144,7 +142,7 @@
|
|||||||
init() {
|
init() {
|
||||||
this.adjustDialogZ(this.$refs.dialog.$el);
|
this.adjustDialogZ(this.$refs.dialog.$el);
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
this.location = utils.parseLocation(this.instanceId);
|
this.location = parseLocation(this.instanceId);
|
||||||
},
|
},
|
||||||
refreshPreviousInstancesInfoTable() {
|
refreshPreviousInstancesInfoTable() {
|
||||||
database.getPlayersFromInstance(this.location.tag).then((data) => {
|
database.getPlayersFromInstance(this.location.tag).then((data) => {
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<safe-dialog
|
||||||
ref="previousInstancesWorldDialog"
|
ref="previousInstancesWorldDialog"
|
||||||
:before-close="beforeDialogClose"
|
|
||||||
:visible.sync="isVisible"
|
:visible.sync="isVisible"
|
||||||
:title="$t('dialog.previous_instances.header')"
|
:title="$t('dialog.previous_instances.header')"
|
||||||
width="1000px"
|
width="1000px"
|
||||||
append-to-body
|
append-to-body>
|
||||||
@mousedown.native="dialogMouseDown"
|
|
||||||
@mouseup.native="dialogMouseUp">
|
|
||||||
<div style="display: flex; align-items: center; justify-content: space-between">
|
<div style="display: flex; align-items: center; justify-content: space-between">
|
||||||
<span style="font-size: 14px" v-text="previousInstancesWorldDialog.worldRef.name"></span>
|
<span style="font-size: 14px" v-text="previousInstancesWorldDialog.worldRef.name"></span>
|
||||||
<el-input
|
<el-input
|
||||||
@@ -66,24 +63,17 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</data-tables>
|
</data-tables>
|
||||||
</el-dialog>
|
</safe-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import utils from '../../../classes/utils';
|
import utils from '../../../classes/utils';
|
||||||
|
import { parseLocation } from '../../../composables/instance/utils';
|
||||||
import database from '../../../service/database';
|
import database from '../../../service/database';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'PreviousInstancesWorldDialog',
|
name: 'PreviousInstancesWorldDialog',
|
||||||
inject: [
|
inject: ['API', 'showLaunchDialog', 'showPreviousInstancesInfoDialog', 'adjustDialogZ'],
|
||||||
'API',
|
|
||||||
'showLaunchDialog',
|
|
||||||
'showPreviousInstancesInfoDialog',
|
|
||||||
'adjustDialogZ',
|
|
||||||
'beforeDialogClose',
|
|
||||||
'dialogMouseDown',
|
|
||||||
'dialogMouseUp'
|
|
||||||
],
|
|
||||||
props: {
|
props: {
|
||||||
previousInstancesWorldDialog: {
|
previousInstancesWorldDialog: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -149,7 +139,7 @@
|
|||||||
database.getpreviousInstancesByWorldId(D.worldRef).then((data) => {
|
database.getpreviousInstancesByWorldId(D.worldRef).then((data) => {
|
||||||
const array = [];
|
const array = [];
|
||||||
for (const ref of data.values()) {
|
for (const ref of data.values()) {
|
||||||
ref.$location = utils.parseLocation(ref.location);
|
ref.$location = parseLocation(ref.location);
|
||||||
if (ref.time > 0) {
|
if (ref.time > 0) {
|
||||||
ref.timer = utils.timeToText(ref.time);
|
ref.timer = utils.timeToText(ref.time);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
96
src/components/dialogs/SafeDialog.vue
Normal file
96
src/components/dialogs/SafeDialog.vue
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
ref="elDialogRef"
|
||||||
|
:visible="props.visible"
|
||||||
|
v-bind="attrs"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
@open="handleOpen"
|
||||||
|
@close="handleClose">
|
||||||
|
<slot></slot>
|
||||||
|
|
||||||
|
<template v-if="slots.title" #title>
|
||||||
|
<slot name="title"></slot>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-if="slots.footer" #footer>
|
||||||
|
<slot name="footer"></slot>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onBeforeUnmount, nextTick, useAttrs, useSlots } from 'vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
visible: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:visible', 'open', 'close']);
|
||||||
|
|
||||||
|
const attrs = useAttrs();
|
||||||
|
const slots = useSlots();
|
||||||
|
|
||||||
|
const elDialogRef = ref(null);
|
||||||
|
const wrapperElement = ref(null);
|
||||||
|
const mouseDownOnWrapper = ref(false);
|
||||||
|
|
||||||
|
const handleOpen = () => {
|
||||||
|
emit('open');
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
addWrapperListeners();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
emit('close');
|
||||||
|
removeWrapperListeners();
|
||||||
|
emit('update:visible', false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleWrapperMouseDown = (event) => {
|
||||||
|
if (event.target === wrapperElement.value) {
|
||||||
|
mouseDownOnWrapper.value = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleWrapperMouseUp = (event) => {
|
||||||
|
if (event.target === wrapperElement.value && mouseDownOnWrapper.value) {
|
||||||
|
handleClose();
|
||||||
|
}
|
||||||
|
mouseDownOnWrapper.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const addWrapperListeners = () => {
|
||||||
|
const wrapper = elDialogRef.value?.$el;
|
||||||
|
|
||||||
|
if (
|
||||||
|
wrapper &&
|
||||||
|
wrapper.nodeType === Node.ELEMENT_NODE &&
|
||||||
|
wrapper.classList &&
|
||||||
|
wrapper.classList.contains('el-dialog__wrapper')
|
||||||
|
) {
|
||||||
|
wrapperElement.value = wrapper;
|
||||||
|
wrapperElement.value.addEventListener('mousedown', handleWrapperMouseDown);
|
||||||
|
wrapperElement.value.addEventListener('mouseup', handleWrapperMouseUp);
|
||||||
|
} else {
|
||||||
|
wrapperElement.value = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeWrapperListeners = () => {
|
||||||
|
if (wrapperElement.value) {
|
||||||
|
wrapperElement.value.removeEventListener('mousedown', handleWrapperMouseDown);
|
||||||
|
wrapperElement.value.removeEventListener('mouseup', handleWrapperMouseUp);
|
||||||
|
wrapperElement.value = null;
|
||||||
|
}
|
||||||
|
mouseDownOnWrapper.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
removeWrapperListeners();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
277
src/components/dialogs/SendBoopDialog.vue
Normal file
277
src/components/dialogs/SendBoopDialog.vue
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
<!--<template>-->
|
||||||
|
<!-- <safe-dialog-->
|
||||||
|
<!-- class="x-dialog"-->
|
||||||
|
<!-- :visible="sendBoopDialog.visible"-->
|
||||||
|
<!-- :title="t('dialog.boop_dialog.header')"-->
|
||||||
|
<!-- width="450px"-->
|
||||||
|
<!-- @close="closeDialog">-->
|
||||||
|
<!-- <el-select-->
|
||||||
|
<!-- v-model="sendBoopDialog.userId"-->
|
||||||
|
<!-- :placeholder="t('dialog.new_instance.instance_creator_placeholder')"-->
|
||||||
|
<!-- filterable-->
|
||||||
|
<!-- style="width: 100%">-->
|
||||||
|
<!-- <el-option-group v-if="vipFriends.length" :label="t('side_panel.favorite')">-->
|
||||||
|
<!-- <el-option-->
|
||||||
|
<!-- v-for="friend in vipFriends"-->
|
||||||
|
<!-- :key="friend.id"-->
|
||||||
|
<!-- class="x-friend-item"-->
|
||||||
|
<!-- :label="friend.name"-->
|
||||||
|
<!-- :value="friend.id"-->
|
||||||
|
<!-- style="height: auto">-->
|
||||||
|
<!-- <template v-if="friend.ref">-->
|
||||||
|
<!-- <div class="avatar" :class="userStatusClass(friend.ref)">-->
|
||||||
|
<!-- <img v-lazy="userImage(friend.ref)" />-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- <div class="detail">-->
|
||||||
|
<!-- <span-->
|
||||||
|
<!-- class="name"-->
|
||||||
|
<!-- :style="{ color: friend.ref.$userColour }"-->
|
||||||
|
<!-- v-text="friend.ref.displayName"></span>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- </template>-->
|
||||||
|
<!-- <span v-else v-text="friend.id"></span>-->
|
||||||
|
<!-- </el-option>-->
|
||||||
|
<!-- </el-option-group>-->
|
||||||
|
<!-- <el-option-group v-if="onlineFriends.length" :label="t('side_panel.online')">-->
|
||||||
|
<!-- <el-option-->
|
||||||
|
<!-- v-for="friend in onlineFriends"-->
|
||||||
|
<!-- :key="friend.id"-->
|
||||||
|
<!-- class="x-friend-item"-->
|
||||||
|
<!-- :label="friend.name"-->
|
||||||
|
<!-- :value="friend.id"-->
|
||||||
|
<!-- style="height: auto">-->
|
||||||
|
<!-- <template v-if="friend.ref">-->
|
||||||
|
<!-- <div class="avatar" :class="userStatusClass(friend.ref)">-->
|
||||||
|
<!-- <img v-lazy="userImage(friend.ref)" />-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- <div class="detail">-->
|
||||||
|
<!-- <span-->
|
||||||
|
<!-- class="name"-->
|
||||||
|
<!-- :style="{ color: friend.ref.$userColour }"-->
|
||||||
|
<!-- v-text="friend.ref.displayName"></span>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- </template>-->
|
||||||
|
<!-- <span v-else v-text="friend.id"></span>-->
|
||||||
|
<!-- </el-option>-->
|
||||||
|
<!-- </el-option-group>-->
|
||||||
|
<!-- <el-option-group v-if="activeFriends.length" :label="t('side_panel.active')">-->
|
||||||
|
<!-- <el-option-->
|
||||||
|
<!-- v-for="friend in activeFriends"-->
|
||||||
|
<!-- :key="friend.id"-->
|
||||||
|
<!-- class="x-friend-item"-->
|
||||||
|
<!-- :label="friend.name"-->
|
||||||
|
<!-- :value="friend.id"-->
|
||||||
|
<!-- style="height: auto">-->
|
||||||
|
<!-- <template v-if="friend.ref">-->
|
||||||
|
<!-- <div class="avatar">-->
|
||||||
|
<!-- <img v-lazy="userImage(friend.ref)" />-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- <div class="detail">-->
|
||||||
|
<!-- <span-->
|
||||||
|
<!-- class="name"-->
|
||||||
|
<!-- :style="{ color: friend.ref.$userColour }"-->
|
||||||
|
<!-- v-text="friend.ref.displayName"></span>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- </template>-->
|
||||||
|
<!-- <span v-else v-text="friend.id"></span>-->
|
||||||
|
<!-- </el-option>-->
|
||||||
|
<!-- </el-option-group>-->
|
||||||
|
<!-- <el-option-group v-if="offlineFriends.length" :label="t('side_panel.offline')">-->
|
||||||
|
<!-- <el-option-->
|
||||||
|
<!-- v-for="friend in offlineFriends"-->
|
||||||
|
<!-- :key="friend.id"-->
|
||||||
|
<!-- class="x-friend-item"-->
|
||||||
|
<!-- :label="friend.name"-->
|
||||||
|
<!-- :value="friend.id"-->
|
||||||
|
<!-- style="height: auto">-->
|
||||||
|
<!-- <template v-if="friend.ref">-->
|
||||||
|
<!-- <div class="avatar">-->
|
||||||
|
<!-- <img v-lazy="userImage(friend.ref)" />-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- <div class="detail">-->
|
||||||
|
<!-- <span-->
|
||||||
|
<!-- class="name"-->
|
||||||
|
<!-- :style="{ color: friend.ref.$userColour }"-->
|
||||||
|
<!-- v-text="friend.ref.displayName"></span>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- </template>-->
|
||||||
|
<!-- <span v-else v-text="friend.id"></span>-->
|
||||||
|
<!-- </el-option>-->
|
||||||
|
<!-- </el-option-group>-->
|
||||||
|
<!-- </el-select>-->
|
||||||
|
|
||||||
|
<!-- <br />-->
|
||||||
|
<!-- <br />-->
|
||||||
|
|
||||||
|
<!-- <el-select-->
|
||||||
|
<!-- v-model="fileId"-->
|
||||||
|
<!-- clearable-->
|
||||||
|
<!-- :placeholder="t('dialog.boop_dialog.select_emoji')"-->
|
||||||
|
<!-- size="small"-->
|
||||||
|
<!-- style="width: 100%"-->
|
||||||
|
<!-- popper-class="max-height-el-select">-->
|
||||||
|
<!-- <el-option-group :label="t('dialog.boop_dialog.my_emojis')">-->
|
||||||
|
<!-- <el-option-->
|
||||||
|
<!-- v-for="image in emojiTable"-->
|
||||||
|
<!-- v-if="image.versions && image.versions.length > 0"-->
|
||||||
|
<!-- :key="image.id"-->
|
||||||
|
<!-- :value="image.id"-->
|
||||||
|
<!-- style="width: 100%; height: 100%">-->
|
||||||
|
<!-- <div-->
|
||||||
|
<!-- v-if="image.versions[image.versions.length - 1].file.url"-->
|
||||||
|
<!-- class="vrcplus-icon"-->
|
||||||
|
<!-- style="overflow: hidden; width: 200px; height: 200px; padding: 10px">-->
|
||||||
|
<!-- <template v-if="image.frames">-->
|
||||||
|
<!-- <div-->
|
||||||
|
<!-- class="avatar"-->
|
||||||
|
<!-- :style="-->
|
||||||
|
<!-- generateEmojiStyle(-->
|
||||||
|
<!-- image.versions[image.versions.length - 1].file.url,-->
|
||||||
|
<!-- image.framesOverTime,-->
|
||||||
|
<!-- image.frames,-->
|
||||||
|
<!-- image.loopStyle-->
|
||||||
|
<!-- )-->
|
||||||
|
<!-- "></div>-->
|
||||||
|
<!-- </template>-->
|
||||||
|
<!-- <template v-else>-->
|
||||||
|
<!-- <img-->
|
||||||
|
<!-- v-lazy="image.versions[image.versions.length - 1].file.url"-->
|
||||||
|
<!-- class="avatar"-->
|
||||||
|
<!-- style="width: 200px; height: 200px" />-->
|
||||||
|
<!-- </template>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- </el-option>-->
|
||||||
|
<!-- </el-option-group>-->
|
||||||
|
<!-- <el-option-group :label="t('dialog.boop_dialog.default_emojis')">-->
|
||||||
|
<!-- <el-option-->
|
||||||
|
<!-- v-for="emojiName in photonEmojis"-->
|
||||||
|
<!-- :key="emojiName"-->
|
||||||
|
<!-- :value="getEmojiValue(emojiName)"-->
|
||||||
|
<!-- style="width: 100%; height: 100%">-->
|
||||||
|
<!-- <span v-text="emojiName"></span>-->
|
||||||
|
<!-- </el-option>-->
|
||||||
|
<!-- </el-option-group>-->
|
||||||
|
<!-- </el-select>-->
|
||||||
|
|
||||||
|
<!-- <template #footer>-->
|
||||||
|
<!-- <el-button size="small" @click="showGalleryDialog(2)">{{-->
|
||||||
|
<!-- t('dialog.boop_dialog.emoji_manager')-->
|
||||||
|
<!-- }}</el-button>-->
|
||||||
|
<!-- <el-button size="small" @click="closeDialog">{{ t('dialog.boop_dialog.cancel') }}</el-button>-->
|
||||||
|
<!-- <el-button size="small" :disabled="!sendBoopDialog.userId" @click="sendBoop">{{-->
|
||||||
|
<!-- t('dialog.boop_dialog.send')-->
|
||||||
|
<!-- }}</el-button>-->
|
||||||
|
<!-- </template>-->
|
||||||
|
<!-- </safe-dialog>-->
|
||||||
|
<!--</template>-->
|
||||||
|
|
||||||
|
<!--<script setup>-->
|
||||||
|
<!-- import { inject, ref } from 'vue';-->
|
||||||
|
<!-- import { useI18n } from 'vue-i18n-bridge';-->
|
||||||
|
<!-- import { photonEmojis } from '../../composables/shared/constants/photon.js';-->
|
||||||
|
<!-- import { notificationRequest } from '../../api';-->
|
||||||
|
<!-- // import { miscRequest } from '../../api';-->
|
||||||
|
|
||||||
|
<!-- const { t } = useI18n();-->
|
||||||
|
|
||||||
|
<!-- const userStatusClass = inject('userStatusClass');-->
|
||||||
|
<!-- const userImage = inject('userImage');-->
|
||||||
|
<!-- const showGalleryDialog = inject('showGalleryDialog');-->
|
||||||
|
|
||||||
|
<!-- const props = defineProps({-->
|
||||||
|
<!-- sendBoopDialog: {-->
|
||||||
|
<!-- type: Object,-->
|
||||||
|
<!-- required: true-->
|
||||||
|
<!-- },-->
|
||||||
|
<!-- emojiTable: {-->
|
||||||
|
<!-- type: Array,-->
|
||||||
|
<!-- required: true-->
|
||||||
|
<!-- },-->
|
||||||
|
<!-- vipFriends: {-->
|
||||||
|
<!-- type: Array,-->
|
||||||
|
<!-- required: true-->
|
||||||
|
<!-- },-->
|
||||||
|
<!-- onlineFriends: {-->
|
||||||
|
<!-- type: Array,-->
|
||||||
|
<!-- required: true-->
|
||||||
|
<!-- },-->
|
||||||
|
<!-- activeFriends: {-->
|
||||||
|
<!-- type: Array,-->
|
||||||
|
<!-- required: true-->
|
||||||
|
<!-- },-->
|
||||||
|
<!-- offlineFriends: {-->
|
||||||
|
<!-- type: Array,-->
|
||||||
|
<!-- required: true-->
|
||||||
|
<!-- },-->
|
||||||
|
<!-- generateEmojiStyle: {-->
|
||||||
|
<!-- type: Function,-->
|
||||||
|
<!-- required: true-->
|
||||||
|
<!-- },-->
|
||||||
|
<!-- notificationTable: {-->
|
||||||
|
<!-- type: Object,-->
|
||||||
|
<!-- required: true-->
|
||||||
|
<!-- }-->
|
||||||
|
<!-- });-->
|
||||||
|
|
||||||
|
<!-- const emit = defineEmits(['update:sendBoopDialog']);-->
|
||||||
|
|
||||||
|
<!-- const fileId = ref('');-->
|
||||||
|
|
||||||
|
<!-- // $app.data.sendBoopDialog = {-->
|
||||||
|
<!-- // visible: false,-->
|
||||||
|
<!-- // userId: ''-->
|
||||||
|
<!-- // };-->
|
||||||
|
<!-- // $app.methods.showSendBoopDialog = function (userId) {-->
|
||||||
|
<!-- // this.$nextTick(() =>-->
|
||||||
|
<!-- // $app.adjustDialogZ(this.$refs.sendBoopDialog.$el)-->
|
||||||
|
<!-- // );-->
|
||||||
|
<!-- // const D = this.sendBoopDialog;-->
|
||||||
|
<!-- // D.userId = userId;-->
|
||||||
|
<!-- // D.visible = true;-->
|
||||||
|
<!-- // if (this.emojiTable.length === 0 && API.currentUser.$isVRCPlus) {-->
|
||||||
|
<!-- // this.refreshEmojiTable();-->
|
||||||
|
<!-- // }-->
|
||||||
|
<!-- // };-->
|
||||||
|
|
||||||
|
<!-- function closeDialog() {-->
|
||||||
|
<!-- emit('update:sendBoopDialog', {-->
|
||||||
|
<!-- ...props.sendBoopDialog,-->
|
||||||
|
<!-- visible: false-->
|
||||||
|
<!-- });-->
|
||||||
|
<!-- }-->
|
||||||
|
<!-- function getEmojiValue(emojiName) {-->
|
||||||
|
<!-- if (!emojiName) {-->
|
||||||
|
<!-- return '';-->
|
||||||
|
<!-- }-->
|
||||||
|
<!-- return `vrchat_${emojiName.replace(/ /g, '_').toLowerCase()}`;-->
|
||||||
|
<!-- }-->
|
||||||
|
|
||||||
|
<!-- function sendBoop() {-->
|
||||||
|
<!-- const D = props.sendBoopDialog;-->
|
||||||
|
<!-- dismissBoop(D.userId);-->
|
||||||
|
<!-- const params = {-->
|
||||||
|
<!-- userId: D.userId-->
|
||||||
|
<!-- };-->
|
||||||
|
<!-- if (fileId.value) {-->
|
||||||
|
<!-- params.emojiId = fileId.value;-->
|
||||||
|
<!-- }-->
|
||||||
|
<!-- // miscRequest.sendBoop(params);-->
|
||||||
|
<!-- D.visible = false;-->
|
||||||
|
<!-- }-->
|
||||||
|
|
||||||
|
<!-- function dismissBoop(userId) {-->
|
||||||
|
<!-- // JANK: This is a hack to remove boop notifications when responding-->
|
||||||
|
<!-- const array = props.notificationTable.data;-->
|
||||||
|
<!-- for (let i = array.length - 1; i >= 0; i--) {-->
|
||||||
|
<!-- const ref = array[i];-->
|
||||||
|
<!-- if (ref.type !== 'boop' || ref.$isExpired || ref.senderUserId !== userId) {-->
|
||||||
|
<!-- continue;-->
|
||||||
|
<!-- }-->
|
||||||
|
<!-- notificationRequest.sendNotificationResponse({-->
|
||||||
|
<!-- notificationId: ref.id,-->
|
||||||
|
<!-- responseType: 'delete',-->
|
||||||
|
<!-- responseData: ''-->
|
||||||
|
<!-- });-->
|
||||||
|
<!-- }-->
|
||||||
|
<!-- }-->
|
||||||
|
<!--</script>-->
|
||||||
90
src/components/dialogs/UserDialog/BioDialog.vue
Normal file
90
src/components/dialogs/UserDialog/BioDialog.vue
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
<template>
|
||||||
|
<safe-dialog
|
||||||
|
class="x-dialog"
|
||||||
|
:visible.sync="bioDialog.visible"
|
||||||
|
:title="t('dialog.bio.header')"
|
||||||
|
width="600px"
|
||||||
|
append-to-body>
|
||||||
|
<div v-loading="bioDialog.loading">
|
||||||
|
<el-input
|
||||||
|
v-model="bioDialog.bio"
|
||||||
|
type="textarea"
|
||||||
|
size="mini"
|
||||||
|
maxlength="512"
|
||||||
|
show-word-limit
|
||||||
|
:autosize="{ minRows: 5, maxRows: 20 }"
|
||||||
|
:placeholder="t('dialog.bio.bio_placeholder')"
|
||||||
|
style="margin-bottom: 10px">
|
||||||
|
</el-input>
|
||||||
|
|
||||||
|
<el-input
|
||||||
|
v-for="(link, index) in bioDialog.bioLinks"
|
||||||
|
:key="index"
|
||||||
|
v-model="bioDialog.bioLinks[index]"
|
||||||
|
:value="link"
|
||||||
|
size="small"
|
||||||
|
style="margin-top: 5px">
|
||||||
|
<img
|
||||||
|
slot="prepend"
|
||||||
|
:src="getFaviconUrl(link)"
|
||||||
|
style="width: 16px; height: 16px; vertical-align: middle" />
|
||||||
|
<el-button slot="append" icon="el-icon-delete" @click="bioDialog.bioLinks.splice(index, 1)" />
|
||||||
|
</el-input>
|
||||||
|
|
||||||
|
<el-button
|
||||||
|
:disabled="bioDialog.bioLinks.length >= 3"
|
||||||
|
size="mini"
|
||||||
|
style="margin-top: 5px"
|
||||||
|
@click="bioDialog.bioLinks.push('')">
|
||||||
|
{{ t('dialog.bio.add_link') }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<el-button type="primary" size="small" :disabled="bioDialog.loading" @click="saveBio">
|
||||||
|
{{ t('dialog.bio.update') }}
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</safe-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { getCurrentInstance } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n-bridge';
|
||||||
|
import { userRequest } from '../../../api';
|
||||||
|
import { getFaviconUrl } from '../../../composables/shared/utils';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const { $message } = getCurrentInstance().proxy;
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
bioDialog: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function saveBio() {
|
||||||
|
const D = props.bioDialog;
|
||||||
|
if (D.loading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
D.loading = true;
|
||||||
|
userRequest
|
||||||
|
.saveCurrentUser({
|
||||||
|
bio: D.bio,
|
||||||
|
bioLinks: D.bioLinks
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
D.loading = false;
|
||||||
|
})
|
||||||
|
.then((args) => {
|
||||||
|
D.visible = false;
|
||||||
|
$message({
|
||||||
|
message: 'Bio updated',
|
||||||
|
type: 'success'
|
||||||
|
});
|
||||||
|
return args;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
1024
src/components/dialogs/UserDialog/GalleryDialog.vue
Normal file
1024
src/components/dialogs/UserDialog/GalleryDialog.vue
Normal file
File diff suppressed because it is too large
Load Diff
96
src/components/dialogs/UserDialog/LanguageDialog.vue
Normal file
96
src/components/dialogs/UserDialog/LanguageDialog.vue
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
<template>
|
||||||
|
<safe-dialog
|
||||||
|
class="x-dialog"
|
||||||
|
:visible.sync="languageDialog.visible"
|
||||||
|
:title="t('dialog.language.header')"
|
||||||
|
width="400px"
|
||||||
|
append-to-body>
|
||||||
|
<div v-loading="languageDialog.loading">
|
||||||
|
<div v-for="item in API.currentUser.$languages" :key="item.key" style="margin: 6px 0">
|
||||||
|
<el-tag
|
||||||
|
size="small"
|
||||||
|
type="info"
|
||||||
|
effect="plain"
|
||||||
|
closable
|
||||||
|
style="margin-right: 5px"
|
||||||
|
@close="removeUserLanguage(item.key)">
|
||||||
|
<span
|
||||||
|
class="flags"
|
||||||
|
:class="languageClass(item.key)"
|
||||||
|
style="display: inline-block; margin-right: 5px"></span>
|
||||||
|
{{ item.value }} ({{ item.key.toUpperCase() }})
|
||||||
|
</el-tag>
|
||||||
|
</div>
|
||||||
|
<el-select
|
||||||
|
value=""
|
||||||
|
:disabled="
|
||||||
|
languageDialog.loading || (API.currentUser.$languages && API.currentUser.$languages.length === 3)
|
||||||
|
"
|
||||||
|
:placeholder="t('dialog.language.select_language')"
|
||||||
|
style="margin-top: 14px"
|
||||||
|
@change="addUserLanguage">
|
||||||
|
<el-option
|
||||||
|
v-for="item in languageDialog.languages"
|
||||||
|
:key="item.key"
|
||||||
|
:value="item.key"
|
||||||
|
:label="item.value">
|
||||||
|
<span
|
||||||
|
class="flags"
|
||||||
|
:class="languageClass(item.key)"
|
||||||
|
style="display: inline-block; margin-right: 5px"></span>
|
||||||
|
{{ item.value }} ({{ item.key.toUpperCase() }})
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
</safe-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { inject } from 'vue';
|
||||||
|
|
||||||
|
import { useI18n } from 'vue-i18n-bridge';
|
||||||
|
import { userRequest } from '../../../api';
|
||||||
|
|
||||||
|
import { languageClass } from '../../../composables/user/utils';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const API = inject('API');
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
languageDialog: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function removeUserLanguage(language) {
|
||||||
|
if (language !== String(language)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const D = props.languageDialog;
|
||||||
|
D.loading = true;
|
||||||
|
userRequest
|
||||||
|
.removeUserTags({
|
||||||
|
tags: [`language_${language}`]
|
||||||
|
})
|
||||||
|
.finally(function () {
|
||||||
|
D.loading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function addUserLanguage(language) {
|
||||||
|
if (language !== String(language)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const D = props.languageDialog;
|
||||||
|
D.loading = true;
|
||||||
|
userRequest
|
||||||
|
.addUserTags({
|
||||||
|
tags: [`language_${language}`]
|
||||||
|
})
|
||||||
|
.finally(function () {
|
||||||
|
D.loading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -1,12 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<safe-dialog
|
||||||
ref="previousInstancesUserDialog"
|
ref="previousInstancesUserDialog"
|
||||||
:before-close="beforeDialogClose"
|
|
||||||
:visible.sync="isVisible"
|
:visible.sync="isVisible"
|
||||||
:title="$t('dialog.previous_instances.header')"
|
:title="$t('dialog.previous_instances.header')"
|
||||||
width="1000px"
|
width="1000px"
|
||||||
@mousedown.native="dialogMouseDown"
|
append-to-body>
|
||||||
@mouseup.native="dialogMouseUp">
|
|
||||||
<div style="display: flex; align-items: center; justify-content: space-between">
|
<div style="display: flex; align-items: center; justify-content: space-between">
|
||||||
<span style="font-size: 14px" v-text="previousInstancesUserDialog.userRef.displayName"></span>
|
<span style="font-size: 14px" v-text="previousInstancesUserDialog.userRef.displayName"></span>
|
||||||
<el-input
|
<el-input
|
||||||
@@ -68,11 +66,12 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</data-tables>
|
</data-tables>
|
||||||
</el-dialog>
|
</safe-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import utils from '../../../classes/utils';
|
import utils from '../../../classes/utils';
|
||||||
|
import { parseLocation } from '../../../composables/instance/utils';
|
||||||
import database from '../../../service/database';
|
import database from '../../../service/database';
|
||||||
import Location from '../../Location.vue';
|
import Location from '../../Location.vue';
|
||||||
|
|
||||||
@@ -81,14 +80,7 @@
|
|||||||
components: {
|
components: {
|
||||||
Location
|
Location
|
||||||
},
|
},
|
||||||
inject: [
|
inject: ['adjustDialogZ', 'showLaunchDialog', 'showPreviousInstancesInfoDialog'],
|
||||||
'beforeDialogClose',
|
|
||||||
'dialogMouseDown',
|
|
||||||
'dialogMouseUp',
|
|
||||||
'adjustDialogZ',
|
|
||||||
'showLaunchDialog',
|
|
||||||
'showPreviousInstancesInfoDialog'
|
|
||||||
],
|
|
||||||
props: {
|
props: {
|
||||||
previousInstancesUserDialog: {
|
previousInstancesUserDialog: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -176,7 +168,7 @@
|
|||||||
database.getpreviousInstancesByUserId(this.previousInstancesUserDialog.userRef).then((data) => {
|
database.getpreviousInstancesByUserId(this.previousInstancesUserDialog.userRef).then((data) => {
|
||||||
const array = [];
|
const array = [];
|
||||||
for (const ref of data.values()) {
|
for (const ref of data.values()) {
|
||||||
ref.$location = utils.parseLocation(ref.location);
|
ref.$location = parseLocation(ref.location);
|
||||||
if (ref.time > 0) {
|
if (ref.time > 0) {
|
||||||
ref.timer = utils.timeToText(ref.time);
|
ref.timer = utils.timeToText(ref.time);
|
||||||
} else {
|
} else {
|
||||||
65
src/components/dialogs/UserDialog/PronounsDialog.vue
Normal file
65
src/components/dialogs/UserDialog/PronounsDialog.vue
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<template>
|
||||||
|
<safe-dialog
|
||||||
|
class="x-dialog"
|
||||||
|
:visible.sync="pronounsDialog.visible"
|
||||||
|
:title="t('dialog.pronouns.header')"
|
||||||
|
width="600px"
|
||||||
|
append-to-body>
|
||||||
|
<div v-loading="pronounsDialog.loading">
|
||||||
|
<el-input
|
||||||
|
type="textarea"
|
||||||
|
v-model="pronounsDialog.pronouns"
|
||||||
|
size="mini"
|
||||||
|
maxlength="32"
|
||||||
|
show-word-limit
|
||||||
|
:autosize="{ minRows: 2, maxRows: 5 }"
|
||||||
|
:placeholder="t('dialog.pronouns.pronouns_placeholder')">
|
||||||
|
</el-input>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<el-button type="primary" size="small" :disabled="pronounsDialog.loading" @click="savePronouns">
|
||||||
|
{{ t('dialog.pronouns.update') }}
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</safe-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { getCurrentInstance } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n-bridge';
|
||||||
|
import { userRequest } from '../../../api';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const { proxy } = getCurrentInstance();
|
||||||
|
const { $message } = proxy;
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
pronounsDialog: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function savePronouns() {
|
||||||
|
const D = props.pronounsDialog;
|
||||||
|
if (D.loading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
D.loading = true;
|
||||||
|
userRequest
|
||||||
|
.saveCurrentUser({
|
||||||
|
pronouns: D.pronouns
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
D.loading = false;
|
||||||
|
})
|
||||||
|
.then((args) => {
|
||||||
|
D.visible = false;
|
||||||
|
$message({
|
||||||
|
message: 'Pronouns updated',
|
||||||
|
type: 'success'
|
||||||
|
});
|
||||||
|
return args;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
138
src/components/dialogs/UserDialog/SendInviteRequestDialog.vue
Normal file
138
src/components/dialogs/UserDialog/SendInviteRequestDialog.vue
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
<template>
|
||||||
|
<safe-dialog
|
||||||
|
class="x-dialog"
|
||||||
|
:visible.sync="sendInviteRequestDialogVisible"
|
||||||
|
:title="t('dialog.invite_request_message.header')"
|
||||||
|
width="800px"
|
||||||
|
append-to-body
|
||||||
|
@close="cancelSendInviteRequest">
|
||||||
|
<template v-if="API.currentUser.$isVRCPlus">
|
||||||
|
<input class="inviteImageUploadButton" type="file" accept="image/*" @change="inviteImageUpload" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<data-tables
|
||||||
|
v-bind="inviteRequestMessageTable"
|
||||||
|
style="margin-top: 10px; cursor: pointer"
|
||||||
|
@row-click="showSendInviteConfirmDialog">
|
||||||
|
<el-table-column
|
||||||
|
:label="t('table.profile.invite_messages.slot')"
|
||||||
|
prop="slot"
|
||||||
|
sortable="custom"
|
||||||
|
width="70"></el-table-column>
|
||||||
|
<el-table-column :label="t('table.profile.invite_messages.message')" prop="message"></el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
:label="t('table.profile.invite_messages.cool_down')"
|
||||||
|
prop="updatedAt"
|
||||||
|
sortable="custom"
|
||||||
|
width="110"
|
||||||
|
align="right">
|
||||||
|
<template #default="scope">
|
||||||
|
<countdown-timer :datetime="scope.row.updatedAt" :hours="1"></countdown-timer>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column :label="t('table.profile.invite_messages.action')" width="70" align="right">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button
|
||||||
|
type="text"
|
||||||
|
icon="el-icon-edit"
|
||||||
|
size="mini"
|
||||||
|
@click.stop="showEditAndSendInviteDialog('request', scope.row)"></el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</data-tables>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<el-button type="small" @click="cancelSendInviteRequest">{{
|
||||||
|
t('dialog.invite_request_message.cancel')
|
||||||
|
}}</el-button>
|
||||||
|
<el-button type="small" @click="API.refreshInviteMessageTableData('request')">{{
|
||||||
|
t('dialog.invite_request_message.refresh')
|
||||||
|
}}</el-button>
|
||||||
|
</template>
|
||||||
|
<SendInviteConfirmDialog
|
||||||
|
:visible.sync="isSendInviteConfirmDialogVisible"
|
||||||
|
:send-invite-dialog="sendInviteDialog"
|
||||||
|
:invite-dialog="inviteDialog"
|
||||||
|
:upload-image="uploadImage"
|
||||||
|
@closeInviteDialog="closeInviteDialog" />
|
||||||
|
<EditAndSendInviteDialog
|
||||||
|
:edit-and-send-invite-dialog.sync="editAndSendInviteDialog"
|
||||||
|
:send-invite-dialog="sendInviteDialog"
|
||||||
|
:invite-dialog="inviteDialog"
|
||||||
|
:upload-image="uploadImage"
|
||||||
|
@closeInviteDialog="closeInviteDialog" />
|
||||||
|
</safe-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { inject, ref } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n-bridge';
|
||||||
|
import EditAndSendInviteDialog from '../InviteDialog/EditAndSendInviteDialog.vue';
|
||||||
|
import SendInviteConfirmDialog from '../InviteDialog/SendInviteConfirmDialog.vue';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const API = inject('API');
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
sendInviteRequestDialogVisible: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
inviteRequestMessageTable: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
sendInviteDialog: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
inviteDialog: {
|
||||||
|
type: Object,
|
||||||
|
require: false,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
uploadImage: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['inviteImageUpload', 'update:sendInviteRequestDialogVisible', 'closeInviteDialog']);
|
||||||
|
|
||||||
|
const isSendInviteConfirmDialogVisible = ref(false);
|
||||||
|
|
||||||
|
const editAndSendInviteDialog = ref({
|
||||||
|
visible: false,
|
||||||
|
messageType: '',
|
||||||
|
newMessage: '',
|
||||||
|
inviteMessage: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
function inviteImageUpload(event) {
|
||||||
|
emit('inviteImageUpload', event);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSendInviteConfirmDialog(val) {
|
||||||
|
isSendInviteConfirmDialogVisible.value = true;
|
||||||
|
//
|
||||||
|
props.sendInviteDialog.messageSlot = val.slot;
|
||||||
|
}
|
||||||
|
|
||||||
|
function showEditAndSendInviteDialog(messageType, inviteMessage) {
|
||||||
|
editAndSendInviteDialog.value = {
|
||||||
|
newMessage: inviteMessage.message,
|
||||||
|
visible: true,
|
||||||
|
messageType,
|
||||||
|
inviteMessage
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelSendInviteRequest() {
|
||||||
|
emit('update:sendInviteRequestDialogVisible', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeInviteDialog() {
|
||||||
|
cancelSendInviteRequest();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
109
src/components/dialogs/UserDialog/SocialStatusDialog.vue
Normal file
109
src/components/dialogs/UserDialog/SocialStatusDialog.vue
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
<template>
|
||||||
|
<safe-dialog
|
||||||
|
class="x-dialog"
|
||||||
|
:visible.sync="socialStatusDialog.visible"
|
||||||
|
:title="t('dialog.social_status.header')"
|
||||||
|
append-to-body
|
||||||
|
width="400px">
|
||||||
|
<div v-loading="socialStatusDialog.loading">
|
||||||
|
<el-collapse style="border: 0">
|
||||||
|
<el-collapse-item>
|
||||||
|
<template #title>
|
||||||
|
<span style="font-size: 16px">{{ t('dialog.social_status.history') }}</span>
|
||||||
|
</template>
|
||||||
|
<data-tables
|
||||||
|
v-bind="socialStatusHistoryTable"
|
||||||
|
style="cursor: pointer"
|
||||||
|
@row-click="setSocialStatusFromHistory">
|
||||||
|
<el-table-column :label="t('table.social_status.no')" prop="no" width="50"></el-table-column>
|
||||||
|
<el-table-column :label="t('table.social_status.status')" prop="status"></el-table-column>
|
||||||
|
</data-tables>
|
||||||
|
</el-collapse-item>
|
||||||
|
</el-collapse>
|
||||||
|
|
||||||
|
<el-select v-model="socialStatusDialog.status" style="display: block; margin-top: 10px">
|
||||||
|
<el-option :label="t('dialog.user.status.join_me')" value="join me">
|
||||||
|
<i class="x-user-status joinme"></i> {{ t('dialog.user.status.join_me') }}
|
||||||
|
</el-option>
|
||||||
|
<el-option :label="t('dialog.user.status.online')" value="active">
|
||||||
|
<i class="x-user-status online"></i> {{ t('dialog.user.status.online') }}
|
||||||
|
</el-option>
|
||||||
|
<el-option :label="t('dialog.user.status.ask_me')" value="ask me">
|
||||||
|
<i class="x-user-status askme"></i> {{ t('dialog.user.status.ask_me') }}
|
||||||
|
</el-option>
|
||||||
|
<el-option :label="t('dialog.user.status.busy')" value="busy">
|
||||||
|
<i class="x-user-status busy"></i> {{ t('dialog.user.status.busy') }}
|
||||||
|
</el-option>
|
||||||
|
<el-option v-if="API.currentUser.$isModerator" :label="t('dialog.user.status.offline')" value="offline">
|
||||||
|
<i class="x-user-status offline"></i> {{ t('dialog.user.status.offline') }}
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
|
|
||||||
|
<el-input
|
||||||
|
v-model="socialStatusDialog.statusDescription"
|
||||||
|
:placeholder="t('dialog.social_status.status_placeholder')"
|
||||||
|
maxlength="32"
|
||||||
|
show-word-limit
|
||||||
|
style="display: block; margin-top: 10px"></el-input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<el-button type="primary" size="small" :disabled="socialStatusDialog.loading" @click="saveSocialStatus">
|
||||||
|
{{ t('dialog.social_status.update') }}
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</safe-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { inject, getCurrentInstance } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n-bridge';
|
||||||
|
import { userRequest } from '../../../api';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const { $message } = getCurrentInstance().proxy;
|
||||||
|
const API = inject('API');
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
socialStatusDialog: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
socialStatusHistoryTable: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function setSocialStatusFromHistory(val) {
|
||||||
|
if (val === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const D = props.socialStatusDialog;
|
||||||
|
D.statusDescription = val.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveSocialStatus() {
|
||||||
|
const D = props.socialStatusDialog;
|
||||||
|
if (D.loading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
D.loading = true;
|
||||||
|
userRequest
|
||||||
|
.saveCurrentUser({
|
||||||
|
status: D.status,
|
||||||
|
statusDescription: D.statusDescription
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
D.loading = false;
|
||||||
|
})
|
||||||
|
.then((args) => {
|
||||||
|
D.visible = false;
|
||||||
|
$message({
|
||||||
|
message: 'Status updated',
|
||||||
|
type: 'success'
|
||||||
|
});
|
||||||
|
return args;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
3179
src/components/dialogs/UserDialog/UserDialog.vue
Normal file
3179
src/components/dialogs/UserDialog/UserDialog.vue
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<safe-dialog
|
||||||
ref="VRCXUpdateDialogRef"
|
ref="VRCXUpdateDialogRef"
|
||||||
class="x-dialog"
|
class="x-dialog"
|
||||||
:before-close="beforeDialogClose"
|
|
||||||
:visible.sync="VRCXUpdateDialog.visible"
|
:visible.sync="VRCXUpdateDialog.visible"
|
||||||
:title="t('dialog.vrcx_updater.header')"
|
:title="t('dialog.vrcx_updater.header')"
|
||||||
width="400px"
|
width="400px">
|
||||||
@mousedown.native="dialogMouseDown"
|
|
||||||
@mouseup.native="dialogMouseUp">
|
|
||||||
<div v-loading="checkingForVRCXUpdate" style="margin-top: 15px">
|
<div v-loading="checkingForVRCXUpdate" style="margin-top: 15px">
|
||||||
<template v-if="updateInProgress">
|
<template v-if="updateInProgress">
|
||||||
<el-progress :percentage="updateProgress" :format="updateProgressText"></el-progress>
|
<el-progress :percentage="updateProgress" :format="updateProgressText"></el-progress>
|
||||||
@@ -62,7 +59,7 @@
|
|||||||
{{ t('dialog.vrcx_updater.install') }}
|
{{ t('dialog.vrcx_updater.install') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</safe-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
@@ -71,9 +68,6 @@
|
|||||||
import { useI18n } from 'vue-i18n-bridge';
|
import { useI18n } from 'vue-i18n-bridge';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const beforeDialogClose = inject('beforeDialogClose');
|
|
||||||
const dialogMouseDown = inject('dialogMouseDown');
|
|
||||||
const dialogMouseUp = inject('dialogMouseUp');
|
|
||||||
const adjustDialogZ = inject('adjustDialogZ');
|
const adjustDialogZ = inject('adjustDialogZ');
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
|||||||
394
src/components/dialogs/WorldDialog/ChangeWorldImageDialog.vue
Normal file
394
src/components/dialogs/WorldDialog/ChangeWorldImageDialog.vue
Normal file
@@ -0,0 +1,394 @@
|
|||||||
|
<template>
|
||||||
|
<safe-dialog
|
||||||
|
class="x-dialog"
|
||||||
|
:visible="changeWorldImageDialogVisible"
|
||||||
|
:title="t('dialog.change_content_image.world')"
|
||||||
|
width="850px"
|
||||||
|
append-to-body
|
||||||
|
@close="closeDialog">
|
||||||
|
<div v-loading="changeWorldImageDialogLoading">
|
||||||
|
<input
|
||||||
|
id="WorldImageUploadButton"
|
||||||
|
type="file"
|
||||||
|
accept="image/*"
|
||||||
|
style="display: none"
|
||||||
|
@change="onFileChangeWorldImage" />
|
||||||
|
<span>{{ t('dialog.change_content_image.description') }}</span>
|
||||||
|
<br />
|
||||||
|
<el-button-group style="padding-bottom: 10px; padding-top: 10px">
|
||||||
|
<el-button type="default" size="small" icon="el-icon-refresh" @click="refresh">{{
|
||||||
|
t('dialog.change_content_image.refresh')
|
||||||
|
}}</el-button>
|
||||||
|
<el-button type="default" size="small" icon="el-icon-upload2" @click="uploadWorldImage">{{
|
||||||
|
t('dialog.change_content_image.upload')
|
||||||
|
}}</el-button>
|
||||||
|
<!-- el-button(type="default" size="small" @click="deleteWorldImage" icon="el-icon-delete") Delete Latest Image-->
|
||||||
|
</el-button-group>
|
||||||
|
<br />
|
||||||
|
<div
|
||||||
|
v-for="image in previousImagesTable"
|
||||||
|
v-if="image.file"
|
||||||
|
:key="image.version"
|
||||||
|
style="display: inline-block">
|
||||||
|
<div
|
||||||
|
class="x-change-image-item"
|
||||||
|
style="cursor: pointer"
|
||||||
|
:class="{ 'current-image': compareCurrentImage(image) }"
|
||||||
|
@click="setWorldImage(image)">
|
||||||
|
<img v-lazy="image.file.url" class="image" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</safe-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { getCurrentInstance, inject, ref } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n-bridge';
|
||||||
|
import { imageRequest } from '../../../api';
|
||||||
|
import { extractFileId } from '../../../composables/shared/utils';
|
||||||
|
import webApiService from '../../../service/webapi';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const API = inject('API');
|
||||||
|
|
||||||
|
const instance = getCurrentInstance();
|
||||||
|
const $message = instance.proxy.$message;
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
changeWorldImageDialogVisible: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
previousImagesTable: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
previousImagesFileId: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
worldDialog: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:changeWorldImageDialogVisible', 'refresh']);
|
||||||
|
|
||||||
|
const changeWorldImageDialogLoading = ref(false);
|
||||||
|
const worldImage = ref({
|
||||||
|
base64File: '',
|
||||||
|
fileMd5: '',
|
||||||
|
base64SignatureFile: '',
|
||||||
|
signatureMd5: '',
|
||||||
|
fileId: '',
|
||||||
|
avatarId: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
function uploadWorldImage() {
|
||||||
|
document.getElementById('WorldImageUploadButton').click();
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeDialog() {
|
||||||
|
emit('update:changeWorldImageDialogVisible', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function refresh() {
|
||||||
|
emit('refresh', 'Change');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resizeImageToFitLimits(file) {
|
||||||
|
const response = await AppApi.ResizeImageToFitLimits(file);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function genMd5(file) {
|
||||||
|
const response = await AppApi.MD5File(file);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function genSig(file) {
|
||||||
|
const response = await AppApi.SignFile(file);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function genLength(file) {
|
||||||
|
const response = await AppApi.FileLength(file);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onFileChangeWorldImage(e) {
|
||||||
|
const clearFile = function () {
|
||||||
|
if (document.querySelector('#WorldImageUploadButton')) {
|
||||||
|
document.querySelector('#WorldImageUploadButton').value = '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const files = e.target.files || e.dataTransfer.files;
|
||||||
|
if (!files.length || !props.worldDialog.visible || props.worldDialog.loading) {
|
||||||
|
clearFile();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (files[0].size >= 100000000) {
|
||||||
|
// 100MB
|
||||||
|
$message({
|
||||||
|
message: t('message.file.too_large'),
|
||||||
|
type: 'error'
|
||||||
|
});
|
||||||
|
clearFile();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!files[0].type.match(/image.*/)) {
|
||||||
|
$message({
|
||||||
|
message: t('message.file.not_image'),
|
||||||
|
type: 'error'
|
||||||
|
});
|
||||||
|
clearFile();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
changeWorldImageDialogLoading.value = true;
|
||||||
|
const r = new FileReader();
|
||||||
|
r.onload = async function (file) {
|
||||||
|
try {
|
||||||
|
const base64File = await resizeImageToFitLimits(btoa(r.result));
|
||||||
|
// 10MB
|
||||||
|
const fileMd5 = await genMd5(base64File);
|
||||||
|
const fileSizeInBytes = parseInt(file.total, 10);
|
||||||
|
const base64SignatureFile = await genSig(base64File);
|
||||||
|
const signatureMd5 = await genMd5(base64SignatureFile);
|
||||||
|
const signatureSizeInBytes = parseInt(await genLength(base64SignatureFile), 10);
|
||||||
|
const worldId = props.worldDialog.id;
|
||||||
|
const { imageUrl } = props.worldDialog.ref;
|
||||||
|
const fileId = extractFileId(imageUrl);
|
||||||
|
if (!fileId) {
|
||||||
|
$message({
|
||||||
|
message: t('message.world.image_invalid'),
|
||||||
|
type: 'error'
|
||||||
|
});
|
||||||
|
clearFile();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
worldImage.value = {
|
||||||
|
base64File,
|
||||||
|
fileMd5,
|
||||||
|
base64SignatureFile,
|
||||||
|
signatureMd5,
|
||||||
|
fileId,
|
||||||
|
worldId
|
||||||
|
};
|
||||||
|
const params = {
|
||||||
|
fileMd5,
|
||||||
|
fileSizeInBytes,
|
||||||
|
signatureMd5,
|
||||||
|
signatureSizeInBytes
|
||||||
|
};
|
||||||
|
|
||||||
|
// Upload chaining
|
||||||
|
await initiateUpload(params, fileId);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('World image upload process failed:', error);
|
||||||
|
} finally {
|
||||||
|
changeWorldImageDialogLoading.value = false;
|
||||||
|
clearFile();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
r.readAsBinaryString(files[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------ Upload Process Start ------------
|
||||||
|
|
||||||
|
async function initiateUpload(params, fileId) {
|
||||||
|
const res = await imageRequest.uploadWorldImage(params, fileId);
|
||||||
|
return worldImageInit(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function worldImageInit(args) {
|
||||||
|
// API.$on('WORLDIMAGE:INIT')
|
||||||
|
const fileId = args.json.id;
|
||||||
|
const fileVersion = args.json.versions[args.json.versions.length - 1].version;
|
||||||
|
const params = {
|
||||||
|
fileId,
|
||||||
|
fileVersion
|
||||||
|
};
|
||||||
|
const res = await imageRequest.uploadWorldImageFileStart(params);
|
||||||
|
return worldImageFileStart(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function worldImageFileStart(args) {
|
||||||
|
// API.$on('WORLDIMAGE:FILESTART')
|
||||||
|
const { url } = args.json;
|
||||||
|
const { fileId, fileVersion } = args.params;
|
||||||
|
const params = {
|
||||||
|
url,
|
||||||
|
fileId,
|
||||||
|
fileVersion
|
||||||
|
};
|
||||||
|
return uploadWorldImageFileAWS(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function uploadWorldImageFileAWS(params) {
|
||||||
|
const json = await webApiService.execute({
|
||||||
|
url: params.url,
|
||||||
|
uploadFilePUT: true,
|
||||||
|
fileData: worldImage.value.base64File,
|
||||||
|
fileMIME: 'image/png',
|
||||||
|
headers: {
|
||||||
|
'Content-MD5': worldImage.value.fileMd5
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (json.status !== 200) {
|
||||||
|
// $app.worldDialog.loading = false;
|
||||||
|
changeWorldImageDialogLoading.value = false;
|
||||||
|
API.$throw('World image upload failed', json, params.url);
|
||||||
|
}
|
||||||
|
const args = {
|
||||||
|
json,
|
||||||
|
params
|
||||||
|
};
|
||||||
|
return worldImageFileAWS(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function worldImageFileAWS(args) {
|
||||||
|
// API.$on('WORLDIMAGE:FILEAWS')
|
||||||
|
const { fileId, fileVersion } = args.params;
|
||||||
|
const params = {
|
||||||
|
fileId,
|
||||||
|
fileVersion
|
||||||
|
};
|
||||||
|
const res = await imageRequest.uploadWorldImageFileFinish(params);
|
||||||
|
return worldImageFileFinish(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function worldImageFileFinish(args) {
|
||||||
|
// API.$on('WORLDIMAGE:FILEFINISH')
|
||||||
|
const { fileId, fileVersion } = args.params;
|
||||||
|
const params = {
|
||||||
|
fileId,
|
||||||
|
fileVersion
|
||||||
|
};
|
||||||
|
const res = await imageRequest.uploadWorldImageSigStart(params);
|
||||||
|
return worldImageSigStart(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function worldImageSigStart(args) {
|
||||||
|
// API.$on('WORLDIMAGE:SIGSTART')
|
||||||
|
const { url } = args.json;
|
||||||
|
const { fileId, fileVersion } = args.params;
|
||||||
|
const params = {
|
||||||
|
url,
|
||||||
|
fileId,
|
||||||
|
fileVersion
|
||||||
|
};
|
||||||
|
return uploadWorldImageSigAWS(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function uploadWorldImageSigAWS(params) {
|
||||||
|
const json = await webApiService.execute({
|
||||||
|
url: params.url,
|
||||||
|
uploadFilePUT: true,
|
||||||
|
fileData: worldImage.value.base64SignatureFile,
|
||||||
|
fileMIME: 'application/x-rsync-signature',
|
||||||
|
headers: {
|
||||||
|
'Content-MD5': worldImage.value.signatureMd5
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (json.status !== 200) {
|
||||||
|
// $app.worldDialog.loading = false;
|
||||||
|
changeWorldImageDialogLoading.value = false;
|
||||||
|
API.$throw('World image upload failed', json, params.url);
|
||||||
|
}
|
||||||
|
const args = {
|
||||||
|
json,
|
||||||
|
params
|
||||||
|
};
|
||||||
|
return worldImageSigAWS(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function worldImageSigAWS(args) {
|
||||||
|
// API.$on('WORLDIMAGE:SIGAWS')
|
||||||
|
const { fileId, fileVersion } = args.params;
|
||||||
|
const params = {
|
||||||
|
fileId,
|
||||||
|
fileVersion
|
||||||
|
};
|
||||||
|
const res = await imageRequest.uploadWorldImageSigFinish(params);
|
||||||
|
return worldImageSigFinish(res);
|
||||||
|
}
|
||||||
|
async function worldImageSigFinish(args) {
|
||||||
|
// API.$on('WORLDIMAGE:SIGFINISH')
|
||||||
|
const { fileId, fileVersion } = args.params;
|
||||||
|
const parmas = {
|
||||||
|
id: worldImage.value.worldId,
|
||||||
|
imageUrl: `${API.endpointDomain}/file/${fileId}/${fileVersion}/file`
|
||||||
|
};
|
||||||
|
const res = await imageRequest.setWorldImage(parmas);
|
||||||
|
return worldImageSet(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
function worldImageSet(args) {
|
||||||
|
changeWorldImageDialogLoading.value = false;
|
||||||
|
if (args.json.imageUrl === args.params.imageUrl) {
|
||||||
|
$message({
|
||||||
|
message: t('message.world.image_changed'),
|
||||||
|
type: 'success'
|
||||||
|
});
|
||||||
|
refresh();
|
||||||
|
} else {
|
||||||
|
API.$throw(0, 'World image change failed', args.params.imageUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------ Upload Process End ------------
|
||||||
|
|
||||||
|
function setWorldImage(image) {
|
||||||
|
changeWorldImageDialogLoading.value = true;
|
||||||
|
const parmas = {
|
||||||
|
id: props.worldDialog.id,
|
||||||
|
imageUrl: `${API.endpointDomain}/file/${props.previousImagesFileId}/${image.version}/file`
|
||||||
|
};
|
||||||
|
imageRequest
|
||||||
|
.setWorldImage(parmas)
|
||||||
|
.then((args) => worldImageSet(args))
|
||||||
|
.finally(() => {
|
||||||
|
changeWorldImageDialogLoading.value = false;
|
||||||
|
closeDialog();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function compareCurrentImage(image) {
|
||||||
|
if (
|
||||||
|
`${API.endpointDomain}/file/${props.previousImagesFileId}/${image.version}/file` ===
|
||||||
|
// FIXME: old:avatarDialog -> new:worldDialog, is this correct?
|
||||||
|
props.worldDialog.ref.imageUrl
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// $app.methods.deleteWorldImage = function () {
|
||||||
|
// this.changeWorldImageDialogLoading = true;
|
||||||
|
// var parmas = {
|
||||||
|
// fileId: this.previousImagesTableFileId,
|
||||||
|
// version: this.previousImagesTable[0].version
|
||||||
|
// };
|
||||||
|
// vrcPlusIconRequest
|
||||||
|
// .deleteFileVersion(parmas)
|
||||||
|
// .then((args) => {
|
||||||
|
// this.previousImagesTableFileId = args.json.id;
|
||||||
|
// var images = [];
|
||||||
|
// args.json.versions.forEach((item) => {
|
||||||
|
// if (!item.deleted) {
|
||||||
|
// images.unshift(item);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// this.checkPreviousImageAvailable(images);
|
||||||
|
// })
|
||||||
|
// .finally(() => {
|
||||||
|
// this.changeWorldImageDialogLoading = false;
|
||||||
|
// });
|
||||||
|
// };
|
||||||
|
</script>
|
||||||
@@ -1,13 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<safe-dialog
|
||||||
:before-close="beforeDialogClose"
|
|
||||||
:visible.sync="isVisible"
|
:visible.sync="isVisible"
|
||||||
:title="$t('dialog.set_world_tags.header')"
|
:title="$t('dialog.set_world_tags.header')"
|
||||||
width="400px"
|
width="400px"
|
||||||
destroy-on-close
|
destroy-on-close
|
||||||
append-to-body
|
append-to-body>
|
||||||
@mousedown.native="dialogMouseDown"
|
|
||||||
@mouseup.native="dialogMouseUp">
|
|
||||||
<el-checkbox v-model="setWorldTagsDialog.avatarScalingDisabled">
|
<el-checkbox v-model="setWorldTagsDialog.avatarScalingDisabled">
|
||||||
{{ $t('dialog.set_world_tags.avatar_scaling_disabled') }}
|
{{ $t('dialog.set_world_tags.avatar_scaling_disabled') }}
|
||||||
</el-checkbox>
|
</el-checkbox>
|
||||||
@@ -80,7 +77,7 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</safe-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -88,7 +85,7 @@
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'SetWorldTagsDialog',
|
name: 'SetWorldTagsDialog',
|
||||||
inject: ['beforeDialogClose', 'dialogMouseDown', 'dialogMouseUp', 'showWorldDialog'],
|
inject: ['showWorldDialog'],
|
||||||
props: {
|
props: {
|
||||||
oldTags: {
|
oldTags: {
|
||||||
type: Array,
|
type: Array,
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<safe-dialog
|
||||||
:before-close="beforeDialogClose"
|
|
||||||
:visible.sync="isVisible"
|
:visible.sync="isVisible"
|
||||||
:title="$t('dialog.allowed_video_player_domains.header')"
|
:title="$t('dialog.allowed_video_player_domains.header')"
|
||||||
width="600px"
|
width="600px"
|
||||||
destroy-on-close
|
destroy-on-close
|
||||||
append-to-body
|
append-to-body>
|
||||||
@mousedown.native="dialogMouseDown"
|
|
||||||
@mouseup.native="dialogMouseUp">
|
|
||||||
<div>
|
<div>
|
||||||
<el-input
|
<el-input
|
||||||
v-for="(domain, index) in urlList"
|
v-for="(domain, index) in urlList"
|
||||||
@@ -31,7 +28,7 @@
|
|||||||
{{ $t('dialog.allowed_video_player_domains.save') }}
|
{{ $t('dialog.allowed_video_player_domains.save') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</safe-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -39,7 +36,6 @@
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'WorldAllowedDomainsDialog',
|
name: 'WorldAllowedDomainsDialog',
|
||||||
inject: ['beforeDialogClose', 'dialogMouseDown', 'dialogMouseUp'],
|
|
||||||
props: {
|
props: {
|
||||||
worldAllowedDomainsDialog: {
|
worldAllowedDomainsDialog: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<safe-dialog
|
||||||
ref="worldDialog"
|
ref="worldDialog"
|
||||||
:before-close="beforeDialogClose"
|
|
||||||
class="x-dialog x-world-dialog"
|
class="x-dialog x-world-dialog"
|
||||||
:visible.sync="isDialogVisible"
|
:visible.sync="isDialogVisible"
|
||||||
:show-close="false"
|
:show-close="false"
|
||||||
width="770px"
|
width="770px">
|
||||||
@mousedown.native="dialogMouseDown"
|
|
||||||
@mouseup.native="dialogMouseUp">
|
|
||||||
<div v-loading="worldDialog.loading">
|
<div v-loading="worldDialog.loading">
|
||||||
<div style="display: flex">
|
<div style="display: flex">
|
||||||
<el-popover placement="right" width="500px" trigger="click">
|
<el-popover placement="right" width="500px" trigger="click">
|
||||||
@@ -760,22 +757,49 @@
|
|||||||
:offline-friends="offlineFriends"
|
:offline-friends="offlineFriends"
|
||||||
:active-friends="activeFriends"
|
:active-friends="activeFriends"
|
||||||
:online-friends="onlineFriends"
|
:online-friends="onlineFriends"
|
||||||
:vip-friends="vipFriends" />
|
:vip-friends="vipFriends"
|
||||||
</el-dialog>
|
:invite-message-table="inviteMessageTable"
|
||||||
|
:upload-image="uploadImage"
|
||||||
|
:last-location="lastLocation" />
|
||||||
|
<ChangeWorldImageDialog
|
||||||
|
:change-world-image-dialog-visible.sync="changeWorldImageDialogVisible"
|
||||||
|
:previous-images-table="previousImagesTable"
|
||||||
|
:previous-images-file-id="previousImagesFileId"
|
||||||
|
:world-dialog="worldDialog"
|
||||||
|
@refresh="displayPreviousImages" />
|
||||||
|
<PreviousImagesDialog
|
||||||
|
:previous-images-dialog-visible.sync="previousImagesDialogVisible"
|
||||||
|
:previous-images-table="previousImagesTable" />
|
||||||
|
</safe-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { favoriteRequest, imageRequest, miscRequest, userRequest, worldRequest } from '../../../api';
|
||||||
import utils from '../../../classes/utils';
|
import utils from '../../../classes/utils';
|
||||||
|
import { refreshInstancePlayerCount as _refreshInstancePlayerCount } from '../../../composables/instance/utils';
|
||||||
|
import {
|
||||||
|
downloadAndSaveJson as _downloadAndSaveJson,
|
||||||
|
extractFileId,
|
||||||
|
replaceVrcPackageUrl as _replaceVrcPackageUrl
|
||||||
|
} from '../../../composables/shared/utils';
|
||||||
import database from '../../../service/database.js';
|
import database from '../../../service/database.js';
|
||||||
import WorldAllowedDomainsDialog from './WorldAllowedDomainsDialog.vue';
|
|
||||||
import SetWorldTagsDialog from './SetWorldTagsDialog.vue';
|
|
||||||
import PreviousInstancesWorldDialog from '../PreviousInstancesDialog/PreviousInstancesWorldDialog.vue';
|
|
||||||
import NewInstanceDialog from '../NewInstanceDialog.vue';
|
import NewInstanceDialog from '../NewInstanceDialog.vue';
|
||||||
import { favoriteRequest, miscRequest, worldRequest } from '../../../api';
|
import PreviousImagesDialog from '../PreviousImagesDialog.vue';
|
||||||
|
import PreviousInstancesWorldDialog from '../PreviousInstancesDialog/PreviousInstancesWorldDialog.vue';
|
||||||
|
import ChangeWorldImageDialog from './ChangeWorldImageDialog.vue';
|
||||||
|
import SetWorldTagsDialog from './SetWorldTagsDialog.vue';
|
||||||
|
import WorldAllowedDomainsDialog from './WorldAllowedDomainsDialog.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'WorldDialog',
|
name: 'WorldDialog',
|
||||||
components: { SetWorldTagsDialog, WorldAllowedDomainsDialog, PreviousInstancesWorldDialog, NewInstanceDialog },
|
components: {
|
||||||
|
PreviousImagesDialog,
|
||||||
|
SetWorldTagsDialog,
|
||||||
|
WorldAllowedDomainsDialog,
|
||||||
|
PreviousInstancesWorldDialog,
|
||||||
|
NewInstanceDialog,
|
||||||
|
ChangeWorldImageDialog
|
||||||
|
},
|
||||||
inject: [
|
inject: [
|
||||||
'API',
|
'API',
|
||||||
'showUserDialog',
|
'showUserDialog',
|
||||||
@@ -785,10 +809,6 @@
|
|||||||
'showPreviousInstancesInfoDialog',
|
'showPreviousInstancesInfoDialog',
|
||||||
'showLaunchDialog',
|
'showLaunchDialog',
|
||||||
'showFullscreenImageDialog',
|
'showFullscreenImageDialog',
|
||||||
'beforeDialogClose',
|
|
||||||
'dialogMouseDown',
|
|
||||||
'dialogMouseUp',
|
|
||||||
'displayPreviousImages',
|
|
||||||
'showWorldDialog',
|
'showWorldDialog',
|
||||||
'showFavoriteDialog',
|
'showFavoriteDialog',
|
||||||
'openExternalLink'
|
'openExternalLink'
|
||||||
@@ -808,6 +828,8 @@
|
|||||||
activeFriends: Array,
|
activeFriends: Array,
|
||||||
onlineFriends: Array,
|
onlineFriends: Array,
|
||||||
vipFriends: Array,
|
vipFriends: Array,
|
||||||
|
inviteMessageTable: Object,
|
||||||
|
uploadImage: String,
|
||||||
|
|
||||||
// TODO: Remove
|
// TODO: Remove
|
||||||
updateInstanceInfo: Number
|
updateInstanceInfo: Number
|
||||||
@@ -826,7 +848,11 @@
|
|||||||
openFlg: false,
|
openFlg: false,
|
||||||
worldRef: {}
|
worldRef: {}
|
||||||
},
|
},
|
||||||
newInstanceDialogLocationTag: ''
|
newInstanceDialogLocationTag: '',
|
||||||
|
changeWorldImageDialogVisible: false,
|
||||||
|
previousImagesFileId: '',
|
||||||
|
previousImagesDialogVisible: false,
|
||||||
|
previousImagesTable: []
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -907,6 +933,51 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
displayPreviousImages(command) {
|
||||||
|
this.previousImagesFileId = '';
|
||||||
|
this.previousImagesTable = [];
|
||||||
|
const { imageUrl } = this.worldDialog.ref;
|
||||||
|
|
||||||
|
const fileId = extractFileId(imageUrl);
|
||||||
|
if (!fileId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const params = {
|
||||||
|
fileId
|
||||||
|
};
|
||||||
|
if (command === 'Display') {
|
||||||
|
this.previousImagesDialogVisible = true;
|
||||||
|
}
|
||||||
|
if (command === 'Change') {
|
||||||
|
this.changeWorldImageDialogVisible = true;
|
||||||
|
}
|
||||||
|
imageRequest.getWorldImages(params).then((args) => {
|
||||||
|
this.previousImagesFileId = args.json.id;
|
||||||
|
const images = [];
|
||||||
|
args.json.versions.forEach((item) => {
|
||||||
|
if (!item.deleted) {
|
||||||
|
images.unshift(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.checkPreviousImageAvailable(images);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async checkPreviousImageAvailable(images) {
|
||||||
|
this.previousImagesTable = [];
|
||||||
|
for (const image of images) {
|
||||||
|
if (image.file && image.file.url) {
|
||||||
|
const response = await fetch(image.file.url, {
|
||||||
|
method: 'HEAD',
|
||||||
|
redirect: 'follow'
|
||||||
|
}).catch((error) => {
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
if (response.status === 200) {
|
||||||
|
this.previousImagesTable.push(image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
showNewInstanceDialog(tag) {
|
showNewInstanceDialog(tag) {
|
||||||
// trigger watcher
|
// trigger watcher
|
||||||
this.newInstanceDialogLocationTag = '';
|
this.newInstanceDialogLocationTag = '';
|
||||||
@@ -946,26 +1017,30 @@
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case 'Make Home':
|
case 'Make Home':
|
||||||
this.API.saveCurrentUser({
|
userRequest
|
||||||
homeLocation: D.id
|
.saveCurrentUser({
|
||||||
}).then((args) => {
|
homeLocation: D.id
|
||||||
this.$message({
|
})
|
||||||
message: 'Home world updated',
|
.then((args) => {
|
||||||
type: 'success'
|
this.$message({
|
||||||
|
message: 'Home world updated',
|
||||||
|
type: 'success'
|
||||||
|
});
|
||||||
|
return args;
|
||||||
});
|
});
|
||||||
return args;
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
case 'Reset Home':
|
case 'Reset Home':
|
||||||
this.API.saveCurrentUser({
|
userRequest
|
||||||
homeLocation: ''
|
.saveCurrentUser({
|
||||||
}).then((args) => {
|
homeLocation: ''
|
||||||
this.$message({
|
})
|
||||||
message: 'Home world has been reset',
|
.then((args) => {
|
||||||
type: 'success'
|
this.$message({
|
||||||
|
message: 'Home world has been reset',
|
||||||
|
type: 'success'
|
||||||
|
});
|
||||||
|
return args;
|
||||||
});
|
});
|
||||||
return args;
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
case 'Publish':
|
case 'Publish':
|
||||||
worldRequest
|
worldRequest
|
||||||
@@ -1040,10 +1115,10 @@
|
|||||||
this.openExternalLink(this.replaceVrcPackageUrl(this.worldDialog.ref.unityPackageUrl));
|
this.openExternalLink(this.replaceVrcPackageUrl(this.worldDialog.ref.unityPackageUrl));
|
||||||
break;
|
break;
|
||||||
case 'Change Image':
|
case 'Change Image':
|
||||||
this.displayPreviousImages('World', 'Change');
|
this.displayPreviousImages('Change');
|
||||||
break;
|
break;
|
||||||
case 'Previous Images':
|
case 'Previous Images':
|
||||||
this.displayPreviousImages('World', 'Display');
|
this.displayPreviousImages('Display');
|
||||||
break;
|
break;
|
||||||
case 'Refresh':
|
case 'Refresh':
|
||||||
this.showWorldDialog(D.id);
|
this.showWorldDialog(D.id);
|
||||||
@@ -1055,12 +1130,15 @@
|
|||||||
this.showFavoriteDialog('world', D.id);
|
this.showFavoriteDialog('world', D.id);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
this.$emit('world-dialog-command', command);
|
this.$emit('worldDialogCommand', command);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
replaceVrcPackageUrl(url) {
|
||||||
|
_replaceVrcPackageUrl(url);
|
||||||
|
},
|
||||||
refreshInstancePlayerCount(tag) {
|
refreshInstancePlayerCount(tag) {
|
||||||
this.$emit('refresh-instance-player-count', tag);
|
_refreshInstancePlayerCount(tag);
|
||||||
},
|
},
|
||||||
onWorldMemoChange() {
|
onWorldMemoChange() {
|
||||||
const worldId = this.worldDialog.id;
|
const worldId = this.worldDialog.id;
|
||||||
@@ -1087,7 +1165,7 @@
|
|||||||
this.treeData = utils.buildTreeData(this.worldDialog.ref);
|
this.treeData = utils.buildTreeData(this.worldDialog.ref);
|
||||||
},
|
},
|
||||||
downloadAndSaveJson(fileName, data) {
|
downloadAndSaveJson(fileName, data) {
|
||||||
utils.downloadAndSaveJson(fileName, data);
|
_downloadAndSaveJson(fileName, data);
|
||||||
},
|
},
|
||||||
copyWorldId() {
|
copyWorldId() {
|
||||||
navigator.clipboard
|
navigator.clipboard
|
||||||
|
|||||||
125
src/composables/avatar/utils.js
Normal file
125
src/composables/avatar/utils.js
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
import utils from '../../classes/utils';
|
||||||
|
|
||||||
|
function storeAvatarImage(args) {
|
||||||
|
const refCreatedAt = args.json.versions[0];
|
||||||
|
const fileCreatedAt = refCreatedAt.created_at;
|
||||||
|
const fileId = args.params.fileId;
|
||||||
|
let avatarName = '';
|
||||||
|
const imageName = args.json.name;
|
||||||
|
const avatarNameRegex = /Avatar - (.*) - Image -/gi.exec(imageName);
|
||||||
|
if (avatarNameRegex) {
|
||||||
|
avatarName = utils.replaceBioSymbols(avatarNameRegex[1]);
|
||||||
|
}
|
||||||
|
const ownerId = args.json.ownerId;
|
||||||
|
const avatarInfo = {
|
||||||
|
ownerId,
|
||||||
|
avatarName,
|
||||||
|
fileCreatedAt
|
||||||
|
};
|
||||||
|
window.API.cachedAvatarNames.set(fileId, avatarInfo);
|
||||||
|
return avatarInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseAvatarUrl(avatar) {
|
||||||
|
const url = new URL(avatar);
|
||||||
|
const urlPath = url.pathname;
|
||||||
|
if (urlPath.substring(5, 13) === '/avatar/') {
|
||||||
|
const avatarId = urlPath.substring(13);
|
||||||
|
return avatarId;
|
||||||
|
}
|
||||||
|
// return void 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPlatformInfo(unityPackages) {
|
||||||
|
var pc = {};
|
||||||
|
var android = {};
|
||||||
|
var ios = {};
|
||||||
|
if (typeof unityPackages === 'object') {
|
||||||
|
for (var unityPackage of unityPackages) {
|
||||||
|
if (
|
||||||
|
unityPackage.variant &&
|
||||||
|
unityPackage.variant !== 'standard' &&
|
||||||
|
unityPackage.variant !== 'security'
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (unityPackage.platform === 'standalonewindows') {
|
||||||
|
if (
|
||||||
|
unityPackage.performanceRating === 'None' &&
|
||||||
|
pc.performanceRating
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
pc = unityPackage;
|
||||||
|
} else if (unityPackage.platform === 'android') {
|
||||||
|
if (
|
||||||
|
unityPackage.performanceRating === 'None' &&
|
||||||
|
android.performanceRating
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
android = unityPackage;
|
||||||
|
} else if (unityPackage.platform === 'ios') {
|
||||||
|
if (
|
||||||
|
unityPackage.performanceRating === 'None' &&
|
||||||
|
ios.performanceRating
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ios = unityPackage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { pc, android, ios };
|
||||||
|
}
|
||||||
|
|
||||||
|
function compareUnityVersion(unitySortNumber) {
|
||||||
|
if (!window.API.cachedConfig.sdkUnityVersion) {
|
||||||
|
console.error('No cachedConfig.sdkUnityVersion');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2022.3.6f1 2022 03 06 000
|
||||||
|
// 2019.4.31f1 2019 04 31 000
|
||||||
|
// 5.3.4p1 5 03 04 010
|
||||||
|
// 2019.4.31f1c1 is a thing
|
||||||
|
var array = window.API.cachedConfig.sdkUnityVersion.split('.');
|
||||||
|
if (array.length < 3) {
|
||||||
|
console.error('Invalid cachedConfig.sdkUnityVersion');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var currentUnityVersion = array[0];
|
||||||
|
currentUnityVersion += array[1].padStart(2, '0');
|
||||||
|
var indexFirstLetter = array[2].search(/[a-zA-Z]/);
|
||||||
|
if (indexFirstLetter > -1) {
|
||||||
|
currentUnityVersion += array[2]
|
||||||
|
.substr(0, indexFirstLetter)
|
||||||
|
.padStart(2, '0');
|
||||||
|
currentUnityVersion += '0';
|
||||||
|
var letter = array[2].substr(indexFirstLetter, 1);
|
||||||
|
if (letter === 'p') {
|
||||||
|
currentUnityVersion += '1';
|
||||||
|
} else {
|
||||||
|
// f
|
||||||
|
currentUnityVersion += '0';
|
||||||
|
}
|
||||||
|
currentUnityVersion += '0';
|
||||||
|
} else {
|
||||||
|
// just in case
|
||||||
|
currentUnityVersion += '000';
|
||||||
|
}
|
||||||
|
// just in case
|
||||||
|
currentUnityVersion = currentUnityVersion.replace(/\D/g, '');
|
||||||
|
|
||||||
|
if (parseInt(unitySortNumber, 10) <= parseInt(currentUnityVersion, 10)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
storeAvatarImage,
|
||||||
|
parseAvatarUrl,
|
||||||
|
getPlatformInfo,
|
||||||
|
compareUnityVersion
|
||||||
|
};
|
||||||
14
src/composables/group/utils.js
Normal file
14
src/composables/group/utils.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
function hasGroupPermission(ref, permission) {
|
||||||
|
if (
|
||||||
|
ref &&
|
||||||
|
ref.myMember &&
|
||||||
|
ref.myMember.permissions &&
|
||||||
|
(ref.myMember.permissions.includes('*') ||
|
||||||
|
ref.myMember.permissions.includes(permission))
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { hasGroupPermission };
|
||||||
168
src/composables/instance/utils.js
Normal file
168
src/composables/instance/utils.js
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
import { instanceRequest } from '../../api';
|
||||||
|
|
||||||
|
// TODO: launch, invite, refresh, etc. buttons, better to split into one component
|
||||||
|
function refreshInstancePlayerCount(instance) {
|
||||||
|
const L = parseLocation(instance);
|
||||||
|
if (L.isRealInstance) {
|
||||||
|
instanceRequest.getInstance({
|
||||||
|
worldId: L.worldId,
|
||||||
|
instanceId: L.instanceId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isRealInstance(instanceId) {
|
||||||
|
if (!instanceId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
switch (instanceId) {
|
||||||
|
case ':':
|
||||||
|
case 'offline':
|
||||||
|
case 'offline:offline':
|
||||||
|
case 'private':
|
||||||
|
case 'private:private':
|
||||||
|
case 'traveling':
|
||||||
|
case 'traveling:traveling':
|
||||||
|
case instanceId.startsWith('local'):
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayLocation(location, worldName, groupName) {
|
||||||
|
var text = worldName;
|
||||||
|
var L = parseLocation(location);
|
||||||
|
if (L.isOffline) {
|
||||||
|
text = 'Offline';
|
||||||
|
} else if (L.isPrivate) {
|
||||||
|
text = 'Private';
|
||||||
|
} else if (L.isTraveling) {
|
||||||
|
text = 'Traveling';
|
||||||
|
} else if (L.worldId) {
|
||||||
|
if (groupName) {
|
||||||
|
text = `${worldName} ${L.accessTypeName}(${groupName})`;
|
||||||
|
} else if (L.instanceId) {
|
||||||
|
text = `${worldName} ${L.accessTypeName}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseLocation(tag) {
|
||||||
|
var _tag = String(tag || '');
|
||||||
|
var ctx = {
|
||||||
|
tag: _tag,
|
||||||
|
isOffline: false,
|
||||||
|
isPrivate: false,
|
||||||
|
isTraveling: false,
|
||||||
|
isRealInstance: false,
|
||||||
|
worldId: '',
|
||||||
|
instanceId: '',
|
||||||
|
instanceName: '',
|
||||||
|
accessType: '',
|
||||||
|
accessTypeName: '',
|
||||||
|
region: '',
|
||||||
|
shortName: '',
|
||||||
|
userId: null,
|
||||||
|
hiddenId: null,
|
||||||
|
privateId: null,
|
||||||
|
friendsId: null,
|
||||||
|
groupId: null,
|
||||||
|
groupAccessType: null,
|
||||||
|
canRequestInvite: false,
|
||||||
|
strict: false,
|
||||||
|
ageGate: false
|
||||||
|
};
|
||||||
|
if (_tag === 'offline' || _tag === 'offline:offline') {
|
||||||
|
ctx.isOffline = true;
|
||||||
|
} else if (_tag === 'private' || _tag === 'private:private') {
|
||||||
|
ctx.isPrivate = true;
|
||||||
|
} else if (_tag === 'traveling' || _tag === 'traveling:traveling') {
|
||||||
|
ctx.isTraveling = true;
|
||||||
|
} else if (!_tag.startsWith('local')) {
|
||||||
|
ctx.isRealInstance = true;
|
||||||
|
var sep = _tag.indexOf(':');
|
||||||
|
// technically not part of instance id, but might be there when coping id from url so why not support it
|
||||||
|
var shortNameQualifier = '&shortName=';
|
||||||
|
var shortNameIndex = _tag.indexOf(shortNameQualifier);
|
||||||
|
if (shortNameIndex >= 0) {
|
||||||
|
ctx.shortName = _tag.substr(
|
||||||
|
shortNameIndex + shortNameQualifier.length
|
||||||
|
);
|
||||||
|
_tag = _tag.substr(0, shortNameIndex);
|
||||||
|
}
|
||||||
|
if (sep >= 0) {
|
||||||
|
ctx.worldId = _tag.substr(0, sep);
|
||||||
|
ctx.instanceId = _tag.substr(sep + 1);
|
||||||
|
ctx.instanceId.split('~').forEach((s, i) => {
|
||||||
|
if (i) {
|
||||||
|
var A = s.indexOf('(');
|
||||||
|
var Z = A >= 0 ? s.lastIndexOf(')') : -1;
|
||||||
|
var key = Z >= 0 ? s.substr(0, A) : s;
|
||||||
|
var value = A < Z ? s.substr(A + 1, Z - A - 1) : '';
|
||||||
|
if (key === 'hidden') {
|
||||||
|
ctx.hiddenId = value;
|
||||||
|
} else if (key === 'private') {
|
||||||
|
ctx.privateId = value;
|
||||||
|
} else if (key === 'friends') {
|
||||||
|
ctx.friendsId = value;
|
||||||
|
} else if (key === 'canRequestInvite') {
|
||||||
|
ctx.canRequestInvite = true;
|
||||||
|
} else if (key === 'region') {
|
||||||
|
ctx.region = value;
|
||||||
|
} else if (key === 'group') {
|
||||||
|
ctx.groupId = value;
|
||||||
|
} else if (key === 'groupAccessType') {
|
||||||
|
ctx.groupAccessType = value;
|
||||||
|
} else if (key === 'strict') {
|
||||||
|
ctx.strict = true;
|
||||||
|
} else if (key === 'ageGate') {
|
||||||
|
ctx.ageGate = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctx.instanceName = s;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ctx.accessType = 'public';
|
||||||
|
if (ctx.privateId !== null) {
|
||||||
|
if (ctx.canRequestInvite) {
|
||||||
|
// InvitePlus
|
||||||
|
ctx.accessType = 'invite+';
|
||||||
|
} else {
|
||||||
|
// InviteOnly
|
||||||
|
ctx.accessType = 'invite';
|
||||||
|
}
|
||||||
|
ctx.userId = ctx.privateId;
|
||||||
|
} else if (ctx.friendsId !== null) {
|
||||||
|
// FriendsOnly
|
||||||
|
ctx.accessType = 'friends';
|
||||||
|
ctx.userId = ctx.friendsId;
|
||||||
|
} else if (ctx.hiddenId !== null) {
|
||||||
|
// FriendsOfGuests
|
||||||
|
ctx.accessType = 'friends+';
|
||||||
|
ctx.userId = ctx.hiddenId;
|
||||||
|
} else if (ctx.groupId !== null) {
|
||||||
|
// Group
|
||||||
|
ctx.accessType = 'group';
|
||||||
|
}
|
||||||
|
ctx.accessTypeName = ctx.accessType;
|
||||||
|
if (ctx.groupAccessType !== null) {
|
||||||
|
if (ctx.groupAccessType === 'public') {
|
||||||
|
ctx.accessTypeName = 'groupPublic';
|
||||||
|
} else if (ctx.groupAccessType === 'plus') {
|
||||||
|
ctx.accessTypeName = 'groupPlus';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctx.worldId = _tag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
refreshInstancePlayerCount,
|
||||||
|
isRealInstance,
|
||||||
|
displayLocation,
|
||||||
|
parseLocation
|
||||||
|
};
|
||||||
@@ -1,19 +1,3 @@
|
|||||||
function getVRChatResolution(res) {
|
|
||||||
switch (res) {
|
|
||||||
case '1280x720':
|
|
||||||
return '1280x720 (720p)';
|
|
||||||
case '1920x1080':
|
|
||||||
return '1920x1080 (1080p)';
|
|
||||||
case '2560x1440':
|
|
||||||
return '2560x1440 (2K)';
|
|
||||||
case '3840x2160':
|
|
||||||
return '3840x2160 (4K)';
|
|
||||||
case '7680x4320':
|
|
||||||
return '7680x4320 (8K)';
|
|
||||||
}
|
|
||||||
return `${res} (Custom)`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const VRChatScreenshotResolutions = [
|
const VRChatScreenshotResolutions = [
|
||||||
{ name: '1280x720 (720p)', width: 1280, height: 720 },
|
{ name: '1280x720 (720p)', width: 1280, height: 720 },
|
||||||
{ name: '1920x1080 (1080p Default)', width: '', height: '' },
|
{ name: '1920x1080 (1080p Default)', width: '', height: '' },
|
||||||
@@ -29,8 +13,4 @@ const VRChatCameraResolutions = [
|
|||||||
{ name: '7680x4320 (8K)', width: 7680, height: 4320 }
|
{ name: '7680x4320 (8K)', width: 7680, height: 4320 }
|
||||||
];
|
];
|
||||||
|
|
||||||
export {
|
export { VRChatScreenshotResolutions, VRChatCameraResolutions };
|
||||||
getVRChatResolution,
|
|
||||||
VRChatScreenshotResolutions,
|
|
||||||
VRChatCameraResolutions
|
|
||||||
};
|
|
||||||
17
src/composables/setting/utils.js
Normal file
17
src/composables/setting/utils.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
function getVRChatResolution(res) {
|
||||||
|
switch (res) {
|
||||||
|
case '1280x720':
|
||||||
|
return '1280x720 (720p)';
|
||||||
|
case '1920x1080':
|
||||||
|
return '1920x1080 (1080p)';
|
||||||
|
case '2560x1440':
|
||||||
|
return '2560x1440 (2K)';
|
||||||
|
case '3840x2160':
|
||||||
|
return '3840x2160 (4K)';
|
||||||
|
case '7680x4320':
|
||||||
|
return '7680x4320 (8K)';
|
||||||
|
}
|
||||||
|
return `${res} (Custom)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { getVRChatResolution };
|
||||||
106
src/composables/shared/constants/photon.js
Normal file
106
src/composables/shared/constants/photon.js
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
const photonEmojis = [
|
||||||
|
'Angry',
|
||||||
|
'Blushing',
|
||||||
|
'Crying',
|
||||||
|
'Frown',
|
||||||
|
'Hand Wave',
|
||||||
|
'Hang Ten',
|
||||||
|
'In Love',
|
||||||
|
'Jack O Lantern',
|
||||||
|
'Kiss',
|
||||||
|
'Laugh',
|
||||||
|
'Skull',
|
||||||
|
'Smile',
|
||||||
|
'Spooky Ghost',
|
||||||
|
'Stoic',
|
||||||
|
'Sunglasses',
|
||||||
|
'Thinking',
|
||||||
|
'Thumbs Down',
|
||||||
|
'Thumbs Up',
|
||||||
|
'Tongue Out',
|
||||||
|
'Wow',
|
||||||
|
'Arrow Point',
|
||||||
|
"Can't see",
|
||||||
|
'Hourglass',
|
||||||
|
'Keyboard',
|
||||||
|
'No Headphones',
|
||||||
|
'No Mic',
|
||||||
|
'Portal',
|
||||||
|
'Shush',
|
||||||
|
'Bats',
|
||||||
|
'Cloud',
|
||||||
|
'Fire',
|
||||||
|
'Snow Fall',
|
||||||
|
'Snowball',
|
||||||
|
'Splash',
|
||||||
|
'Web',
|
||||||
|
'Beer',
|
||||||
|
'Candy',
|
||||||
|
'Candy Cane',
|
||||||
|
'Candy Corn',
|
||||||
|
'Champagne',
|
||||||
|
'Drink',
|
||||||
|
'Gingerbread',
|
||||||
|
'Ice Cream',
|
||||||
|
'Pineapple',
|
||||||
|
'Pizza',
|
||||||
|
'Tomato',
|
||||||
|
'Beachball',
|
||||||
|
'Coal',
|
||||||
|
'Confetti',
|
||||||
|
'Gift',
|
||||||
|
'Gifts',
|
||||||
|
'Life Ring',
|
||||||
|
'Mistletoe',
|
||||||
|
'Money',
|
||||||
|
'Neon Shades',
|
||||||
|
'Sun Lotion',
|
||||||
|
'Boo',
|
||||||
|
'Broken Heart',
|
||||||
|
'Exclamation',
|
||||||
|
'Go',
|
||||||
|
'Heart',
|
||||||
|
'Music Note',
|
||||||
|
'Question',
|
||||||
|
'Stop',
|
||||||
|
'Zzz'
|
||||||
|
];
|
||||||
|
|
||||||
|
const photonEventType = [
|
||||||
|
'MeshVisibility',
|
||||||
|
'AnimationFloat',
|
||||||
|
'AnimationBool',
|
||||||
|
'AnimationTrigger',
|
||||||
|
'AudioTrigger',
|
||||||
|
'PlayAnimation',
|
||||||
|
'SendMessage',
|
||||||
|
'SetParticlePlaying',
|
||||||
|
'TeleportPlayer',
|
||||||
|
'RunConsoleCommand',
|
||||||
|
'SetGameObjectActive',
|
||||||
|
'SetWebPanelURI',
|
||||||
|
'SetWebPanelVolume',
|
||||||
|
'SpawnObject',
|
||||||
|
'SendRPC',
|
||||||
|
'ActivateCustomTrigger',
|
||||||
|
'DestroyObject',
|
||||||
|
'SetLayer',
|
||||||
|
'SetMaterial',
|
||||||
|
'AddHealth',
|
||||||
|
'AddDamage',
|
||||||
|
'SetComponentActive',
|
||||||
|
'AnimationInt',
|
||||||
|
'AnimationIntAdd',
|
||||||
|
'AnimationIntSubtract',
|
||||||
|
'AnimationIntMultiply',
|
||||||
|
'AnimationIntDivide',
|
||||||
|
'AddVelocity',
|
||||||
|
'SetVelocity',
|
||||||
|
'AddAngularVelocity',
|
||||||
|
'SetAngularVelocity',
|
||||||
|
'AddForce',
|
||||||
|
'SetUIText',
|
||||||
|
'CallUdonMethod'
|
||||||
|
];
|
||||||
|
|
||||||
|
export { photonEmojis, photonEventType };
|
||||||
318
src/composables/shared/utils.js
Normal file
318
src/composables/shared/utils.js
Normal file
@@ -0,0 +1,318 @@
|
|||||||
|
import Noty from 'noty';
|
||||||
|
import utils from '../../classes/utils';
|
||||||
|
import { compareUnityVersion } from '../avatar/utils';
|
||||||
|
|
||||||
|
function getAvailablePlatforms(unityPackages) {
|
||||||
|
var isPC = false;
|
||||||
|
var isQuest = false;
|
||||||
|
var isIos = false;
|
||||||
|
if (typeof unityPackages === 'object') {
|
||||||
|
for (var unityPackage of unityPackages) {
|
||||||
|
if (
|
||||||
|
unityPackage.variant &&
|
||||||
|
unityPackage.variant !== 'standard' &&
|
||||||
|
unityPackage.variant !== 'security'
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (unityPackage.platform === 'standalonewindows') {
|
||||||
|
isPC = true;
|
||||||
|
} else if (unityPackage.platform === 'android') {
|
||||||
|
isQuest = true;
|
||||||
|
} else if (unityPackage.platform === 'ios') {
|
||||||
|
isIos = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { isPC, isQuest, isIos };
|
||||||
|
}
|
||||||
|
|
||||||
|
function downloadAndSaveJson(fileName, data) {
|
||||||
|
if (!fileName || !data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
var link = document.createElement('a');
|
||||||
|
link.setAttribute(
|
||||||
|
'href',
|
||||||
|
`data:application/json;charset=utf-8,${encodeURIComponent(
|
||||||
|
JSON.stringify(data, null, 2)
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
link.setAttribute('download', `${fileName}.json`);
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
} catch {
|
||||||
|
new Noty({
|
||||||
|
type: 'error',
|
||||||
|
text: utils.escapeTag('Failed to download JSON.')
|
||||||
|
}).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteVRChatCache(ref) {
|
||||||
|
var assetUrl = '';
|
||||||
|
var variant = '';
|
||||||
|
for (var i = ref.unityPackages.length - 1; i > -1; i--) {
|
||||||
|
var unityPackage = ref.unityPackages[i];
|
||||||
|
if (
|
||||||
|
unityPackage.variant &&
|
||||||
|
unityPackage.variant !== 'standard' &&
|
||||||
|
unityPackage.variant !== 'security'
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
unityPackage.platform === 'standalonewindows' &&
|
||||||
|
compareUnityVersion(unityPackage.unitySortNumber)
|
||||||
|
) {
|
||||||
|
assetUrl = unityPackage.assetUrl;
|
||||||
|
if (unityPackage.variant !== 'standard') {
|
||||||
|
variant = unityPackage.variant;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var id = extractFileId(assetUrl);
|
||||||
|
var version = parseInt(extractFileVersion(assetUrl), 10);
|
||||||
|
var variantVersion = parseInt(extractVariantVersion(assetUrl), 10);
|
||||||
|
await AssetBundleManager.DeleteCache(id, version, variant, variantVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkVRChatCache(ref) {
|
||||||
|
if (!ref.unityPackages) {
|
||||||
|
return { Item1: -1, Item2: false, Item3: '' };
|
||||||
|
}
|
||||||
|
var assetUrl = '';
|
||||||
|
var variant = '';
|
||||||
|
for (var i = ref.unityPackages.length - 1; i > -1; i--) {
|
||||||
|
var unityPackage = ref.unityPackages[i];
|
||||||
|
if (unityPackage.variant && unityPackage.variant !== 'security') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
unityPackage.platform === 'standalonewindows' &&
|
||||||
|
compareUnityVersion(unityPackage.unitySortNumber)
|
||||||
|
) {
|
||||||
|
assetUrl = unityPackage.assetUrl;
|
||||||
|
if (unityPackage.variant !== 'standard') {
|
||||||
|
variant = unityPackage.variant;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!assetUrl) {
|
||||||
|
assetUrl = ref.assetUrl;
|
||||||
|
}
|
||||||
|
var id = extractFileId(assetUrl);
|
||||||
|
var version = parseInt(extractFileVersion(assetUrl), 10);
|
||||||
|
var variantVersion = parseInt(extractVariantVersion(assetUrl), 10);
|
||||||
|
if (!id || !version) {
|
||||||
|
return { Item1: -1, Item2: false, Item3: '' };
|
||||||
|
}
|
||||||
|
|
||||||
|
return AssetBundleManager.CheckVRChatCache(
|
||||||
|
id,
|
||||||
|
version,
|
||||||
|
variant,
|
||||||
|
variantVersion
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyToClipboard(text, message = 'Copied successfully!') {
|
||||||
|
navigator.clipboard
|
||||||
|
.writeText(text)
|
||||||
|
.then(() => {
|
||||||
|
window.$app.$message({
|
||||||
|
message: message,
|
||||||
|
type: 'success'
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error('Copy failed:', err);
|
||||||
|
window.$app.$message.error('Copy failed!');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFaviconUrl(resource) {
|
||||||
|
try {
|
||||||
|
const url = new URL(resource);
|
||||||
|
return `https://icons.duckduckgo.com/ip2/${url.host}.ico`;
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Invalid URL:', err);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertFileUrlToImageUrl(url, resolution = 128) {
|
||||||
|
if (!url) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* possible patterns?
|
||||||
|
* /file/file_fileId/version
|
||||||
|
* /file/file_fileId/version/
|
||||||
|
* /file/file_fileId/version/file
|
||||||
|
* /file/file_fileId/version/file/
|
||||||
|
*/
|
||||||
|
const pattern = /file\/file_([a-f0-9-]+)\/(\d+)(\/file)?\/?$/;
|
||||||
|
const match = url.match(pattern);
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
const fileId = match[1];
|
||||||
|
const version = match[2];
|
||||||
|
return `https://api.vrchat.cloud/api/1/image/file_${fileId}/${version}/${resolution}`;
|
||||||
|
}
|
||||||
|
// no match return origin url
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceVrcPackageUrl(url) {
|
||||||
|
if (!url) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return url.replace('https://api.vrchat.cloud/', 'https://vrchat.com/');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLaunchURL(instance) {
|
||||||
|
var L = instance;
|
||||||
|
if (L.instanceId) {
|
||||||
|
if (L.shortName) {
|
||||||
|
return `https://vrchat.com/home/launch?worldId=${encodeURIComponent(
|
||||||
|
L.worldId
|
||||||
|
)}&instanceId=${encodeURIComponent(
|
||||||
|
L.instanceId
|
||||||
|
)}&shortName=${encodeURIComponent(L.shortName)}`;
|
||||||
|
}
|
||||||
|
return `https://vrchat.com/home/launch?worldId=${encodeURIComponent(
|
||||||
|
L.worldId
|
||||||
|
)}&instanceId=${encodeURIComponent(L.instanceId)}`;
|
||||||
|
}
|
||||||
|
return `https://vrchat.com/home/launch?worldId=${encodeURIComponent(
|
||||||
|
L.worldId
|
||||||
|
)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractFileId(s) {
|
||||||
|
var match = String(s).match(/file_[0-9A-Za-z-]+/);
|
||||||
|
return match ? match[0] : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractFileVersion(s) {
|
||||||
|
var match = /(?:\/file_[0-9A-Za-z-]+\/)([0-9]+)/gi.exec(s);
|
||||||
|
return match ? match[1] : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractVariantVersion(url) {
|
||||||
|
if (!url) {
|
||||||
|
return '0';
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const params = new URLSearchParams(new URL(url).search);
|
||||||
|
const version = params.get('v');
|
||||||
|
if (version) {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
return '0';
|
||||||
|
} catch {
|
||||||
|
return '0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
getAvailablePlatforms,
|
||||||
|
downloadAndSaveJson,
|
||||||
|
deleteVRChatCache,
|
||||||
|
checkVRChatCache,
|
||||||
|
copyToClipboard,
|
||||||
|
getFaviconUrl,
|
||||||
|
convertFileUrlToImageUrl,
|
||||||
|
replaceVrcPackageUrl,
|
||||||
|
getLaunchURL,
|
||||||
|
extractFileId,
|
||||||
|
extractFileVersion,
|
||||||
|
extractVariantVersion
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------- devtools method --------------------------
|
||||||
|
|
||||||
|
// not window.$app
|
||||||
|
window.getBundleLocation = async function (input) {
|
||||||
|
const $app = window.$app;
|
||||||
|
var assetUrl = input;
|
||||||
|
var variant = '';
|
||||||
|
if (assetUrl) {
|
||||||
|
// continue
|
||||||
|
} else if (
|
||||||
|
$app.avatarDialog.visible &&
|
||||||
|
$app.avatarDialog.ref.unityPackages.length > 0
|
||||||
|
) {
|
||||||
|
var unityPackages = $app.avatarDialog.ref.unityPackages;
|
||||||
|
for (let i = unityPackages.length - 1; i > -1; i--) {
|
||||||
|
var unityPackage = unityPackages[i];
|
||||||
|
if (
|
||||||
|
unityPackage.variant &&
|
||||||
|
unityPackage.variant !== 'standard' &&
|
||||||
|
unityPackage.variant !== 'security'
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
unityPackage.platform === 'standalonewindows' &&
|
||||||
|
compareUnityVersion(unityPackage.unitySortNumber)
|
||||||
|
) {
|
||||||
|
assetUrl = unityPackage.assetUrl;
|
||||||
|
if (unityPackage.variant !== 'standard') {
|
||||||
|
variant = unityPackage.variant;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if ($app.avatarDialog.visible && $app.avatarDialog.ref.assetUrl) {
|
||||||
|
assetUrl = $app.avatarDialog.ref.assetUrl;
|
||||||
|
} else if (
|
||||||
|
$app.worldDialog.visible &&
|
||||||
|
$app.worldDialog.ref.unityPackages.length > 0
|
||||||
|
) {
|
||||||
|
var unityPackages = $app.worldDialog.ref.unityPackages;
|
||||||
|
for (let i = unityPackages.length - 1; i > -1; i--) {
|
||||||
|
var unityPackage = unityPackages[i];
|
||||||
|
if (
|
||||||
|
unityPackage.platform === 'standalonewindows' &&
|
||||||
|
compareUnityVersion(unityPackage.unitySortNumber)
|
||||||
|
) {
|
||||||
|
assetUrl = unityPackage.assetUrl;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if ($app.worldDialog.visible && $app.worldDialog.ref.assetUrl) {
|
||||||
|
assetUrl = $app.worldDialog.ref.assetUrl;
|
||||||
|
}
|
||||||
|
if (!assetUrl) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var fileId = extractFileId(assetUrl);
|
||||||
|
var fileVersion = parseInt(extractFileVersion(assetUrl), 10);
|
||||||
|
var variantVersion = parseInt(extractVariantVersion(assetUrl), 10);
|
||||||
|
var assetLocation = await AssetBundleManager.GetVRChatCacheFullLocation(
|
||||||
|
fileId,
|
||||||
|
fileVersion,
|
||||||
|
variant,
|
||||||
|
variantVersion
|
||||||
|
);
|
||||||
|
var cacheInfo = await AssetBundleManager.CheckVRChatCache(
|
||||||
|
fileId,
|
||||||
|
fileVersion,
|
||||||
|
variant,
|
||||||
|
variantVersion
|
||||||
|
);
|
||||||
|
var inCache = false;
|
||||||
|
if (cacheInfo.Item1 > 0) {
|
||||||
|
inCache = true;
|
||||||
|
}
|
||||||
|
console.log(`InCache: ${inCache}`);
|
||||||
|
var fullAssetLocation = `${assetLocation}\\__data`;
|
||||||
|
console.log(fullAssetLocation);
|
||||||
|
return fullAssetLocation;
|
||||||
|
};
|
||||||
34
src/composables/user/constants/emoji.js
Normal file
34
src/composables/user/constants/emoji.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
const emojiAnimationStyleUrl =
|
||||||
|
'https://assets.vrchat.com/www/images/emoji-previews/';
|
||||||
|
|
||||||
|
const emojiAnimationStyleList = {
|
||||||
|
Aura: 'Preview_B2-Aura.gif',
|
||||||
|
Bats: 'Preview_B2-Fall_Bats.gif',
|
||||||
|
Bees: 'Preview_B2-Bees.gif',
|
||||||
|
Bounce: 'Preview_B2-Bounce.gif',
|
||||||
|
Cloud: 'Preview_B2-Cloud.gif',
|
||||||
|
Confetti: 'Preview_B2-Winter_Confetti.gif',
|
||||||
|
Crying: 'Preview_B2-Crying.gif',
|
||||||
|
Dislike: 'Preview_B2-Dislike.gif',
|
||||||
|
Fire: 'Preview_B2-Fire.gif',
|
||||||
|
Idea: 'Preview_B2-Idea.gif',
|
||||||
|
Lasers: 'Preview_B2-Lasers.gif',
|
||||||
|
Like: 'Preview_B2-Like.gif',
|
||||||
|
Magnet: 'Preview_B2-Magnet.gif',
|
||||||
|
Mistletoe: 'Preview_B2-Winter_Mistletoe.gif',
|
||||||
|
Money: 'Preview_B2-Money.gif',
|
||||||
|
Noise: 'Preview_B2-Noise.gif',
|
||||||
|
Orbit: 'Preview_B2-Orbit.gif',
|
||||||
|
Pizza: 'Preview_B2-Pizza.gif',
|
||||||
|
Rain: 'Preview_B2-Rain.gif',
|
||||||
|
Rotate: 'Preview_B2-Rotate.gif',
|
||||||
|
Shake: 'Preview_B2-Shake.gif',
|
||||||
|
Snow: 'Preview_B2-Spin.gif',
|
||||||
|
Snowball: 'Preview_B2-Winter_Snowball.gif',
|
||||||
|
Spin: 'Preview_B2-Spin.gif',
|
||||||
|
Splash: 'Preview_B2-SummerSplash.gif',
|
||||||
|
Stop: 'Preview_B2-Stop.gif',
|
||||||
|
ZZZ: 'Preview_B2-ZZZ.gif'
|
||||||
|
};
|
||||||
|
|
||||||
|
export { emojiAnimationStyleUrl, emojiAnimationStyleList };
|
||||||
73
src/composables/user/constants/language.js
Normal file
73
src/composables/user/constants/language.js
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
// vrchat to famfamfam language mappings
|
||||||
|
const languageMappings = {
|
||||||
|
eng: 'us',
|
||||||
|
kor: 'kr',
|
||||||
|
rus: 'ru',
|
||||||
|
spa: 'es',
|
||||||
|
por: 'pt',
|
||||||
|
zho: 'cn',
|
||||||
|
deu: 'de',
|
||||||
|
jpn: 'jp',
|
||||||
|
fra: 'fr',
|
||||||
|
swe: 'se',
|
||||||
|
nld: 'nl',
|
||||||
|
pol: 'pl',
|
||||||
|
dan: 'dk',
|
||||||
|
nor: 'no',
|
||||||
|
ita: 'it',
|
||||||
|
tha: 'th',
|
||||||
|
fin: 'fi',
|
||||||
|
hun: 'hu',
|
||||||
|
ces: 'cz',
|
||||||
|
tur: 'tr',
|
||||||
|
ara: 'ae',
|
||||||
|
ron: 'ro',
|
||||||
|
vie: 'vn',
|
||||||
|
ukr: 'ua',
|
||||||
|
ase: 'us',
|
||||||
|
bfi: 'gb',
|
||||||
|
dse: 'nl',
|
||||||
|
fsl: 'fr',
|
||||||
|
jsl: 'jp',
|
||||||
|
kvk: 'kr',
|
||||||
|
|
||||||
|
mlt: 'mt',
|
||||||
|
ind: 'id',
|
||||||
|
hrv: 'hr',
|
||||||
|
heb: 'he',
|
||||||
|
afr: 'af',
|
||||||
|
ben: 'be',
|
||||||
|
bul: 'bg',
|
||||||
|
cmn: 'cn',
|
||||||
|
cym: 'cy',
|
||||||
|
ell: 'el',
|
||||||
|
est: 'et',
|
||||||
|
fil: 'ph',
|
||||||
|
gla: 'gd',
|
||||||
|
gle: 'ga',
|
||||||
|
hin: 'hi',
|
||||||
|
hmn: 'cn',
|
||||||
|
hye: 'hy',
|
||||||
|
isl: 'is',
|
||||||
|
lav: 'lv',
|
||||||
|
lit: 'lt',
|
||||||
|
ltz: 'lb',
|
||||||
|
mar: 'hi',
|
||||||
|
mkd: 'mk',
|
||||||
|
msa: 'my',
|
||||||
|
sco: 'gd',
|
||||||
|
slk: 'sk',
|
||||||
|
slv: 'sl',
|
||||||
|
tel: 'hi',
|
||||||
|
mri: 'nz',
|
||||||
|
wuu: 'cn',
|
||||||
|
yue: 'cn',
|
||||||
|
tws: 'cn',
|
||||||
|
asf: 'au',
|
||||||
|
nzs: 'nz',
|
||||||
|
gsg: 'de',
|
||||||
|
epo: 'eo',
|
||||||
|
tok: 'tok'
|
||||||
|
};
|
||||||
|
|
||||||
|
export { languageMappings };
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
const userDialogGroupSortingOptions = {
|
||||||
|
alphabetical: {
|
||||||
|
name: 'dialog.user.groups.sorting.alphabetical',
|
||||||
|
value: 'alphabetical'
|
||||||
|
},
|
||||||
|
members: {
|
||||||
|
name: 'dialog.user.groups.sorting.members',
|
||||||
|
value: 'members'
|
||||||
|
},
|
||||||
|
inGame: {
|
||||||
|
name: 'dialog.user.groups.sorting.in_game',
|
||||||
|
value: 'inGame'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export { userDialogGroupSortingOptions };
|
||||||
79
src/composables/user/utils.js
Normal file
79
src/composables/user/utils.js
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import { languageMappings } from './constants/language';
|
||||||
|
|
||||||
|
function userOnlineForTimestamp(ctx) {
|
||||||
|
if (ctx.ref.state === 'online' && ctx.ref.$online_for) {
|
||||||
|
return ctx.ref.$online_for;
|
||||||
|
} else if (ctx.ref.state === 'active' && ctx.ref.$active_for) {
|
||||||
|
return ctx.ref.$active_for;
|
||||||
|
} else if (ctx.ref.$offline_for) {
|
||||||
|
return ctx.ref.$offline_for;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function languageClass(language) {
|
||||||
|
const style = {};
|
||||||
|
const mapping = languageMappings[language];
|
||||||
|
if (typeof mapping !== 'undefined') {
|
||||||
|
style[mapping] = true;
|
||||||
|
} else {
|
||||||
|
style.unknown = true;
|
||||||
|
}
|
||||||
|
return style;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPrintFileName(print) {
|
||||||
|
const authorName = print.authorName;
|
||||||
|
// fileDate format: 2024-11-03_16-14-25.757
|
||||||
|
const createdAt = getPrintLocalDate(print);
|
||||||
|
const fileNameDate = createdAt
|
||||||
|
.toISOString()
|
||||||
|
.replace(/:/g, '-')
|
||||||
|
.replace(/T/g, '_')
|
||||||
|
.replace(/Z/g, '');
|
||||||
|
const fileName = `${authorName}_${fileNameDate}_${print.id}.png`;
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPrintLocalDate(print) {
|
||||||
|
if (print.createdAt) {
|
||||||
|
const createdAt = new Date(print.createdAt);
|
||||||
|
// cursed convert to local time
|
||||||
|
createdAt.setMinutes(
|
||||||
|
createdAt.getMinutes() - createdAt.getTimezoneOffset()
|
||||||
|
);
|
||||||
|
return createdAt;
|
||||||
|
}
|
||||||
|
if (print.timestamp) {
|
||||||
|
return new Date(print.timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
const createdAt = new Date();
|
||||||
|
// cursed convert to local time
|
||||||
|
createdAt.setMinutes(
|
||||||
|
createdAt.getMinutes() - createdAt.getTimezoneOffset()
|
||||||
|
);
|
||||||
|
return createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isFriendOnline(friend) {
|
||||||
|
if (typeof friend === 'undefined' || typeof friend.ref === 'undefined') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (friend.state === 'online') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (friend.state !== 'online' && friend.ref.location !== 'private') {
|
||||||
|
// wat
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
userOnlineForTimestamp,
|
||||||
|
languageClass,
|
||||||
|
getPrintFileName,
|
||||||
|
getPrintLocalDate,
|
||||||
|
isFriendOnline
|
||||||
|
};
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
mixin boops
|
|
||||||
el-dialog.x-dialog(
|
|
||||||
:before-close='beforeDialogClose'
|
|
||||||
@mousedown.native='dialogMouseDown'
|
|
||||||
@mouseup.native='dialogMouseUp'
|
|
||||||
ref='sendBoopDialog'
|
|
||||||
:visible.sync='sendBoopDialog.visible'
|
|
||||||
:title='$t("dialog.boop_dialog.header")'
|
|
||||||
width='450px')
|
|
||||||
div(v-if='sendBoopDialog.visible')
|
|
||||||
el-select(
|
|
||||||
v-model='sendBoopDialog.userId'
|
|
||||||
:placeholder='$t("dialog.new_instance.instance_creator_placeholder")'
|
|
||||||
filterable
|
|
||||||
style='width: 100%')
|
|
||||||
el-option-group(v-if='vipFriends.length' :label='$t("side_panel.favorite")')
|
|
||||||
el-option.x-friend-item(
|
|
||||||
v-for='friend in vipFriends'
|
|
||||||
:key='friend.id'
|
|
||||||
:label='friend.name'
|
|
||||||
:value='friend.id'
|
|
||||||
style='height: auto')
|
|
||||||
template(v-if='friend.ref')
|
|
||||||
.avatar(:class='userStatusClass(friend.ref)')
|
|
||||||
img(v-lazy='userImage(friend.ref)')
|
|
||||||
.detail
|
|
||||||
span.name(v-text='friend.ref.displayName' :style='{ color: friend.ref.$userColour }')
|
|
||||||
span(v-else v-text='friend.id')
|
|
||||||
el-option-group(v-if='onlineFriends.length' :label='$t("side_panel.online")')
|
|
||||||
el-option.x-friend-item(
|
|
||||||
v-for='friend in onlineFriends'
|
|
||||||
:key='friend.id'
|
|
||||||
:label='friend.name'
|
|
||||||
:value='friend.id'
|
|
||||||
style='height: auto')
|
|
||||||
template(v-if='friend.ref')
|
|
||||||
.avatar(:class='userStatusClass(friend.ref)')
|
|
||||||
img(v-lazy='userImage(friend.ref)')
|
|
||||||
.detail
|
|
||||||
span.name(v-text='friend.ref.displayName' :style='{ color: friend.ref.$userColour }')
|
|
||||||
span(v-else v-text='friend.id')
|
|
||||||
el-option-group(v-if='activeFriends.length' :label='$t("side_panel.active")')
|
|
||||||
el-option.x-friend-item(
|
|
||||||
v-for='friend in activeFriends'
|
|
||||||
:key='friend.id'
|
|
||||||
:label='friend.name'
|
|
||||||
:value='friend.id'
|
|
||||||
style='height: auto')
|
|
||||||
template(v-if='friend.ref')
|
|
||||||
.avatar
|
|
||||||
img(v-lazy='userImage(friend.ref)')
|
|
||||||
.detail
|
|
||||||
span.name(v-text='friend.ref.displayName' :style='{ color: friend.ref.$userColour }')
|
|
||||||
span(v-else v-text='friend.id')
|
|
||||||
el-option-group(v-if='offlineFriends.length' :label='$t("side_panel.offline")')
|
|
||||||
el-option.x-friend-item(
|
|
||||||
v-for='friend in offlineFriends'
|
|
||||||
:key='friend.id'
|
|
||||||
:label='friend.name'
|
|
||||||
:value='friend.id'
|
|
||||||
style='height: auto')
|
|
||||||
template(v-if='friend.ref')
|
|
||||||
.avatar
|
|
||||||
img(v-lazy='userImage(friend.ref)')
|
|
||||||
.detail
|
|
||||||
span.name(v-text='friend.ref.displayName' :style='{ color: friend.ref.$userColour }')
|
|
||||||
span(v-else v-text='friend.id')
|
|
||||||
br
|
|
||||||
br
|
|
||||||
el-select(
|
|
||||||
v-model='sendBoopDialog.fileId'
|
|
||||||
clearable
|
|
||||||
:placeholder='$t("dialog.boop_dialog.select_emoji")'
|
|
||||||
size='small'
|
|
||||||
style='width: 100%'
|
|
||||||
popper-class='max-height-el-select')
|
|
||||||
el-option-group(:label='$t("dialog.boop_dialog.my_emojis")')
|
|
||||||
el-option(
|
|
||||||
v-if='image.versions && image.versions.length > 0'
|
|
||||||
v-for='image in emojiTable'
|
|
||||||
:key='image.id'
|
|
||||||
:value='image.id'
|
|
||||||
style='width: 100%; height: 100%')
|
|
||||||
.vrcplus-icon(
|
|
||||||
v-if='image.versions[image.versions.length - 1].file.url'
|
|
||||||
style='overflow: hidden; width: 200px; height: 200px; padding: 10px')
|
|
||||||
template(v-if='image.frames')
|
|
||||||
.avatar(
|
|
||||||
:style='generateEmojiStyle(image.versions[image.versions.length - 1].file.url, image.framesOverTime, image.frames, image.loopStyle)')
|
|
||||||
template(v-else)
|
|
||||||
img.avatar(
|
|
||||||
v-lazy='image.versions[image.versions.length - 1].file.url'
|
|
||||||
style='width: 200px; height: 200px')
|
|
||||||
el-option-group(:label='$t("dialog.boop_dialog.default_emojis")')
|
|
||||||
el-option(
|
|
||||||
v-for='emojiName in photonEmojis'
|
|
||||||
:key='emojiName'
|
|
||||||
:value='getEmojiValue(emojiName)'
|
|
||||||
style='width: 100%; height: 100%')
|
|
||||||
span(v-text='emojiName')
|
|
||||||
template(#footer)
|
|
||||||
el-button(size='small' @click='showGalleryDialog(2)') {{ $t('dialog.boop_dialog.emoji_manager') }}
|
|
||||||
el-button(size='small' @click='sendBoopDialog.visible = false') {{ $t('dialog.boop_dialog.cancel') }}
|
|
||||||
el-button(size='small' @click='sendBoop' :disabled='!sendBoopDialog.userId') {{ $t('dialog.boop_dialog.send') }}
|
|
||||||
@@ -1,444 +0,0 @@
|
|||||||
mixin currentUser
|
|
||||||
//- dialog: social status
|
|
||||||
el-dialog.x-dialog(
|
|
||||||
:before-close='beforeDialogClose'
|
|
||||||
@mousedown.native='dialogMouseDown'
|
|
||||||
@mouseup.native='dialogMouseUp'
|
|
||||||
ref='socialStatusDialog'
|
|
||||||
:visible.sync='socialStatusDialog.visible'
|
|
||||||
:title='$t("dialog.social_status.header")'
|
|
||||||
width='400px')
|
|
||||||
div(v-loading='socialStatusDialog.loading')
|
|
||||||
el-collapse(style='border: 0')
|
|
||||||
el-collapse-item
|
|
||||||
template(slot='title')
|
|
||||||
span(style='font-size: 16px') {{ $t('dialog.social_status.history') }}
|
|
||||||
data-tables(
|
|
||||||
v-bind='socialStatusHistoryTable'
|
|
||||||
@row-click='setSocialStatusFromHistory'
|
|
||||||
style='cursor: pointer')
|
|
||||||
el-table-column(:label='$t("table.social_status.no")' prop='no' width='50')
|
|
||||||
el-table-column(:label='$t("table.social_status.status")' prop='status')
|
|
||||||
el-select(v-model='socialStatusDialog.status' style='display: block; margin-top: 10px')
|
|
||||||
el-option(:label='$t("dialog.user.status.join_me")' value='join me').
|
|
||||||
#[i.x-user-status.joinme] {{ $t('dialog.user.status.join_me') }}
|
|
||||||
el-option(:label='$t("dialog.user.status.online")' value='active').
|
|
||||||
#[i.x-user-status.online] {{ $t('dialog.user.status.online') }}
|
|
||||||
el-option(:label='$t("dialog.user.status.ask_me")' value='ask me').
|
|
||||||
#[i.x-user-status.askme] {{ $t('dialog.user.status.ask_me') }}
|
|
||||||
el-option(:label='$t("dialog.user.status.busy")' value='busy').
|
|
||||||
#[i.x-user-status.busy] {{ $t('dialog.user.status.busy') }}
|
|
||||||
el-option(
|
|
||||||
v-if='API.currentUser.$isModerator'
|
|
||||||
:label='$t("dialog.user.status.offline")'
|
|
||||||
value='offline').
|
|
||||||
#[i.x-user-status.offline] {{ $t('dialog.user.status.offline') }}
|
|
||||||
el-input(
|
|
||||||
v-model='socialStatusDialog.statusDescription'
|
|
||||||
:placeholder='$t("dialog.social_status.status_placeholder")'
|
|
||||||
maxlength='32'
|
|
||||||
show-word-limit
|
|
||||||
style='display: block; margin-top: 10px')
|
|
||||||
template(#footer)
|
|
||||||
el-button(type='primary' size='small' :disabled='socialStatusDialog.loading' @click='saveSocialStatus') {{ $t('dialog.social_status.update') }}
|
|
||||||
|
|
||||||
//- dialog: language
|
|
||||||
el-dialog.x-dialog(
|
|
||||||
:before-close='beforeDialogClose'
|
|
||||||
@mousedown.native='dialogMouseDown'
|
|
||||||
@mouseup.native='dialogMouseUp'
|
|
||||||
ref='languageDialog'
|
|
||||||
:visible.sync='languageDialog.visible'
|
|
||||||
:title='$t("dialog.language.header")'
|
|
||||||
width='400px')
|
|
||||||
div(v-loading='languageDialog.loading')
|
|
||||||
div(style='margin: 6px 0' v-for='item in API.currentUser.$languages' :key='item.key')
|
|
||||||
el-tag(
|
|
||||||
size='small'
|
|
||||||
type='info'
|
|
||||||
effect='plain'
|
|
||||||
closable
|
|
||||||
@close='removeUserLanguage(item.key)'
|
|
||||||
style='margin-right: 5px')
|
|
||||||
span.flags(:class='languageClass(item.key)' style='display: inline-block; margin-right: 5px')
|
|
||||||
| {{ item.value }} ({{ item.key.toUpperCase() }})
|
|
||||||
el-select(
|
|
||||||
value=''
|
|
||||||
:disabled='languageDialog.loading || (API.currentUser.$languages && API.currentUser.$languages.length === 3)'
|
|
||||||
:placeholder='$t("dialog.language.select_language")'
|
|
||||||
@change='addUserLanguage'
|
|
||||||
style='margin-top: 14px')
|
|
||||||
el-option(
|
|
||||||
v-for='item in languageDialog.languages'
|
|
||||||
:key='item.key'
|
|
||||||
:value='item.key'
|
|
||||||
:label='item.value')
|
|
||||||
span.flags(:class='languageClass(item.key)' style='display: inline-block; margin-right: 5px')
|
|
||||||
| {{ item.value }} ({{ item.key.toUpperCase() }})
|
|
||||||
//- dialog: bio
|
|
||||||
el-dialog.x-dialog(
|
|
||||||
:before-close='beforeDialogClose'
|
|
||||||
@mousedown.native='dialogMouseDown'
|
|
||||||
@mouseup.native='dialogMouseUp'
|
|
||||||
ref='bioDialog'
|
|
||||||
:visible.sync='bioDialog.visible'
|
|
||||||
:title='$t("dialog.bio.header")'
|
|
||||||
width='600px')
|
|
||||||
div(v-loading='bioDialog.loading')
|
|
||||||
el-input(
|
|
||||||
type='textarea'
|
|
||||||
v-model='bioDialog.bio'
|
|
||||||
size='mini'
|
|
||||||
maxlength='512'
|
|
||||||
show-word-limit
|
|
||||||
:autosize='{ minRows: 5, maxRows: 20 }'
|
|
||||||
:placeholder='$t("dialog.bio.bio_placeholder")'
|
|
||||||
style='margin-bottom: 10px')
|
|
||||||
el-input(
|
|
||||||
v-for='(link, index) in bioDialog.bioLinks'
|
|
||||||
:key='index'
|
|
||||||
:value='link'
|
|
||||||
v-model='bioDialog.bioLinks[index]'
|
|
||||||
size='small'
|
|
||||||
style='margin-top: 5px')
|
|
||||||
img(slot='prepend' :src='getFaviconUrl(link)' style='width: 16px; height: 16px')
|
|
||||||
el-button(slot='append' icon='el-icon-delete' @click='bioDialog.bioLinks.splice(index, 1)')
|
|
||||||
el-button(
|
|
||||||
@click='bioDialog.bioLinks.push("")'
|
|
||||||
:disabled='bioDialog.bioLinks.length >= 3'
|
|
||||||
size='mini'
|
|
||||||
style='margin-top: 5px') {{ $t('dialog.bio.add_link') }}
|
|
||||||
template(#footer)
|
|
||||||
el-button(type='primary' size='small' :disabled='bioDialog.loading' @click='saveBio') {{ $t('dialog.bio.update') }}
|
|
||||||
|
|
||||||
//- dialog: pronouns
|
|
||||||
el-dialog.x-dialog(
|
|
||||||
:before-close='beforeDialogClose'
|
|
||||||
@mousedown.native='dialogMouseDown'
|
|
||||||
@mouseup.native='dialogMouseUp'
|
|
||||||
ref='pronounsDialog'
|
|
||||||
:visible.sync='pronounsDialog.visible'
|
|
||||||
:title='$t("dialog.pronouns.header")'
|
|
||||||
width='600px')
|
|
||||||
div(v-loading='pronounsDialog.loading')
|
|
||||||
el-input(
|
|
||||||
type='textarea'
|
|
||||||
v-model='pronounsDialog.pronouns'
|
|
||||||
size='mini'
|
|
||||||
maxlength='32'
|
|
||||||
show-word-limit
|
|
||||||
:autosize='{ minRows: 2, maxRows: 5 }'
|
|
||||||
:placeholder='$t("dialog.pronouns.pronouns_placeholder")')
|
|
||||||
template(#footer)
|
|
||||||
el-button(type='primary' size='small' :disabled='pronounsDialog.loading' @click='savePronouns') {{ $t('dialog.pronouns.update') }}
|
|
||||||
|
|
||||||
//- dialog: Gallery/VRCPlusIcons
|
|
||||||
el-dialog.x-dialog(
|
|
||||||
:before-close='beforeDialogClose'
|
|
||||||
@mousedown.native='dialogMouseDown'
|
|
||||||
@mouseup.native='dialogMouseUp'
|
|
||||||
ref='galleryDialog'
|
|
||||||
:visible.sync='galleryDialogVisible'
|
|
||||||
:title='$t("dialog.gallery_icons.header")'
|
|
||||||
width='97vw'
|
|
||||||
top='5vh'
|
|
||||||
destroy-on-close)
|
|
||||||
el-tabs(type='card' ref='galleryTabs')
|
|
||||||
el-tab-pane(v-if='galleryDialogVisible' v-loading='galleryDialogGalleryLoading')
|
|
||||||
span(slot='label') {{ $t('dialog.gallery_icons.gallery') }}
|
|
||||||
span(style='color: #909399; font-size: 12px; margin-left: 5px') {{ galleryTable.length }}/64
|
|
||||||
input#GalleryUploadButton(
|
|
||||||
type='file'
|
|
||||||
accept='image/*'
|
|
||||||
@change='onFileChangeGallery'
|
|
||||||
style='display: none')
|
|
||||||
span {{ $t('dialog.gallery_icons.recommended_image_size') }}: 1200x900px (4:3)
|
|
||||||
br
|
|
||||||
br
|
|
||||||
el-button-group
|
|
||||||
el-button(type='default' size='small' @click='refreshGalleryTable' icon='el-icon-refresh') {{ $t('dialog.gallery_icons.refresh') }}
|
|
||||||
el-button(
|
|
||||||
type='default'
|
|
||||||
size='small'
|
|
||||||
@click='displayGalleryUpload'
|
|
||||||
icon='el-icon-upload2'
|
|
||||||
:disabled='!API.currentUser.$isVRCPlus') {{ $t('dialog.gallery_icons.upload') }}
|
|
||||||
el-button(
|
|
||||||
type='default'
|
|
||||||
size='small'
|
|
||||||
@click='setProfilePicOverride("")'
|
|
||||||
icon='el-icon-close'
|
|
||||||
:disabled='!API.currentUser.profilePicOverride') {{ $t('dialog.gallery_icons.clear') }}
|
|
||||||
br
|
|
||||||
.x-friend-item(
|
|
||||||
v-if='image.versions && image.versions.length > 0'
|
|
||||||
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[image.versions.length - 1].file.url'
|
|
||||||
@click='setProfilePicOverride(image.id)'
|
|
||||||
:class='{ "current-vrcplus-icon": compareCurrentProfilePic(image.id) }')
|
|
||||||
img.avatar(v-lazy='image.versions[image.versions.length - 1].file.url')
|
|
||||||
div(style='float: right; margin-top: 5px')
|
|
||||||
el-button(
|
|
||||||
type='default'
|
|
||||||
@click='showFullscreenImageDialog(image.versions[image.versions.length - 1].file.url)'
|
|
||||||
size='mini'
|
|
||||||
icon='el-icon-picture-outline'
|
|
||||||
circle)
|
|
||||||
el-button(
|
|
||||||
type='default'
|
|
||||||
@click='deleteGalleryImage(image.id)'
|
|
||||||
size='mini'
|
|
||||||
icon='el-icon-delete'
|
|
||||||
circle
|
|
||||||
style='margin-left: 5px')
|
|
||||||
el-tab-pane(v-if='galleryDialogVisible' v-loading='galleryDialogIconsLoading' lazy)
|
|
||||||
span(slot='label') {{ $t('dialog.gallery_icons.icons') }}
|
|
||||||
span(style='color: #909399; font-size: 12px; margin-left: 5px') {{ VRCPlusIconsTable.length }}/64
|
|
||||||
input#VRCPlusIconUploadButton(
|
|
||||||
type='file'
|
|
||||||
accept='image/*'
|
|
||||||
@change='onFileChangeVRCPlusIcon'
|
|
||||||
style='display: none')
|
|
||||||
span {{ $t('dialog.gallery_icons.recommended_image_size') }}: 2048x2048px (1:1)
|
|
||||||
br
|
|
||||||
br
|
|
||||||
el-button-group
|
|
||||||
el-button(type='default' size='small' @click='refreshVRCPlusIconsTable' icon='el-icon-refresh') {{ $t('dialog.gallery_icons.refresh') }}
|
|
||||||
el-button(
|
|
||||||
type='default'
|
|
||||||
size='small'
|
|
||||||
@click='displayVRCPlusIconUpload'
|
|
||||||
icon='el-icon-upload2'
|
|
||||||
:disabled='!API.currentUser.$isVRCPlus') {{ $t('dialog.gallery_icons.upload') }}
|
|
||||||
el-button(
|
|
||||||
type='default'
|
|
||||||
size='small'
|
|
||||||
@click='setVRCPlusIcon("")'
|
|
||||||
icon='el-icon-close'
|
|
||||||
:disabled='!API.currentUser.userIcon') {{ $t('dialog.gallery_icons.clear') }}
|
|
||||||
br
|
|
||||||
.x-friend-item(
|
|
||||||
v-if='image.versions && image.versions.length > 0'
|
|
||||||
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[image.versions.length - 1].file.url'
|
|
||||||
@click='setVRCPlusIcon(image.id)'
|
|
||||||
:class='{ "current-vrcplus-icon": compareCurrentVRCPlusIcon(image.id) }')
|
|
||||||
img.avatar(v-lazy='image.versions[image.versions.length - 1].file.url')
|
|
||||||
div(style='float: right; margin-top: 5px')
|
|
||||||
el-button(
|
|
||||||
type='default'
|
|
||||||
@click='showFullscreenImageDialog(image.versions[image.versions.length - 1].file.url)'
|
|
||||||
size='mini'
|
|
||||||
icon='el-icon-picture-outline'
|
|
||||||
circle)
|
|
||||||
el-button(
|
|
||||||
type='default'
|
|
||||||
@click='deleteVRCPlusIcon(image.id)'
|
|
||||||
size='mini'
|
|
||||||
icon='el-icon-delete'
|
|
||||||
circle
|
|
||||||
style='margin-left: 5px')
|
|
||||||
el-tab-pane(v-if='galleryDialogVisible' v-loading='galleryDialogEmojisLoading' lazy)
|
|
||||||
span(slot='label') {{ $t('dialog.gallery_icons.emojis') }}
|
|
||||||
span(style='color: #909399; font-size: 12px; margin-left: 5px') {{ emojiTable.length }}/9
|
|
||||||
input#EmojiUploadButton(type='file' accept='image/*' @change='onFileChangeEmoji' style='display: none')
|
|
||||||
span {{ $t('dialog.gallery_icons.recommended_image_size') }}: 1024x1024px (1:1)
|
|
||||||
br
|
|
||||||
br
|
|
||||||
div(style='display: flex; align-items: center')
|
|
||||||
el-button-group(style='margin-right: 10px')
|
|
||||||
el-button(type='default' size='small' @click='refreshEmojiTable' icon='el-icon-refresh') {{ $t('dialog.gallery_icons.refresh') }}
|
|
||||||
el-button(
|
|
||||||
type='default'
|
|
||||||
size='small'
|
|
||||||
@click='displayEmojiUpload'
|
|
||||||
icon='el-icon-upload2'
|
|
||||||
:disabled='!API.currentUser.$isVRCPlus') {{ $t('dialog.gallery_icons.upload') }}
|
|
||||||
el-select(v-model='emojiAnimationStyle' popper-class='max-height-el-select')
|
|
||||||
el-option-group {{ $t('dialog.gallery_icons.emoji_animation_styles') }}
|
|
||||||
el-option.x-friend-item(
|
|
||||||
v-for='(fileName, styleName) in emojiAnimationStyleList'
|
|
||||||
:key='styleName'
|
|
||||||
:label='styleName'
|
|
||||||
:value='styleName'
|
|
||||||
style='height: auto')
|
|
||||||
.avatar(style='width: 200px; height: 200px')
|
|
||||||
img(v-lazy='`${emojiAnimationStyleUrl}${fileName}`')
|
|
||||||
.detail
|
|
||||||
span.name(v-text='styleName' style='margin-right: 100px')
|
|
||||||
el-checkbox(v-model='emojiAnimType' style='margin-left: 10px; margin-right: 10px')
|
|
||||||
span {{ $t('dialog.gallery_icons.emoji_animation_type') }}
|
|
||||||
template(v-if='emojiAnimType')
|
|
||||||
span(style='margin-right: 10px') {{ $t('dialog.gallery_icons.emoji_animation_fps') }}
|
|
||||||
el-input-number(
|
|
||||||
size='small'
|
|
||||||
v-model='emojiAnimFps'
|
|
||||||
:min='1'
|
|
||||||
:max='64'
|
|
||||||
style='margin-right: 10px; width: 112px')
|
|
||||||
span(style='margin-right: 10px') {{ $t('dialog.gallery_icons.emoji_animation_frame_count') }}
|
|
||||||
el-input-number(
|
|
||||||
size='small'
|
|
||||||
v-model='emojiAnimFrameCount'
|
|
||||||
:min='2'
|
|
||||||
:max='64'
|
|
||||||
style='margin-right: 10px; width: 112px')
|
|
||||||
el-checkbox(v-model='emojiAnimLoopPingPong' style='margin-left: 10px; margin-right: 10px')
|
|
||||||
span {{ $t('dialog.gallery_icons.emoji_loop_pingpong') }}
|
|
||||||
br
|
|
||||||
br
|
|
||||||
span {{ $t('dialog.gallery_icons.flipbook_info') }}
|
|
||||||
br
|
|
||||||
.x-friend-item(
|
|
||||||
v-if='image.versions && image.versions.length > 0'
|
|
||||||
v-for='image in emojiTable'
|
|
||||||
:key='image.id'
|
|
||||||
style='display: inline-block; margin-top: 10px; width: unset; cursor: default')
|
|
||||||
.vrcplus-icon(
|
|
||||||
v-if='image.versions[image.versions.length - 1].file.url'
|
|
||||||
style='overflow: hidden'
|
|
||||||
@click='showFullscreenImageDialog(image.versions[image.versions.length - 1].file.url, getEmojiFileName(image))')
|
|
||||||
template(v-if='image.frames')
|
|
||||||
.avatar(
|
|
||||||
:style='generateEmojiStyle(image.versions[image.versions.length - 1].file.url, image.framesOverTime, image.frames, image.loopStyle)')
|
|
||||||
template(v-else)
|
|
||||||
img.avatar(v-lazy='image.versions[image.versions.length - 1].file.url')
|
|
||||||
div(style='display: inline-block; margin: 5px')
|
|
||||||
span(v-if='image.loopStyle === "pingpong"') #[i.el-icon-refresh.el-icon--left]
|
|
||||||
span(style='margin-right: 5px') {{ image.animationStyle }}
|
|
||||||
span(v-if='image.framesOverTime' style='margin-right: 5px') {{ image.framesOverTime }}fps
|
|
||||||
span(v-if='image.frames' style='margin-right: 5px') {{ image.frames }}frames
|
|
||||||
br
|
|
||||||
div(style='float: right; margin-top: 5px')
|
|
||||||
el-button(
|
|
||||||
type='default'
|
|
||||||
@click='showFullscreenImageDialog(image.versions[image.versions.length - 1].file.url, getEmojiFileName(image))'
|
|
||||||
size='mini'
|
|
||||||
icon='el-icon-picture-outline'
|
|
||||||
circle)
|
|
||||||
el-button(
|
|
||||||
type='default'
|
|
||||||
@click='deleteEmoji(image.id)'
|
|
||||||
size='mini'
|
|
||||||
icon='el-icon-delete'
|
|
||||||
circle
|
|
||||||
style='margin-left: 5px')
|
|
||||||
el-tab-pane(v-if='galleryDialogVisible' v-loading='galleryDialogStickersLoading' lazy)
|
|
||||||
span(slot='label') {{ $t('dialog.gallery_icons.stickers') }}
|
|
||||||
span(style='color: #909399; font-size: 12px; margin-left: 5px') {{ stickerTable.length }}/9
|
|
||||||
input#StickerUploadButton(
|
|
||||||
type='file'
|
|
||||||
accept='image/*'
|
|
||||||
@change='onFileChangeSticker'
|
|
||||||
style='display: none')
|
|
||||||
span {{ $t('dialog.gallery_icons.recommended_image_size') }}: 1024x1024px (1:1)
|
|
||||||
br
|
|
||||||
br
|
|
||||||
el-button-group
|
|
||||||
el-button(type='default' size='small' @click='refreshStickerTable' icon='el-icon-refresh') {{ $t('dialog.gallery_icons.refresh') }}
|
|
||||||
el-button(
|
|
||||||
type='default'
|
|
||||||
size='small'
|
|
||||||
@click='displayStickerUpload'
|
|
||||||
icon='el-icon-upload2'
|
|
||||||
:disabled='!API.currentUser.$isVRCPlus') {{ $t('dialog.gallery_icons.upload') }}
|
|
||||||
br
|
|
||||||
.x-friend-item(
|
|
||||||
v-if='image.versions && image.versions.length > 0'
|
|
||||||
v-for='image in stickerTable'
|
|
||||||
:key='image.id'
|
|
||||||
style='display: inline-block; margin-top: 10px; width: unset; cursor: default')
|
|
||||||
.vrcplus-icon(
|
|
||||||
v-if='image.versions[image.versions.length - 1].file.url'
|
|
||||||
style='overflow: hidden'
|
|
||||||
@click='showFullscreenImageDialog(image.versions[image.versions.length - 1].file.url)')
|
|
||||||
img.avatar(v-lazy='image.versions[image.versions.length - 1].file.url')
|
|
||||||
div(style='float: right; margin-top: 5px')
|
|
||||||
el-button(
|
|
||||||
type='default'
|
|
||||||
@click='showFullscreenImageDialog(image.versions[image.versions.length - 1].file.url)'
|
|
||||||
size='mini'
|
|
||||||
icon='el-icon-picture-outline'
|
|
||||||
circle)
|
|
||||||
el-button(
|
|
||||||
type='default'
|
|
||||||
@click='deleteSticker(image.id)'
|
|
||||||
size='mini'
|
|
||||||
icon='el-icon-delete'
|
|
||||||
circle
|
|
||||||
style='margin-left: 5px')
|
|
||||||
el-tab-pane(v-if='galleryDialogVisible' v-loading='galleryDialogPrintsLoading' lazy)
|
|
||||||
span(slot='label') {{ $t('dialog.gallery_icons.prints') }}
|
|
||||||
span(style='color: #909399; font-size: 12px; margin-left: 5px') {{ printTable.length }}/64
|
|
||||||
input#PrintUploadButton(type='file' accept='image/*' @change='onFileChangePrint' style='display: none')
|
|
||||||
span {{ $t('dialog.gallery_icons.recommended_image_size') }}: 1920x1080px (16:9)
|
|
||||||
br
|
|
||||||
br
|
|
||||||
div(style='display: flex; align-items: center')
|
|
||||||
el-button-group
|
|
||||||
el-button(type='default' size='small' @click='refreshPrintTable' icon='el-icon-refresh') {{ $t('dialog.gallery_icons.refresh') }}
|
|
||||||
el-button(
|
|
||||||
type='default'
|
|
||||||
size='small'
|
|
||||||
@click='displayPrintUpload'
|
|
||||||
icon='el-icon-upload2'
|
|
||||||
:disabled='!API.currentUser.$isVRCPlus') {{ $t('dialog.gallery_icons.upload') }}
|
|
||||||
el-input(
|
|
||||||
type='textarea'
|
|
||||||
v-model='printUploadNote'
|
|
||||||
size='mini'
|
|
||||||
rows='1'
|
|
||||||
resize='none'
|
|
||||||
maxlength='32'
|
|
||||||
style='margin-left: 10px; width: 300px'
|
|
||||||
:placeholder='$t("dialog.gallery_icons.note")')
|
|
||||||
el-checkbox(v-model='printCropBorder' style='margin-left: 10px; margin-right: 10px')
|
|
||||||
span {{ $t('dialog.gallery_icons.crop_print_border') }}
|
|
||||||
br
|
|
||||||
.x-friend-item(
|
|
||||||
v-for='image in printTable'
|
|
||||||
:key='image.id'
|
|
||||||
style='display: inline-block; margin-top: 10px; width: unset; cursor: default')
|
|
||||||
.vrcplus-icon(
|
|
||||||
style='overflow: hidden'
|
|
||||||
@click='showFullscreenImageDialog(image.files.image, getPrintFileName(image))')
|
|
||||||
img.avatar(v-lazy='image.files.image')
|
|
||||||
div(style='margin-top: 5px; width: 208px')
|
|
||||||
span.x-ellipsis(v-if='image.note' v-text='image.note' style='display: block')
|
|
||||||
span(v-else style='display: block')
|
|
||||||
location.x-ellipsis(
|
|
||||||
v-if='image.worldId'
|
|
||||||
:location='image.worldId'
|
|
||||||
:hint='image.worldName'
|
|
||||||
style='display: block')
|
|
||||||
span(v-else style='display: block')
|
|
||||||
display-name.x-ellipsis(
|
|
||||||
v-if='image.authorId'
|
|
||||||
:userid='image.authorId'
|
|
||||||
:hint='image.authorName'
|
|
||||||
style='color: #909399; font-family: monospace; display: block')
|
|
||||||
span(v-else style='font-family: monospace; display: block')
|
|
||||||
span.x-ellipsis(
|
|
||||||
v-if='image.createdAt'
|
|
||||||
style='color: #909399; font-family: monospace; font-size: 11px; display: block') {{ image.createdAt | formatDate('long') }}
|
|
||||||
span(v-else style='display: block')
|
|
||||||
div(style='float: right')
|
|
||||||
el-button(
|
|
||||||
type='default'
|
|
||||||
@click='showFullscreenImageDialog(image.files.image, getPrintFileName(image))'
|
|
||||||
size='mini'
|
|
||||||
icon='el-icon-picture-outline'
|
|
||||||
circle)
|
|
||||||
el-button(
|
|
||||||
type='default'
|
|
||||||
@click='deletePrint(image.id)'
|
|
||||||
size='mini'
|
|
||||||
icon='el-icon-delete'
|
|
||||||
circle
|
|
||||||
style='margin-left: 5px')
|
|
||||||
215
src/mixins/dialogs/dialogs.pug
Normal file
215
src/mixins/dialogs/dialogs.pug
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
mixin dialogs
|
||||||
|
//- previous instances
|
||||||
|
PreviousInstancesInfoDialog(
|
||||||
|
:visible.sync='previousInstancesInfoDialogVisible'
|
||||||
|
:instanceId='previousInstancesInfoDialogInstanceId'
|
||||||
|
:gameLogIsFriend='gameLogIsFriend'
|
||||||
|
:gameLogIsFavorite='gameLogIsFavorite'
|
||||||
|
:lookupUser='lookupUser'
|
||||||
|
:isDarkMode='isDarkMode')
|
||||||
|
|
||||||
|
//- favorites
|
||||||
|
FriendImportDialog(
|
||||||
|
:friendImportDialogVisible.sync='friendImportDialogVisible'
|
||||||
|
:friendImportDialogInput.sync='friendImportDialogInput')
|
||||||
|
|
||||||
|
WorldImportDialog(
|
||||||
|
:worldImportDialogVisible.sync='worldImportDialogVisible'
|
||||||
|
:worldImportDialogInput.sync='worldImportDialogInput'
|
||||||
|
:getLocalWorldFavoriteGroupLength='getLocalWorldFavoriteGroupLength'
|
||||||
|
:localWorldFavoriteGroups='localWorldFavoriteGroups'
|
||||||
|
@addLocalWorldFavorite='addLocalWorldFavorite')
|
||||||
|
|
||||||
|
AvatarImportDialog(
|
||||||
|
:avatarImportDialogVisible.sync='avatarImportDialogVisible'
|
||||||
|
:avatarImportDialogInput.sync='avatarImportDialogInput'
|
||||||
|
:getLocalAvatarFavoriteGroupLength='getLocalAvatarFavoriteGroupLength'
|
||||||
|
:localAvatarFavoriteGroups='localAvatarFavoriteGroups'
|
||||||
|
@addLocalAvatarFavorite='addLocalAvatarFavorite')
|
||||||
|
|
||||||
|
//- favorites dialog
|
||||||
|
ChooseFavoriteGroupDialog(
|
||||||
|
:favoriteDialog.sync='favoriteDialog'
|
||||||
|
:localAvatarFavoriteGroups='localAvatarFavoriteGroups'
|
||||||
|
:localWorldFavoriteGroups='localWorldFavoriteGroups'
|
||||||
|
:hasLocalWorldFavorite='hasLocalWorldFavorite'
|
||||||
|
:getLocalWorldFavoriteGroupLength='getLocalWorldFavoriteGroupLength'
|
||||||
|
:hasLocalAvatarFavorite='hasLocalAvatarFavorite'
|
||||||
|
:getLocalAvatarFavoriteGroupLength='getLocalAvatarFavoriteGroupLength'
|
||||||
|
@addLocalWorldFavorite='addLocalWorldFavorite'
|
||||||
|
@removeLocalWorldFavorite='removeLocalWorldFavorite'
|
||||||
|
@addLocalAvatarFavorite='addLocalAvatarFavorite'
|
||||||
|
@removeLocalAvatarFavorite='removeLocalAvatarFavorite'
|
||||||
|
@deleteFavoriteNoConfirm='deleteFavoriteNoConfirm')
|
||||||
|
|
||||||
|
//- launch
|
||||||
|
LaunchDialog(
|
||||||
|
:checkCanInvite='checkCanInvite'
|
||||||
|
:launchDialogData.sync='launchDialogData'
|
||||||
|
:hideTooltips='hideTooltips'
|
||||||
|
:vipFriends='vipFriends'
|
||||||
|
:onlineFriends='onlineFriends'
|
||||||
|
:activeFriends='activeFriends'
|
||||||
|
:inviteMessageTable='inviteMessageTable'
|
||||||
|
:uploadImage='uploadImage'
|
||||||
|
:lastLocation='lastLocation'
|
||||||
|
@launchGame='launchGame')
|
||||||
|
|
||||||
|
UserDialog(
|
||||||
|
:userDialog='userDialog'
|
||||||
|
:languageDialog='languageDialog'
|
||||||
|
:hideTooltips='hideTooltips'
|
||||||
|
:lastLocation='lastLocation'
|
||||||
|
:lastLocationDestination='lastLocationDestination'
|
||||||
|
:isGameRunning='isGameRunning'
|
||||||
|
:checkCanInvite='checkCanInvite'
|
||||||
|
:updateInstanceInfo='updateInstanceInfo'
|
||||||
|
:hideUserNotes='hideUserNotes'
|
||||||
|
:hideUserMemos='hideUserMemos'
|
||||||
|
:userOnlineFor='userOnlineFor'
|
||||||
|
:userDialogWorldSortingOptions='userDialogWorldSortingOptions'
|
||||||
|
:userDialogWorldOrderOptions='userDialogWorldOrderOptions'
|
||||||
|
:avatarRemoteDatabase='avatarRemoteDatabase'
|
||||||
|
:friendLogTable='friendLogTable'
|
||||||
|
:lookupAvatars='lookupAvatars'
|
||||||
|
:updateInGameGroupOrder='updateInGameGroupOrder'
|
||||||
|
:inviteMessageTable='inviteMessageTable'
|
||||||
|
:uploadImage='uploadImage'
|
||||||
|
:inviteRequestMessageTable='inviteRequestMessageTable'
|
||||||
|
:inGameGroupOrder='inGameGroupOrder'
|
||||||
|
:shiftHeld='shiftHeld'
|
||||||
|
:vipFriends='vipFriends'
|
||||||
|
:onlineFriends='onlineFriends'
|
||||||
|
:offlineFriends='offlineFriends'
|
||||||
|
:activeFriends='activeFriends'
|
||||||
|
:galleryDialogVisible='galleryDialogVisible'
|
||||||
|
:galleryDialogGalleryLoading='galleryDialogGalleryLoading'
|
||||||
|
:galleryDialogIconsLoading='galleryDialogIconsLoading'
|
||||||
|
:galleryDialogEmojisLoading='galleryDialogEmojisLoading'
|
||||||
|
:galleryDialogStickersLoading='galleryDialogStickersLoading'
|
||||||
|
:galleryDialogPrintsLoading='galleryDialogPrintsLoading'
|
||||||
|
:galleryTable='galleryTable'
|
||||||
|
:VRCPlusIconsTable='VRCPlusIconsTable'
|
||||||
|
:emojiTable='emojiTable'
|
||||||
|
:stickerTable='stickerTable'
|
||||||
|
:printUploadNote='printUploadNote'
|
||||||
|
:printCropBorder='printCropBorder'
|
||||||
|
:printTable='printTable'
|
||||||
|
@refreshGalleryTable='refreshGalleryTable'
|
||||||
|
@refreshEmojiTable='refreshEmojiTable'
|
||||||
|
@refreshStickerTable='refreshStickerTable'
|
||||||
|
@refreshVRCPlusIconsTable='refreshVRCPlusIconsTable'
|
||||||
|
@refreshPrintTable='refreshPrintTable'
|
||||||
|
@sortUserDialogAvatars='sortUserDialogAvatars'
|
||||||
|
@logout='logout'
|
||||||
|
@showAvatarAuthorDialog='showAvatarAuthorDialog'
|
||||||
|
@showGalleryDialog='showGalleryDialog'
|
||||||
|
@saveCurrentUserGroups='saveCurrentUserGroups'
|
||||||
|
@refreshUserDialogAvatars='refreshUserDialogAvatars'
|
||||||
|
@refreshUserDialogTreeData='refreshUserDialogTreeData'
|
||||||
|
@saveUserMemo='saveUserMemo'
|
||||||
|
@setGroupVisibility='setGroupVisibility'
|
||||||
|
@leaveGroupPrompt='leaveGroupPrompt'
|
||||||
|
@clearInviteImageUpload='clearInviteImageUpload'
|
||||||
|
@closeGalleryDialog='galleryDialogVisible = false')
|
||||||
|
|
||||||
|
WorldDialog(
|
||||||
|
:worldDialog.sync='worldDialog'
|
||||||
|
:hideTooltips='hideTooltips'
|
||||||
|
:shiftHeld='shiftHeld'
|
||||||
|
:isGameRunning='isGameRunning'
|
||||||
|
:lastLocation='lastLocation'
|
||||||
|
:instanceJoinHistory='instanceJoinHistory'
|
||||||
|
:updateInstanceInfo='updateInstanceInfo'
|
||||||
|
:isAgeGatedInstancesVisible='isAgeGatedInstancesVisible'
|
||||||
|
:createNewInstance='createNewInstance'
|
||||||
|
:instanceContentSettings='instanceContentSettings'
|
||||||
|
:offlineFriends='offlineFriends'
|
||||||
|
:activeFriends='activeFriends'
|
||||||
|
:onlineFriends='onlineFriends'
|
||||||
|
:vipFriends='vipFriends'
|
||||||
|
:inviteMessageTable='inviteMessageTable'
|
||||||
|
:uploadImage='uploadImage'
|
||||||
|
@openFolderGeneric='openFolderGeneric'
|
||||||
|
@deleteVRChatCache='deleteVRChatCache'
|
||||||
|
@worldDialogCommand='worldDialogCommand')
|
||||||
|
|
||||||
|
GroupDialog(
|
||||||
|
:groupDialog.sync='groupDialog'
|
||||||
|
:hideTooltips='hideTooltips'
|
||||||
|
:lastLocation='lastLocation'
|
||||||
|
:updateInstanceInfo='updateInstanceInfo'
|
||||||
|
:groupDialogSortingOptions='groupDialogSortingOptions'
|
||||||
|
:groupDialogFilterOptions='groupDialogFilterOptions'
|
||||||
|
:isGroupGalleryLoading='isGroupGalleryLoading'
|
||||||
|
:randomUserColours='randomUserColours'
|
||||||
|
@updateGroupPostSearch='updateGroupPostSearch'
|
||||||
|
@groupDialogCommand='groupDialogCommand'
|
||||||
|
@getGroupDialogGroup='getGroupDialogGroup')
|
||||||
|
|
||||||
|
AvatarDialog(
|
||||||
|
:avatarDialog='avatarDialog'
|
||||||
|
:hideTooltips='hideTooltips'
|
||||||
|
:isGameRunning='isGameRunning'
|
||||||
|
@openFolderGeneric='openFolderGeneric'
|
||||||
|
@deleteVRChatCache='deleteVRChatCache')
|
||||||
|
|
||||||
|
FullscreenImageDialog(:fullscreenImageDialog='fullscreenImageDialog')
|
||||||
|
|
||||||
|
//- settings
|
||||||
|
FeedFiltersDialog(
|
||||||
|
:feedFiltersDialogMode.sync='feedFiltersDialogMode'
|
||||||
|
:photonLoggingEnabled='photonLoggingEnabled'
|
||||||
|
:sharedFeedFilters='sharedFeedFilters'
|
||||||
|
:sharedFeedFiltersDefaults='sharedFeedFiltersDefaults'
|
||||||
|
@updateSharedFeed='updateSharedFeed')
|
||||||
|
|
||||||
|
LaunchOptionsDialog(:isLaunchOptionsDialogVisible.sync='isLaunchOptionsDialogVisible')
|
||||||
|
|
||||||
|
OpenSourceSoftwareNoticeDialog(:ossDialog.sync='ossDialog')
|
||||||
|
|
||||||
|
ChangelogDialog(:changeLogDialog.sync='changeLogDialog')
|
||||||
|
|
||||||
|
ScreenshotMetadataDialog(
|
||||||
|
:screenshotMetadataDialog='screenshotMetadataDialog'
|
||||||
|
:currentlyDroppingFile='currentlyDroppingFile'
|
||||||
|
:fullscreenImageDialog='fullscreenImageDialog'
|
||||||
|
@lookupUser='lookupUser')
|
||||||
|
|
||||||
|
EditInviteMessageDialog(:editInviteMessageDialog.sync='editInviteMessageDialog')
|
||||||
|
|
||||||
|
NoteExportDialog(:isNoteExportDialogVisible.sync='isNoteExportDialogVisible' :friends='friends')
|
||||||
|
|
||||||
|
VRChatConfigDialog(
|
||||||
|
:isVRChatConfigDialogVisible.sync='isVRChatConfigDialogVisible'
|
||||||
|
:VRChatUsedCacheSize='VRChatUsedCacheSize'
|
||||||
|
:VRChatTotalCacheSize='VRChatTotalCacheSize'
|
||||||
|
:VRChatCacheSizeLoading='VRChatCacheSizeLoading'
|
||||||
|
:folderSelectorDialog='folderSelectorDialog'
|
||||||
|
:hideTooltips='hideTooltips'
|
||||||
|
@getVRChatCacheSize='getVRChatCacheSize'
|
||||||
|
@sweepVRChatCache='sweepVRChatCache')
|
||||||
|
|
||||||
|
YouTubeApiDialog(
|
||||||
|
:isYouTubeApiDialogVisible.sync='isYouTubeApiDialogVisible'
|
||||||
|
:lookupYouTubeVideo='lookupYouTubeVideo'
|
||||||
|
:youTubeApiKey.sync='youTubeApiKey')
|
||||||
|
|
||||||
|
NotificationPositionDialog(
|
||||||
|
:isNotificationPositionDialogVisible.sync='isNotificationPositionDialogVisible'
|
||||||
|
:notificationPosition.sync='notificationPosition'
|
||||||
|
@changeNotificationPosition='changeNotificationPosition')
|
||||||
|
|
||||||
|
AvatarProviderDialog(
|
||||||
|
:isAvatarProviderDialogVisible.sync='isAvatarProviderDialogVisible'
|
||||||
|
:avatarRemoteDatabaseProviderList='avatarRemoteDatabaseProviderList'
|
||||||
|
@saveAvatarProviderList='saveAvatarProviderList'
|
||||||
|
@removeAvatarProvider='removeAvatarProvider')
|
||||||
|
|
||||||
|
RegistryBackupDialog(
|
||||||
|
:isRegistryBackupDialogVisible.sync='isRegistryBackupDialogVisible'
|
||||||
|
:backupVrcRegistry='backupVrcRegistry')
|
||||||
|
|
||||||
|
PrimaryPasswordDialog(
|
||||||
|
:enablePrimaryPasswordDialog.sync='enablePrimaryPasswordDialog'
|
||||||
|
@setPrimaryPassword='setPrimaryPassword')
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
mixin images
|
|
||||||
//- dialog: Change avatar image
|
|
||||||
el-dialog.x-dialog(
|
|
||||||
:before-close='beforeDialogClose'
|
|
||||||
@mousedown.native='dialogMouseDown'
|
|
||||||
@mouseup.native='dialogMouseUp'
|
|
||||||
ref='changeAvatarImageDialog'
|
|
||||||
:visible.sync='changeAvatarImageDialogVisible'
|
|
||||||
:title='$t("dialog.change_content_image.avatar")'
|
|
||||||
width='850px')
|
|
||||||
div(v-if='changeAvatarImageDialogVisible' v-loading='changeAvatarImageDialogLoading')
|
|
||||||
input#AvatarImageUploadButton(
|
|
||||||
type='file'
|
|
||||||
accept='image/*'
|
|
||||||
@change='onFileChangeAvatarImage'
|
|
||||||
style='display: none')
|
|
||||||
span {{ $t('dialog.change_content_image.description') }}
|
|
||||||
br
|
|
||||||
el-button-group(style='padding-bottom: 10px; padding-top: 10px')
|
|
||||||
el-button(
|
|
||||||
type='default'
|
|
||||||
size='small'
|
|
||||||
@click='displayPreviousImages("Avatar", "Change")'
|
|
||||||
icon='el-icon-refresh') {{ $t('dialog.change_content_image.refresh') }}
|
|
||||||
el-button(type='default' size='small' @click='uploadAvatarImage' icon='el-icon-upload2') {{ $t('dialog.change_content_image.upload') }}
|
|
||||||
//- el-button(type="default" size="small" @click="deleteAvatarImage" icon="el-icon-delete") Delete Latest Image
|
|
||||||
br
|
|
||||||
div(
|
|
||||||
style='display: inline-block'
|
|
||||||
v-for='image in previousImagesTable'
|
|
||||||
:key='image.version'
|
|
||||||
v-if='image.file')
|
|
||||||
.x-change-image-item(
|
|
||||||
@click='setAvatarImage(image)'
|
|
||||||
style='cursor: pointer'
|
|
||||||
:class='{ "current-image": compareCurrentImage(image) }')
|
|
||||||
img.image(v-lazy='image.file.url')
|
|
||||||
|
|
||||||
//- dialog: Change world image
|
|
||||||
el-dialog.x-dialog(
|
|
||||||
:before-close='beforeDialogClose'
|
|
||||||
@mousedown.native='dialogMouseDown'
|
|
||||||
@mouseup.native='dialogMouseUp'
|
|
||||||
ref='changeWorldImageDialog'
|
|
||||||
:visible.sync='changeWorldImageDialogVisible'
|
|
||||||
:title='$t("dialog.change_content_image.world")'
|
|
||||||
width='850px')
|
|
||||||
div(v-if='changeWorldImageDialogVisible' v-loading='changeWorldImageDialogLoading')
|
|
||||||
input#WorldImageUploadButton(
|
|
||||||
type='file'
|
|
||||||
accept='image/*'
|
|
||||||
@change='onFileChangeWorldImage'
|
|
||||||
style='display: none')
|
|
||||||
span {{ $t('dialog.change_content_image.description') }}
|
|
||||||
br
|
|
||||||
el-button-group(style='padding-bottom: 10px; padding-top: 10px')
|
|
||||||
el-button(
|
|
||||||
type='default'
|
|
||||||
size='small'
|
|
||||||
@click='displayPreviousImages("World", "Change")'
|
|
||||||
icon='el-icon-refresh') {{ $t('dialog.change_content_image.refresh') }}
|
|
||||||
el-button(type='default' size='small' @click='uploadWorldImage' icon='el-icon-upload2') {{ $t('dialog.change_content_image.upload') }}
|
|
||||||
//- el-button(type="default" size="small" @click="deleteWorldImage" icon="el-icon-delete") Delete Latest Image
|
|
||||||
br
|
|
||||||
div(
|
|
||||||
style='display: inline-block'
|
|
||||||
v-for='image in previousImagesTable'
|
|
||||||
:key='image.version'
|
|
||||||
v-if='image.file')
|
|
||||||
.x-change-image-item(
|
|
||||||
@click='setWorldImage(image)'
|
|
||||||
style='cursor: pointer'
|
|
||||||
:class='{ "current-image": compareCurrentImage(image) }')
|
|
||||||
img.image(v-lazy='image.file.url')
|
|
||||||
|
|
||||||
//- dialog: Display previous avatar/world images
|
|
||||||
el-dialog.x-dialog(
|
|
||||||
:before-close='beforeDialogClose'
|
|
||||||
@mousedown.native='dialogMouseDown'
|
|
||||||
@mouseup.native='dialogMouseUp'
|
|
||||||
ref='previousImagesDialog'
|
|
||||||
:visible.sync='previousImagesDialogVisible'
|
|
||||||
:title='$t("dialog.previous_images.header")'
|
|
||||||
width='800px')
|
|
||||||
div(v-if='previousImagesDialogVisible')
|
|
||||||
div(
|
|
||||||
style='display: inline-block'
|
|
||||||
v-for='image in previousImagesTable'
|
|
||||||
:key='image.version'
|
|
||||||
v-if='image.file')
|
|
||||||
el-popover.x-change-image-item(placement='right' width='500px' trigger='click')
|
|
||||||
img.x-link(slot='reference' v-lazy='image.file.url')
|
|
||||||
img.x-link(
|
|
||||||
v-lazy='image.file.url'
|
|
||||||
style='width: 500px; height: 375px'
|
|
||||||
@click='showFullscreenImageDialog(image.file.url)')
|
|
||||||
|
|
||||||
//- dialog: gallery select
|
|
||||||
el-dialog.x-dialog(
|
|
||||||
:before-close='beforeDialogClose'
|
|
||||||
@mousedown.native='dialogMouseDown'
|
|
||||||
@mouseup.native='dialogMouseUp'
|
|
||||||
ref='gallerySelectDialog'
|
|
||||||
:visible.sync='gallerySelectDialog.visible'
|
|
||||||
:title='$t("dialog.gallery_select.header")'
|
|
||||||
width='100%')
|
|
||||||
div(v-if='gallerySelectDialog.visible')
|
|
||||||
span(slot='label') {{ $t('dialog.gallery_select.gallery') }}
|
|
||||||
span(style='color: #909399; font-size: 12px; margin-left: 5px') {{ galleryTable.length }}/64
|
|
||||||
br
|
|
||||||
input#GalleryUploadButton(type='file' accept='image/*' @change='onFileChangeGallery' style='display: none')
|
|
||||||
el-button-group
|
|
||||||
el-button(type='default' size='small' @click='selectImageGallerySelect("", "")' icon='el-icon-close') {{ $t('dialog.gallery_select.none') }}
|
|
||||||
el-button(type='default' size='small' @click='refreshGalleryTable' icon='el-icon-refresh') {{ $t('dialog.gallery_select.refresh') }}
|
|
||||||
el-button(
|
|
||||||
type='default'
|
|
||||||
size='small'
|
|
||||||
@click='displayGalleryUpload'
|
|
||||||
icon='el-icon-upload2'
|
|
||||||
:disabled='!API.currentUser.$isVRCPlus') {{ $t('dialog.gallery_select.upload') }}
|
|
||||||
br
|
|
||||||
.x-friend-item(
|
|
||||||
v-if='image.versions && image.versions.length > 0'
|
|
||||||
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[image.versions.length - 1].file.url'
|
|
||||||
@click='selectImageGallerySelect(image.versions[image.versions.length - 1].file.url, image.id)')
|
|
||||||
img.avatar(v-lazy='image.versions[image.versions.length - 1].file.url')
|
|
||||||
|
|
||||||
//- dialog: full screen image
|
|
||||||
el-dialog.x-dialog(
|
|
||||||
ref='fullscreenImageDialog'
|
|
||||||
:before-close='beforeDialogClose'
|
|
||||||
@mousedown.native='dialogMouseDown'
|
|
||||||
@mouseup.native='dialogMouseUp'
|
|
||||||
:visible.sync='fullscreenImageDialog.visible'
|
|
||||||
top='1vh'
|
|
||||||
width='97vw')
|
|
||||||
div(style='margin: 0 0 5px 5px')
|
|
||||||
el-button(@click='copyImageUrl(fullscreenImageDialog.imageUrl)' size='mini' icon='el-icon-s-order' circle)
|
|
||||||
el-button(
|
|
||||||
type='default'
|
|
||||||
size='mini'
|
|
||||||
icon='el-icon-download'
|
|
||||||
circle
|
|
||||||
@click='downloadAndSaveImage(fullscreenImageDialog.imageUrl, fullscreenImageDialog.fileName)'
|
|
||||||
style='margin-left: 5px')
|
|
||||||
img(v-lazy='fullscreenImageDialog.imageUrl' style='width: 100%; height: 85vh; object-fit: contain')
|
|
||||||
@@ -1,345 +0,0 @@
|
|||||||
mixin invites
|
|
||||||
//- 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')
|
|
||||||
div(v-if='inviteDialog.visible' v-loading='inviteDialog.loading')
|
|
||||||
location(:location='inviteDialog.worldId' :link='false')
|
|
||||||
br
|
|
||||||
el-button(
|
|
||||||
size='mini'
|
|
||||||
v-text='$t("dialog.invite.add_self")'
|
|
||||||
@click='addSelfToInvite'
|
|
||||||
style='margin-top: 10px')
|
|
||||||
el-button(
|
|
||||||
size='mini'
|
|
||||||
v-text='$t("dialog.invite.add_friends_in_instance")'
|
|
||||||
@click='addFriendsInInstanceToInvite'
|
|
||||||
:disabled='inviteDialog.friendsInInstance.length === 0'
|
|
||||||
style='margin-top: 10px')
|
|
||||||
el-button(
|
|
||||||
size='mini'
|
|
||||||
v-text='$t("dialog.invite.add_favorite_friends")'
|
|
||||||
@click='addFavoriteFriendsToInvite'
|
|
||||||
:disabled='vipFriends.length === 0'
|
|
||||||
style='margin-top: 10px')
|
|
||||||
el-select(
|
|
||||||
v-model='inviteDialog.userIds'
|
|
||||||
multiple
|
|
||||||
clearable
|
|
||||||
:placeholder='$t("dialog.invite.select_placeholder")'
|
|
||||||
filterable
|
|
||||||
:disabled='inviteDialog.loading'
|
|
||||||
style='width: 100%; margin-top: 15px')
|
|
||||||
el-option-group(v-if='API.currentUser' :label='$t("side_panel.me")')
|
|
||||||
el-option.x-friend-item(
|
|
||||||
:label='API.currentUser.displayName'
|
|
||||||
:value='API.currentUser.id'
|
|
||||||
style='height: auto')
|
|
||||||
.avatar(:class='userStatusClass(API.currentUser)')
|
|
||||||
img(v-lazy='userImage(API.currentUser)')
|
|
||||||
.detail
|
|
||||||
span.name(v-text='API.currentUser.displayName')
|
|
||||||
el-option-group(
|
|
||||||
v-if='inviteDialog.friendsInInstance.length'
|
|
||||||
:label='$t("dialog.invite.friends_in_instance")')
|
|
||||||
el-option.x-friend-item(
|
|
||||||
v-for='friend in inviteDialog.friendsInInstance'
|
|
||||||
:key='friend.id'
|
|
||||||
:label='friend.name'
|
|
||||||
:value='friend.id'
|
|
||||||
style='height: auto')
|
|
||||||
template(v-if='friend.ref')
|
|
||||||
.avatar(:class='userStatusClass(friend.ref)')
|
|
||||||
img(v-lazy='userImage(friend.ref)')
|
|
||||||
.detail
|
|
||||||
span.name(v-text='friend.ref.displayName' :style='{ color: friend.ref.$userColour }')
|
|
||||||
span(v-else v-text='friend.id')
|
|
||||||
el-option-group(v-if='vipFriends.length' :label='$t("side_panel.favorite")')
|
|
||||||
el-option.x-friend-item(
|
|
||||||
v-for='friend in vipFriends'
|
|
||||||
:key='friend.id'
|
|
||||||
:label='friend.name'
|
|
||||||
:value='friend.id'
|
|
||||||
style='height: auto')
|
|
||||||
template(v-if='friend.ref')
|
|
||||||
.avatar(:class='userStatusClass(friend.ref)')
|
|
||||||
img(v-lazy='userImage(friend.ref)')
|
|
||||||
.detail
|
|
||||||
span.name(v-text='friend.ref.displayName' :style='{ color: friend.ref.$userColour }')
|
|
||||||
span(v-else v-text='friend.id')
|
|
||||||
el-option-group(v-if='onlineFriends.length' :label='$t("side_panel.online")')
|
|
||||||
el-option.x-friend-item(
|
|
||||||
v-for='friend in onlineFriends'
|
|
||||||
:key='friend.id'
|
|
||||||
:label='friend.name'
|
|
||||||
:value='friend.id'
|
|
||||||
style='height: auto')
|
|
||||||
template(v-if='friend.ref')
|
|
||||||
.avatar(:class='userStatusClass(friend.ref)')
|
|
||||||
img(v-lazy='userImage(friend.ref)')
|
|
||||||
.detail
|
|
||||||
span.name(v-text='friend.ref.displayName' :style='{ color: friend.ref.$userColour }')
|
|
||||||
span(v-else v-text='friend.id')
|
|
||||||
el-option-group(v-if='activeFriends.length' :label='$t("side_panel.active")')
|
|
||||||
el-option.x-friend-item(
|
|
||||||
v-for='friend in activeFriends'
|
|
||||||
:key='friend.id'
|
|
||||||
:label='friend.name'
|
|
||||||
:value='friend.id'
|
|
||||||
style='height: auto')
|
|
||||||
template(v-if='friend.ref')
|
|
||||||
.avatar
|
|
||||||
img(v-lazy='userImage(friend.ref)')
|
|
||||||
.detail
|
|
||||||
span.name(v-text='friend.ref.displayName' :style='{ color: friend.ref.$userColour }')
|
|
||||||
span(v-else v-text='friend.id')
|
|
||||||
template(#footer)
|
|
||||||
el-button(
|
|
||||||
size='small'
|
|
||||||
:disabled='inviteDialog.loading || !inviteDialog.userIds.length'
|
|
||||||
@click='showSendInviteDialog()') {{ $t('dialog.invite.invite_with_message') }}
|
|
||||||
el-button(
|
|
||||||
type='primary'
|
|
||||||
size='small'
|
|
||||||
:disabled='inviteDialog.loading || !inviteDialog.userIds.length'
|
|
||||||
@click='sendInvite()') {{ $t('dialog.invite.invite') }}
|
|
||||||
|
|
||||||
//- dialog: Edit And Send Invite Response Message
|
|
||||||
el-dialog.x-dialog(
|
|
||||||
:before-close='beforeDialogClose'
|
|
||||||
@mousedown.native='dialogMouseDown'
|
|
||||||
@mouseup.native='dialogMouseUp'
|
|
||||||
ref='editAndSendInviteResponseDialog'
|
|
||||||
:visible.sync='editAndSendInviteResponseDialog.visible'
|
|
||||||
:title='$t("dialog.edit_send_invite_response_message.header")'
|
|
||||||
width='400px')
|
|
||||||
div(style='font-size: 12px')
|
|
||||||
span {{ $t('dialog.edit_send_invite_response_message.description') }}
|
|
||||||
el-input(
|
|
||||||
type='textarea'
|
|
||||||
v-model='editAndSendInviteResponseDialog.newMessage'
|
|
||||||
size='mini'
|
|
||||||
maxlength='64'
|
|
||||||
show-word-limit
|
|
||||||
:autosize='{ minRows: 2, maxRows: 5 }'
|
|
||||||
placeholder=''
|
|
||||||
style='margin-top: 10px')
|
|
||||||
template(#footer)
|
|
||||||
el-button(type='small' @click='cancelEditAndSendInviteResponse') {{ $t('dialog.edit_send_invite_response_message.cancel') }}
|
|
||||||
el-button(type='primary' size='small' @click='saveEditAndSendInviteResponse') {{ $t('dialog.edit_send_invite_response_message.send') }}
|
|
||||||
|
|
||||||
//- dialog Table: Send Invite Response Message
|
|
||||||
el-dialog.x-dialog(
|
|
||||||
:before-close='beforeDialogClose'
|
|
||||||
@mousedown.native='dialogMouseDown'
|
|
||||||
@mouseup.native='dialogMouseUp'
|
|
||||||
ref='sendInviteResponseDialog'
|
|
||||||
:visible.sync='sendInviteResponseDialogVisible'
|
|
||||||
:title='$t("dialog.invite_response_message.header")'
|
|
||||||
width='800px')
|
|
||||||
template(v-if='API.currentUser.$isVRCPlus')
|
|
||||||
input.inviteImageUploadButton(type='file' accept='image/*' @change='inviteImageUpload')
|
|
||||||
data-tables(
|
|
||||||
v-if='sendInviteResponseDialogVisible'
|
|
||||||
v-bind='inviteResponseMessageTable'
|
|
||||||
@row-click='showSendInviteResponseConfirmDialog'
|
|
||||||
style='margin-top: 10px; cursor: pointer')
|
|
||||||
el-table-column(:label='$t("table.profile.invite_messages.slot")' prop='slot' sortable='custom' width='70')
|
|
||||||
el-table-column(:label='$t("table.profile.invite_messages.message")' prop='message')
|
|
||||||
el-table-column(
|
|
||||||
:label='$t("table.profile.invite_messages.cool_down")'
|
|
||||||
prop='updatedAt'
|
|
||||||
sortable='custom'
|
|
||||||
width='110'
|
|
||||||
align='right')
|
|
||||||
template(#default='scope')
|
|
||||||
countdown-timer(:datetime='scope.row.updatedAt' :hours='1')
|
|
||||||
el-table-column(:label='$t("table.profile.invite_messages.action")' width='70' align='right')
|
|
||||||
template(#default='scope')
|
|
||||||
el-button(
|
|
||||||
type='text'
|
|
||||||
icon='el-icon-edit'
|
|
||||||
size='mini'
|
|
||||||
@click='showEditAndSendInviteResponseDialog("response", scope.row)')
|
|
||||||
template(#footer)
|
|
||||||
el-button(type='small' @click='cancelSendInviteResponse') {{ $t('dialog.invite_response_message.cancel') }}
|
|
||||||
el-button(type='small' @click='API.refreshInviteMessageTableData("response")') {{ $t('dialog.invite_response_message.refresh') }}
|
|
||||||
|
|
||||||
//- dialog Table: Send Invite Request Response Message
|
|
||||||
el-dialog.x-dialog(
|
|
||||||
:before-close='beforeDialogClose'
|
|
||||||
@mousedown.native='dialogMouseDown'
|
|
||||||
@mouseup.native='dialogMouseUp'
|
|
||||||
ref='sendInviteRequestResponseDialog'
|
|
||||||
:visible.sync='sendInviteRequestResponseDialogVisible'
|
|
||||||
:title='$t("dialog.invite_request_response_message.header")'
|
|
||||||
width='800px')
|
|
||||||
template(v-if='API.currentUser.$isVRCPlus')
|
|
||||||
input.inviteImageUploadButton(type='file' accept='image/*' @change='inviteImageUpload')
|
|
||||||
data-tables(
|
|
||||||
v-if='sendInviteRequestResponseDialogVisible'
|
|
||||||
v-bind='inviteRequestResponseMessageTable'
|
|
||||||
@row-click='showSendInviteResponseConfirmDialog'
|
|
||||||
style='margin-top: 10px; cursor: pointer')
|
|
||||||
el-table-column(:label='$t("table.profile.invite_messages.slot")' prop='slot' sortable='custom' width='70')
|
|
||||||
el-table-column(:label='$t("table.profile.invite_messages.message")' prop='message')
|
|
||||||
el-table-column(
|
|
||||||
:label='$t("table.profile.invite_messages.cool_down")'
|
|
||||||
prop='updatedAt'
|
|
||||||
sortable='custom'
|
|
||||||
width='110'
|
|
||||||
align='right')
|
|
||||||
template(#default='scope')
|
|
||||||
countdown-timer(:datetime='scope.row.updatedAt' :hours='1')
|
|
||||||
el-table-column(:label='$t("table.profile.invite_messages.action")' width='70' align='right')
|
|
||||||
template(#default='scope')
|
|
||||||
el-button(
|
|
||||||
type='text'
|
|
||||||
icon='el-icon-edit'
|
|
||||||
size='mini'
|
|
||||||
@click='showEditAndSendInviteResponseDialog("requestResponse", scope.row)')
|
|
||||||
template(#footer)
|
|
||||||
el-button(type='small' @click='cancelSendInviteRequestResponse') {{ $t('dialog.invite_request_response_message.cancel') }}
|
|
||||||
el-button(type='small' @click='API.refreshInviteMessageTableData("requestResponse")') {{ $t('dialog.invite_request_response_message.refresh') }}
|
|
||||||
|
|
||||||
//- dialog: Send Invite Response Message Confirm
|
|
||||||
el-dialog.x-dialog(
|
|
||||||
:before-close='beforeDialogClose'
|
|
||||||
@mousedown.native='dialogMouseDown'
|
|
||||||
@mouseup.native='dialogMouseUp'
|
|
||||||
ref='sendInviteResponseConfirmDialog'
|
|
||||||
:visible.sync='sendInviteResponseConfirmDialog.visible'
|
|
||||||
:title='$t("dialog.invite_response_message.header")'
|
|
||||||
width='400px')
|
|
||||||
div(style='font-size: 12px')
|
|
||||||
span {{ $t('dialog.invite_response_message.confirmation') }}
|
|
||||||
template(#footer)
|
|
||||||
el-button(type='small' @click='cancelInviteResponseConfirm') {{ $t('dialog.invite_response_message.cancel') }}
|
|
||||||
el-button(type='primary' size='small' @click='sendInviteResponseConfirm') {{ $t('dialog.invite_response_message.confirm') }}
|
|
||||||
|
|
||||||
//- dialog Table: Send Invite Message
|
|
||||||
el-dialog.x-dialog(
|
|
||||||
:before-close='beforeDialogClose'
|
|
||||||
@mousedown.native='dialogMouseDown'
|
|
||||||
@mouseup.native='dialogMouseUp'
|
|
||||||
ref='sendInviteDialog'
|
|
||||||
:visible.sync='sendInviteDialogVisible'
|
|
||||||
:title='$t("dialog.invite_message.header")'
|
|
||||||
width='800px')
|
|
||||||
template(v-if='API.currentUser.$isVRCPlus')
|
|
||||||
//- template(v-if="gallerySelectDialog.selectedFileId")
|
|
||||||
//- div(style="display:inline-block;flex:none;margin-right:5px")
|
|
||||||
//- el-popover(placement="right" width="500px" trigger="click")
|
|
||||||
//- img.x-link(slot="reference" v-lazy="gallerySelectDialog.selectedImageUrl" style="flex:none;width:60px;height:60px;border-radius:4px;object-fit:cover")
|
|
||||||
//- img.x-link(v-lazy="gallerySelectDialog.selectedImageUrl" style="height:500px" @click="showFullscreenImageDialog(gallerySelectDialog.selectedImageUrl)")
|
|
||||||
//- el-button(size="mini" @click="clearImageGallerySelect" style="vertical-align:top") {{ $t('dialog.invite_message.clear_selected_image') }}
|
|
||||||
//- template(v-else)
|
|
||||||
//- el-button(size="mini" @click="showGallerySelectDialog" style="margin-right:5px") {{ $t('dialog.invite_message.select_image') }}
|
|
||||||
input.inviteImageUploadButton(type='file' accept='image/*' @change='inviteImageUpload')
|
|
||||||
data-tables(
|
|
||||||
v-if='sendInviteDialogVisible'
|
|
||||||
v-bind='inviteMessageTable'
|
|
||||||
@row-click='showSendInviteConfirmDialog'
|
|
||||||
style='margin-top: 10px; cursor: pointer')
|
|
||||||
el-table-column(:label='$t("table.profile.invite_messages.slot")' prop='slot' sortable='custom' width='70')
|
|
||||||
el-table-column(:label='$t("table.profile.invite_messages.message")' prop='message')
|
|
||||||
el-table-column(
|
|
||||||
:label='$t("table.profile.invite_messages.cool_down")'
|
|
||||||
prop='updatedAt'
|
|
||||||
sortable='custom'
|
|
||||||
width='110'
|
|
||||||
align='right')
|
|
||||||
template(#default='scope')
|
|
||||||
countdown-timer(:datetime='scope.row.updatedAt' :hours='1')
|
|
||||||
el-table-column(:label='$t("table.profile.invite_messages.action")' width='70' align='right')
|
|
||||||
template(#default='scope')
|
|
||||||
el-button(
|
|
||||||
type='text'
|
|
||||||
icon='el-icon-edit'
|
|
||||||
size='mini'
|
|
||||||
@click='showEditAndSendInviteDialog("message", scope.row)')
|
|
||||||
template(#footer)
|
|
||||||
el-button(type='small' @click='cancelSendInvite') {{ $t('dialog.invite_message.cancel') }}
|
|
||||||
el-button(type='small' @click='API.refreshInviteMessageTableData("message")') {{ $t('dialog.invite_message.refresh') }}
|
|
||||||
|
|
||||||
//- dialog Table: Send Invite Request Message
|
|
||||||
el-dialog.x-dialog(
|
|
||||||
:before-close='beforeDialogClose'
|
|
||||||
@mousedown.native='dialogMouseDown'
|
|
||||||
@mouseup.native='dialogMouseUp'
|
|
||||||
ref='sendInviteRequestDialog'
|
|
||||||
:visible.sync='sendInviteRequestDialogVisible'
|
|
||||||
:title='$t("dialog.invite_request_message.header")'
|
|
||||||
width='800px')
|
|
||||||
template(v-if='API.currentUser.$isVRCPlus')
|
|
||||||
input.inviteImageUploadButton(type='file' accept='image/*' @change='inviteImageUpload')
|
|
||||||
data-tables(
|
|
||||||
v-if='sendInviteRequestDialogVisible'
|
|
||||||
v-bind='inviteRequestMessageTable'
|
|
||||||
@row-click='showSendInviteConfirmDialog'
|
|
||||||
style='margin-top: 10px; cursor: pointer')
|
|
||||||
el-table-column(:label='$t("table.profile.invite_messages.slot")' prop='slot' sortable='custom' width='70')
|
|
||||||
el-table-column(:label='$t("table.profile.invite_messages.message")' prop='message')
|
|
||||||
el-table-column(
|
|
||||||
:label='$t("table.profile.invite_messages.cool_down")'
|
|
||||||
prop='updatedAt'
|
|
||||||
sortable='custom'
|
|
||||||
width='110'
|
|
||||||
align='right')
|
|
||||||
template(#default='scope')
|
|
||||||
countdown-timer(:datetime='scope.row.updatedAt' :hours='1')
|
|
||||||
el-table-column(:label='$t("table.profile.invite_messages.action")' width='70' align='right')
|
|
||||||
template(#default='scope')
|
|
||||||
el-button(
|
|
||||||
type='text'
|
|
||||||
icon='el-icon-edit'
|
|
||||||
size='mini'
|
|
||||||
@click='showEditAndSendInviteDialog("request", scope.row)')
|
|
||||||
template(#footer)
|
|
||||||
el-button(type='small' @click='cancelSendInviteRequest') {{ $t('dialog.invite_request_message.cancel') }}
|
|
||||||
el-button(type='small' @click='API.refreshInviteMessageTableData("request")') {{ $t('dialog.invite_request_message.refresh') }}
|
|
||||||
|
|
||||||
//- dialog: Send Invite Message Confirm
|
|
||||||
el-dialog.x-dialog(
|
|
||||||
:before-close='beforeDialogClose'
|
|
||||||
@mousedown.native='dialogMouseDown'
|
|
||||||
@mouseup.native='dialogMouseUp'
|
|
||||||
ref='sendInviteConfirmDialog'
|
|
||||||
:visible.sync='sendInviteConfirmDialog.visible'
|
|
||||||
:title='$t("dialog.invite_message.header")'
|
|
||||||
width='400px')
|
|
||||||
div(style='font-size: 12px')
|
|
||||||
span {{ $t('dialog.invite_message.confirmation') }}
|
|
||||||
template(#footer)
|
|
||||||
el-button(type='small' @click='cancelInviteConfirm') {{ $t('dialog.invite_message.cancel') }}
|
|
||||||
el-button(type='primary' size='small' @click='sendInviteConfirm') {{ $t('dialog.invite_message.confirm') }}
|
|
||||||
|
|
||||||
//- dialog: Edit And Send Invite Message
|
|
||||||
el-dialog.x-dialog(
|
|
||||||
:before-close='beforeDialogClose'
|
|
||||||
@mousedown.native='dialogMouseDown'
|
|
||||||
@mouseup.native='dialogMouseUp'
|
|
||||||
ref='editAndSendInviteDialog'
|
|
||||||
:visible.sync='editAndSendInviteDialog.visible'
|
|
||||||
:title='$t("dialog.edit_send_invite_message.header")'
|
|
||||||
width='400px')
|
|
||||||
div(style='font-size: 12px')
|
|
||||||
span {{ $t('dialog.edit_send_invite_message.description') }}
|
|
||||||
el-input(
|
|
||||||
type='textarea'
|
|
||||||
v-model='editAndSendInviteDialog.newMessage'
|
|
||||||
size='mini'
|
|
||||||
maxlength='64'
|
|
||||||
show-word-limit
|
|
||||||
:autosize='{ minRows: 2, maxRows: 5 }'
|
|
||||||
placeholder=''
|
|
||||||
style='margin-top: 10px')
|
|
||||||
template(#footer)
|
|
||||||
el-button(type='small' @click='cancelEditAndSendInvite') {{ $t('dialog.edit_send_invite_message.cancel') }}
|
|
||||||
el-button(type='primary' size='small' @click='saveEditAndSendInvite') {{ $t('dialog.edit_send_invite_message.send') }}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,104 +0,0 @@
|
|||||||
mixin loginPage
|
|
||||||
.x-login-container(v-if='!API.isLoggedIn' v-loading='loginForm.loading')
|
|
||||||
.x-login
|
|
||||||
div(style='position: fixed; top: 0; left: 0; margin: 5px')
|
|
||||||
el-tooltip(placement='top' :content='$t("view.login.updater")' :disabled='hideTooltips')
|
|
||||||
el-button(type='default' @click='showVRCXUpdateDialog' size='mini' icon='el-icon-download' circle)
|
|
||||||
el-tooltip(placement='top' :content='$t("view.login.proxy_settings")' :disabled='hideTooltips')
|
|
||||||
el-button(
|
|
||||||
type='default'
|
|
||||||
@click='promptProxySettings'
|
|
||||||
size='mini'
|
|
||||||
icon='el-icon-connection'
|
|
||||||
style='margin-left: 5px'
|
|
||||||
circle)
|
|
||||||
|
|
||||||
.x-login-form-container
|
|
||||||
div
|
|
||||||
h2(style='font-weight: bold; text-align: center; margin: 0') {{ $t('view.login.login') }}
|
|
||||||
el-form(
|
|
||||||
ref='loginForm'
|
|
||||||
:model='loginForm'
|
|
||||||
:rules='loginForm.rules'
|
|
||||||
@submit.native.prevent='login()')
|
|
||||||
el-form-item(:label='$t("view.login.field.username")' prop='username' required)
|
|
||||||
el-input(
|
|
||||||
v-model='loginForm.username'
|
|
||||||
name='username'
|
|
||||||
:placeholder='$t("view.login.field.username")'
|
|
||||||
clearable)
|
|
||||||
el-form-item(
|
|
||||||
:label='$t("view.login.field.password")'
|
|
||||||
prop='password'
|
|
||||||
required
|
|
||||||
style='margin-top: 10px')
|
|
||||||
el-input(
|
|
||||||
type='password'
|
|
||||||
v-model='loginForm.password'
|
|
||||||
name='password'
|
|
||||||
:placeholder='$t("view.login.field.password")'
|
|
||||||
clearable
|
|
||||||
show-password)
|
|
||||||
el-checkbox(v-model='loginForm.saveCredentials' style='margin-top: 15px') {{ $t('view.login.field.saveCredentials') }}
|
|
||||||
el-checkbox(
|
|
||||||
v-model='enableCustomEndpoint'
|
|
||||||
@change='toggleCustomEndpoint'
|
|
||||||
style='margin-top: 10px') {{ $t('view.login.field.devEndpoint') }}
|
|
||||||
el-form-item(
|
|
||||||
v-if='enableCustomEndpoint'
|
|
||||||
:label='$t("view.login.field.endpoint")'
|
|
||||||
prop='endpoint'
|
|
||||||
style='margin-top: 10px')
|
|
||||||
el-input(
|
|
||||||
v-model='loginForm.endpoint'
|
|
||||||
name='endpoint'
|
|
||||||
:placeholder='API.endpointDomainVrchat'
|
|
||||||
clearable)
|
|
||||||
el-form-item(
|
|
||||||
v-if='enableCustomEndpoint'
|
|
||||||
:label='$t("view.login.field.websocket")'
|
|
||||||
prop='endpoint'
|
|
||||||
style='margin-top: 10px')
|
|
||||||
el-input(
|
|
||||||
v-model='loginForm.websocket'
|
|
||||||
name='websocket'
|
|
||||||
:placeholder='API.websocketDomainVrchat'
|
|
||||||
clearable)
|
|
||||||
el-form-item(style='margin-top: 15px')
|
|
||||||
el-button(native-type='submit' type='primary' style='width: 100%') {{ $t('view.login.login') }}
|
|
||||||
el-button(
|
|
||||||
type='primary'
|
|
||||||
@click='openExternalLink("https://vrchat.com/register")'
|
|
||||||
style='width: 100%') {{ $t('view.login.register') }}
|
|
||||||
|
|
||||||
hr.x-vertical-divider(v-if='Object.keys(loginForm.savedCredentials).length !== 0')/
|
|
||||||
|
|
||||||
div(v-if='Object.keys(loginForm.savedCredentials).length !== 0')
|
|
||||||
h2(style='font-weight: bold; text-align: center; margin: 0') {{ $t('view.login.savedAccounts') }}
|
|
||||||
.x-scroll-wrapper(style='margin-top: 10px')
|
|
||||||
.x-saved-account-list
|
|
||||||
.x-friend-item(
|
|
||||||
v-for='user in loginForm.savedCredentials'
|
|
||||||
:key='user.user.id'
|
|
||||||
@click='relogin(user)')
|
|
||||||
.avatar
|
|
||||||
img(v-lazy='userImage(user.user)')
|
|
||||||
.detail
|
|
||||||
span.name(v-text='user.user.displayName')
|
|
||||||
span.extra(v-text='user.user.username')
|
|
||||||
span.extra(v-text='user.loginParmas.endpoint')
|
|
||||||
el-button(
|
|
||||||
type='default'
|
|
||||||
@click.stop='deleteSavedLogin(user.user.id)'
|
|
||||||
size='mini'
|
|
||||||
icon='el-icon-delete'
|
|
||||||
style='margin-left: 10px'
|
|
||||||
circle)
|
|
||||||
|
|
||||||
.x-legal-notice-container
|
|
||||||
div(style='text-align: center; font-size: 12px')
|
|
||||||
p #[a.x-link(@click='openExternalLink("https://vrchat.com/home/password")') {{ $t('view.login.forgotPassword') }}]
|
|
||||||
p © 2019-2025 #[a.x-link(@click='openExternalLink("https://github.com/pypy-vrc")') pypy] & #[a.x-link(@click='openExternalLink("https://github.com/Natsumi-sama")') Natsumi]
|
|
||||||
p {{ $t('view.settings.general.legal_notice.info') }}
|
|
||||||
p {{ $t('view.settings.general.legal_notice.disclaimer1') }}
|
|
||||||
p {{ $t('view.settings.general.legal_notice.disclaimer2') }}
|
|
||||||
@@ -1,195 +0,0 @@
|
|||||||
mixin feedTab
|
|
||||||
.x-container(v-show='menuActiveIndex === "feed"')
|
|
||||||
div(style='margin: 0 0 10px; display: flex; align-items: center')
|
|
||||||
div(style='flex: none; margin-right: 10px; display: flex; align-items: center')
|
|
||||||
el-tooltip(
|
|
||||||
placement='bottom'
|
|
||||||
:content='$t("view.feed.favorites_only_tooltip")'
|
|
||||||
:disabled='hideTooltips')
|
|
||||||
el-switch(v-model='feedTable.vip' @change='feedTableLookup' active-color='#13ce66')
|
|
||||||
el-select(
|
|
||||||
v-model='feedTable.filter'
|
|
||||||
@change='feedTableLookup'
|
|
||||||
multiple
|
|
||||||
clearable
|
|
||||||
style='flex: 1; height: 40px'
|
|
||||||
:placeholder='$t("view.feed.filter_placeholder")')
|
|
||||||
el-option(
|
|
||||||
v-for='type in ["GPS", "Online", "Offline", "Status", "Avatar", "Bio"]'
|
|
||||||
:key='type'
|
|
||||||
:label='$t("view.feed.filters." + type)'
|
|
||||||
:value='type')
|
|
||||||
el-input(
|
|
||||||
v-model='feedTable.search'
|
|
||||||
:placeholder='$t("view.feed.search_placeholder")'
|
|
||||||
@keyup.native.13='feedTableLookup'
|
|
||||||
@change='feedTableLookup'
|
|
||||||
clearable
|
|
||||||
style='flex: none; width: 150px; margin: 0 10px')
|
|
||||||
data-tables(v-bind='feedTable' v-loading='feedTable.loading' lazy)
|
|
||||||
el-table-column(type='expand' width='20')
|
|
||||||
template(#default='scope')
|
|
||||||
div(style='position: relative; font-size: 14px')
|
|
||||||
template(v-if='scope.row.type === "GPS"')
|
|
||||||
location(
|
|
||||||
v-if='scope.row.previousLocation'
|
|
||||||
:location='scope.row.previousLocation'
|
|
||||||
style='display: inline-block')
|
|
||||||
el-tag(type='info' effect='plain' size='mini' style='margin-left: 5px') {{ timeToText(scope.row.time) }}
|
|
||||||
br
|
|
||||||
span(style='margin-right: 5px')
|
|
||||||
i.el-icon-right
|
|
||||||
location(
|
|
||||||
v-if='scope.row.location'
|
|
||||||
:location='scope.row.location'
|
|
||||||
:hint='scope.row.worldName'
|
|
||||||
:grouphint='scope.row.groupName')
|
|
||||||
template(v-else-if='scope.row.type === "Offline"')
|
|
||||||
template(v-if='scope.row.location')
|
|
||||||
location(
|
|
||||||
:location='scope.row.location'
|
|
||||||
:hint='scope.row.worldName'
|
|
||||||
:grouphint='scope.row.groupName')
|
|
||||||
el-tag(type='info' effect='plain' size='mini' style='margin-left: 5px') {{ timeToText(scope.row.time) }}
|
|
||||||
template(v-else-if='scope.row.type === "Online"')
|
|
||||||
location(
|
|
||||||
v-if='scope.row.location'
|
|
||||||
:location='scope.row.location'
|
|
||||||
:hint='scope.row.worldName'
|
|
||||||
:grouphint='scope.row.groupName')
|
|
||||||
template(v-else-if='scope.row.type === "Avatar"')
|
|
||||||
div(style='display: flex; align-items: center')
|
|
||||||
el-popover(placement='right' width='500px' trigger='click')
|
|
||||||
div(
|
|
||||||
slot='reference'
|
|
||||||
style='display: inline-block; vertical-align: top; width: 160px')
|
|
||||||
template(v-if='scope.row.previousCurrentAvatarThumbnailImageUrl')
|
|
||||||
img.x-link(
|
|
||||||
v-lazy='scope.row.previousCurrentAvatarThumbnailImageUrl'
|
|
||||||
style='flex: none; width: 160px; height: 120px; border-radius: 4px')
|
|
||||||
br
|
|
||||||
avatar-info(
|
|
||||||
:imageurl='scope.row.previousCurrentAvatarThumbnailImageUrl'
|
|
||||||
:userid='scope.row.userId'
|
|
||||||
:hintownerid='scope.row.previousOwnerId'
|
|
||||||
:hintavatarname='scope.row.previousAvatarName'
|
|
||||||
:avatartags='scope.row.previousCurrentAvatarTags')
|
|
||||||
img.x-link(
|
|
||||||
v-lazy='scope.row.previousCurrentAvatarImageUrl'
|
|
||||||
style='width: 500px; height: 375px'
|
|
||||||
@click='showFullscreenImageDialog(scope.row.previousCurrentAvatarImageUrl)')
|
|
||||||
span(style='position: relative; margin: 0 10px')
|
|
||||||
i.el-icon-right
|
|
||||||
el-popover(placement='right' width='500px' trigger='click')
|
|
||||||
div(
|
|
||||||
slot='reference'
|
|
||||||
style='display: inline-block; vertical-align: top; width: 160px')
|
|
||||||
template(v-if='scope.row.currentAvatarThumbnailImageUrl')
|
|
||||||
img.x-link(
|
|
||||||
v-lazy='scope.row.currentAvatarThumbnailImageUrl'
|
|
||||||
style='flex: none; width: 160px; height: 120px; border-radius: 4px')
|
|
||||||
br
|
|
||||||
avatar-info(
|
|
||||||
:imageurl='scope.row.currentAvatarThumbnailImageUrl'
|
|
||||||
:userid='scope.row.userId'
|
|
||||||
:hintownerid='scope.row.ownerId'
|
|
||||||
:hintavatarname='scope.row.avatarName'
|
|
||||||
:avatartags='scope.row.currentAvatarTags')
|
|
||||||
img.x-link(
|
|
||||||
v-lazy='scope.row.currentAvatarImageUrl'
|
|
||||||
style='width: 500px; height: 375px'
|
|
||||||
@click='showFullscreenImageDialog(scope.row.currentAvatarImageUrl)')
|
|
||||||
template(v-else-if='scope.row.type === "Status"')
|
|
||||||
el-tooltip(placement='top')
|
|
||||||
template(#content)
|
|
||||||
span(v-if='scope.row.previousStatus === "active"') {{ $t('dialog.user.status.active') }}
|
|
||||||
span(v-else-if='scope.row.previousStatus === "join me"') {{ $t('dialog.user.status.join_me') }}
|
|
||||||
span(v-else-if='scope.row.previousStatus === "ask me"') {{ $t('dialog.user.status.ask_me') }}
|
|
||||||
span(v-else-if='scope.row.previousStatus === "busy"') {{ $t('dialog.user.status.busy') }}
|
|
||||||
span(v-else) {{ $t('dialog.user.status.offline') }}
|
|
||||||
i.x-user-status(:class='statusClass(scope.row.previousStatus)')
|
|
||||||
span(v-text='scope.row.previousStatusDescription' style='margin-left: 5px')
|
|
||||||
br
|
|
||||||
span
|
|
||||||
i.el-icon-right
|
|
||||||
el-tooltip(placement='top')
|
|
||||||
template(#content)
|
|
||||||
span(v-if='scope.row.status === "active"') {{ $t('dialog.user.status.active') }}
|
|
||||||
span(v-else-if='scope.row.status === "join me"') {{ $t('dialog.user.status.join_me') }}
|
|
||||||
span(v-else-if='scope.row.status === "ask me"') {{ $t('dialog.user.status.ask_me') }}
|
|
||||||
span(v-else-if='scope.row.status === "busy"') {{ $t('dialog.user.status.busy') }}
|
|
||||||
span(v-else) {{ $t('dialog.user.status.offline') }}
|
|
||||||
i.x-user-status(:class='statusClass(scope.row.status)' style='margin: 0 5px')
|
|
||||||
span(v-text='scope.row.statusDescription')
|
|
||||||
template(v-else-if='scope.row.type === "Bio"')
|
|
||||||
pre(
|
|
||||||
v-html='formatDifference(scope.row.previousBio, scope.row.bio)'
|
|
||||||
style='font-family: inherit; font-size: 12px; white-space: pre-wrap; line-height: 25px; line-height: 22px')
|
|
||||||
el-table-column(:label='$t("table.feed.date")' prop='created_at' sortable='custom' width='120')
|
|
||||||
template(#default='scope')
|
|
||||||
el-tooltip(placement='right')
|
|
||||||
template(#content)
|
|
||||||
span {{ scope.row.created_at | formatDate('long') }}
|
|
||||||
span {{ scope.row.created_at | formatDate('short') }}
|
|
||||||
el-table-column(:label='$t("table.feed.type")' prop='type' width='80')
|
|
||||||
template(#default='scope')
|
|
||||||
span.x-link(v-text='$t("view.feed.filters." + scope.row.type)')
|
|
||||||
el-table-column(:label='$t("table.feed.user")' prop='displayName' width='180')
|
|
||||||
template(#default='scope')
|
|
||||||
span.x-link(
|
|
||||||
v-text='scope.row.displayName'
|
|
||||||
@click='showUserDialog(scope.row.userId)'
|
|
||||||
style='padding-right: 10px')
|
|
||||||
el-table-column(:label='$t("table.feed.detail")')
|
|
||||||
template(#default='scope')
|
|
||||||
template(v-if='scope.row.type === "GPS"')
|
|
||||||
location(
|
|
||||||
v-if='scope.row.location'
|
|
||||||
:location='scope.row.location'
|
|
||||||
:hint='scope.row.worldName'
|
|
||||||
:grouphint='scope.row.groupName')
|
|
||||||
template(v-else-if='scope.row.type === "Offline" || scope.row.type === "Online"')
|
|
||||||
location(
|
|
||||||
v-if='scope.row.location'
|
|
||||||
:location='scope.row.location'
|
|
||||||
:hint='scope.row.worldName'
|
|
||||||
:grouphint='scope.row.groupName')
|
|
||||||
template(v-else-if='scope.row.type === "Status"')
|
|
||||||
template(v-if='scope.row.statusDescription === scope.row.previousStatusDescription')
|
|
||||||
el-tooltip(placement='top')
|
|
||||||
template(#content)
|
|
||||||
span(v-if='scope.row.previousStatus === "active"') {{ $t('dialog.user.status.active') }}
|
|
||||||
span(v-else-if='scope.row.previousStatus === "join me"') {{ $t('dialog.user.status.join_me') }}
|
|
||||||
span(v-else-if='scope.row.previousStatus === "ask me"') {{ $t('dialog.user.status.ask_me') }}
|
|
||||||
span(v-else-if='scope.row.previousStatus === "busy"') {{ $t('dialog.user.status.busy') }}
|
|
||||||
span(v-else) {{ $t('dialog.user.status.offline') }}
|
|
||||||
i.x-user-status(:class='statusClass(scope.row.previousStatus)')
|
|
||||||
span(style='margin: 0 5px')
|
|
||||||
i.el-icon-right
|
|
||||||
el-tooltip(placement='top')
|
|
||||||
template(#content)
|
|
||||||
span(v-if='scope.row.status === "active"') {{ $t('dialog.user.status.active') }}
|
|
||||||
span(v-else-if='scope.row.status === "join me"') {{ $t('dialog.user.status.join_me') }}
|
|
||||||
span(v-else-if='scope.row.status === "ask me"') {{ $t('dialog.user.status.ask_me') }}
|
|
||||||
span(v-else-if='scope.row.status === "busy"') {{ $t('dialog.user.status.busy') }}
|
|
||||||
span(v-else) {{ $t('dialog.user.status.offline') }}
|
|
||||||
i.x-user-status(:class='statusClass(scope.row.status)')
|
|
||||||
template(v-else)
|
|
||||||
el-tooltip(placement='top')
|
|
||||||
template(#content)
|
|
||||||
span(v-if='scope.row.status === "active"') {{ $t('dialog.user.status.active') }}
|
|
||||||
span(v-else-if='scope.row.status === "join me"') {{ $t('dialog.user.status.join_me') }}
|
|
||||||
span(v-else-if='scope.row.status === "ask me"') {{ $t('dialog.user.status.ask_me') }}
|
|
||||||
span(v-else-if='scope.row.status === "busy"') {{ $t('dialog.user.status.busy') }}
|
|
||||||
span(v-else) {{ $t('dialog.user.status.offline') }}
|
|
||||||
i.x-user-status(:class='statusClass(scope.row.status)' style='margin-right: 3px')
|
|
||||||
span(v-text='scope.row.statusDescription')
|
|
||||||
template(v-else-if='scope.row.type === "Avatar"')
|
|
||||||
avatar-info(
|
|
||||||
:imageurl='scope.row.currentAvatarImageUrl'
|
|
||||||
:userid='scope.row.userId'
|
|
||||||
:hintownerid='scope.row.ownerId'
|
|
||||||
:hintavatarname='scope.row.avatarName'
|
|
||||||
:avatartags='scope.row.currentAvatarTags')
|
|
||||||
template(v-else-if='scope.row.type === "Bio"')
|
|
||||||
span(v-text='scope.row.bio')
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
mixin friendLogTab
|
|
||||||
.x-container(v-if='menuActiveIndex === "friendLog"')
|
|
||||||
data-tables(v-bind='friendLogTable' ref='friendLogTableRef')
|
|
||||||
template(#tool)
|
|
||||||
div(style='margin: 0 0 10px; display: flex; align-items: center')
|
|
||||||
el-select(
|
|
||||||
v-model='friendLogTable.filters[0].value'
|
|
||||||
@change='saveTableFilters'
|
|
||||||
multiple
|
|
||||||
clearable
|
|
||||||
style='flex: 1'
|
|
||||||
:placeholder='$t("view.friend_log.filter_placeholder")')
|
|
||||||
el-option(
|
|
||||||
v-for='type in ["Friend", "Unfriend", "FriendRequest", "CancelFriendRequest", "DisplayName", "TrustLevel"]'
|
|
||||||
:key='type'
|
|
||||||
:label='$t("view.friend_log.filters." + type)'
|
|
||||||
:value='type')
|
|
||||||
el-input(
|
|
||||||
v-model='friendLogTable.filters[1].value'
|
|
||||||
:placeholder='$t("view.friend_log.search_placeholder")'
|
|
||||||
style='flex: none; width: 150px; margin-left: 10px')
|
|
||||||
el-table-column(:label='$t("table.friendLog.date")' prop='created_at' sortable='custom' width='200')
|
|
||||||
template(#default='scope')
|
|
||||||
el-tooltip(placement='right')
|
|
||||||
template(#content)
|
|
||||||
span {{ scope.row.created_at | formatDate('long') }}
|
|
||||||
span {{ scope.row.created_at | formatDate('short') }}
|
|
||||||
el-table-column(:label='$t("table.friendLog.type")' prop='type' width='150')
|
|
||||||
template(#default='scope')
|
|
||||||
span(v-text='$t("view.friend_log.filters." + scope.row.type)')
|
|
||||||
el-table-column(:label='$t("table.friendLog.user")' prop='displayName')
|
|
||||||
template(#default='scope')
|
|
||||||
span(v-if='scope.row.type === "DisplayName"') {{ scope.row.previousDisplayName }} #[i.el-icon-right]
|
|
||||||
span.x-link(
|
|
||||||
v-text='scope.row.displayName || scope.row.userId'
|
|
||||||
@click='showUserDialog(scope.row.userId)'
|
|
||||||
style='padding-right: 10px')
|
|
||||||
template(v-if='scope.row.type === "TrustLevel"')
|
|
||||||
span ({{ scope.row.previousTrustLevel }} #[i.el-icon-right] {{ scope.row.trustLevel }})
|
|
||||||
el-table-column(:label='$t("table.friendLog.action")' width='80' align='right')
|
|
||||||
template(#default='scope')
|
|
||||||
el-button(
|
|
||||||
v-if='shiftHeld'
|
|
||||||
style='color: #f56c6c'
|
|
||||||
type='text'
|
|
||||||
icon='el-icon-close'
|
|
||||||
size='mini'
|
|
||||||
@click='deleteFriendLog(scope.row)')
|
|
||||||
el-button(
|
|
||||||
v-else
|
|
||||||
type='text'
|
|
||||||
icon='el-icon-delete'
|
|
||||||
size='mini'
|
|
||||||
@click='deleteFriendLogPrompt(scope.row)')
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
mixin gameLogTab
|
|
||||||
.x-container(v-show='menuActiveIndex === "gameLog"')
|
|
||||||
data-tables(v-bind='gameLogTable' v-loading='gameLogTable.loading')
|
|
||||||
template(#tool)
|
|
||||||
div(style='margin: 0 0 10px; display: flex; align-items: center')
|
|
||||||
div(style='flex: none; margin-right: 10px; display: flex; align-items: center')
|
|
||||||
el-tooltip(
|
|
||||||
placement='bottom'
|
|
||||||
:content='$t("view.feed.favorites_only_tooltip")'
|
|
||||||
:disabled='hideTooltips')
|
|
||||||
el-switch(v-model='gameLogTable.vip' @change='gameLogTableLookup' active-color='#13ce66')
|
|
||||||
el-select(
|
|
||||||
v-model='gameLogTable.filter'
|
|
||||||
@change='gameLogTableLookup'
|
|
||||||
multiple
|
|
||||||
clearable
|
|
||||||
style='flex: 1'
|
|
||||||
:placeholder='$t("view.game_log.filter_placeholder")')
|
|
||||||
el-option(
|
|
||||||
v-for='type in ["Location", "OnPlayerJoined", "OnPlayerLeft", "VideoPlay", "Event", "External", "StringLoad", "ImageLoad"]'
|
|
||||||
:key='type'
|
|
||||||
:label='$t("view.game_log.filters." + type)'
|
|
||||||
:value='type')
|
|
||||||
el-input(
|
|
||||||
v-model='gameLogTable.search'
|
|
||||||
:placeholder='$t("view.game_log.search_placeholder")'
|
|
||||||
@keyup.native.13='gameLogTableLookup'
|
|
||||||
@change='gameLogTableLookup'
|
|
||||||
clearable
|
|
||||||
style='flex: none; width: 150px; margin: 0 10px')
|
|
||||||
el-table-column(:label='$t("table.gameLog.date")' prop='created_at' sortable='custom' width='120')
|
|
||||||
template(#default='scope')
|
|
||||||
el-tooltip(placement='right')
|
|
||||||
template(#content)
|
|
||||||
span {{ scope.row.created_at | formatDate('long') }}
|
|
||||||
span {{ scope.row.created_at | formatDate('short') }}
|
|
||||||
el-table-column(:label='$t("table.gameLog.type")' prop='type' width='120')
|
|
||||||
template(#default='scope')
|
|
||||||
el-tooltip(placement='right' :open-delay='500' :disabled='hideTooltips')
|
|
||||||
template(#content)
|
|
||||||
span {{ $t('view.game_log.filters.' + scope.row.type) }}
|
|
||||||
span.x-link(
|
|
||||||
v-if='scope.row.location && scope.row.type !== "Location"'
|
|
||||||
v-text='$t("view.game_log.filters." + scope.row.type)'
|
|
||||||
@click='showWorldDialog(scope.row.location)')
|
|
||||||
span(v-else v-text='$t("view.game_log.filters." + scope.row.type)')
|
|
||||||
el-table-column(:label='$t("table.gameLog.icon")' prop='isFriend' width='70' align='center')
|
|
||||||
template(#default='scope')
|
|
||||||
template(v-if='gameLogIsFriend(scope.row)')
|
|
||||||
el-tooltip(v-if='gameLogIsFavorite(scope.row)' placement='top' content='Favorite')
|
|
||||||
span ⭐
|
|
||||||
el-tooltip(v-else placement='top' content='Friend')
|
|
||||||
span 💚
|
|
||||||
el-table-column(:label='$t("table.gameLog.user")' prop='displayName' width='180')
|
|
||||||
template(#default='scope')
|
|
||||||
span.x-link(
|
|
||||||
v-if='scope.row.displayName'
|
|
||||||
v-text='scope.row.displayName'
|
|
||||||
@click='lookupUser(scope.row)'
|
|
||||||
style='padding-right: 10px')
|
|
||||||
el-table-column(:label='$t("table.gameLog.detail")' prop='data')
|
|
||||||
template(#default='scope')
|
|
||||||
location(
|
|
||||||
v-if='scope.row.type === "Location"'
|
|
||||||
:location='scope.row.location'
|
|
||||||
:hint='scope.row.worldName'
|
|
||||||
:grouphint='scope.row.groupName')
|
|
||||||
location(
|
|
||||||
v-else-if='scope.row.type === "PortalSpawn"'
|
|
||||||
:location='scope.row.instanceId'
|
|
||||||
:hint='scope.row.worldName'
|
|
||||||
:grouphint='scope.row.groupName')
|
|
||||||
template(v-else-if='scope.row.type === "Event"')
|
|
||||||
span(v-text='scope.row.data')
|
|
||||||
template(v-else-if='scope.row.type === "External"')
|
|
||||||
span(v-text='scope.row.message')
|
|
||||||
template(v-else-if='scope.row.type === "VideoPlay"')
|
|
||||||
span(v-if='scope.row.videoId' style='margin-right: 5px') {{ scope.row.videoId }}:
|
|
||||||
span(v-if='scope.row.videoId === "LSMedia"' v-text='scope.row.videoName')
|
|
||||||
span.x-link(
|
|
||||||
v-else-if='scope.row.videoName'
|
|
||||||
@click='openExternalLink(scope.row.videoUrl)'
|
|
||||||
v-text='scope.row.videoName')
|
|
||||||
span.x-link(v-else @click='openExternalLink(scope.row.videoUrl)' v-text='scope.row.videoUrl')
|
|
||||||
template(v-else-if='scope.row.type === "ImageLoad"')
|
|
||||||
span.x-link(@click='openExternalLink(scope.row.resourceUrl)' v-text='scope.row.resourceUrl')
|
|
||||||
template(v-else-if='scope.row.type === "StringLoad"')
|
|
||||||
span.x-link(@click='openExternalLink(scope.row.resourceUrl)' v-text='scope.row.resourceUrl')
|
|
||||||
template(
|
|
||||||
v-else-if='scope.row.type === "Notification" || scope.row.type === "OnPlayerJoined" || scope.row.type === "OnPlayerLeft"')
|
|
||||||
span.x-link(v-else v-text='scope.row.data')
|
|
||||||
el-table-column(:label='$t("table.gameLog.action")' width='80' align='right')
|
|
||||||
template(#default='scope')
|
|
||||||
template(
|
|
||||||
v-if='scope.row.type !== "OnPlayerJoined" && scope.row.type !== "OnPlayerLeft" && scope.row.type !== "Location" && scope.row.type !== "PortalSpawn"')
|
|
||||||
el-button(
|
|
||||||
v-if='shiftHeld'
|
|
||||||
style='color: #f56c6c'
|
|
||||||
type='text'
|
|
||||||
icon='el-icon-close'
|
|
||||||
size='mini'
|
|
||||||
@click='deleteGameLogEntry(scope.row)')
|
|
||||||
el-button(
|
|
||||||
v-else
|
|
||||||
type='text'
|
|
||||||
icon='el-icon-delete'
|
|
||||||
size='mini'
|
|
||||||
@click='deleteGameLogEntryPrompt(scope.row)')
|
|
||||||
el-tooltip(
|
|
||||||
placement='top'
|
|
||||||
:content='$t("dialog.previous_instances.info")'
|
|
||||||
:disabled='hideTooltips')
|
|
||||||
el-button(
|
|
||||||
v-if='scope.row.type === "Location"'
|
|
||||||
type='text'
|
|
||||||
icon='el-icon-s-data'
|
|
||||||
size='mini'
|
|
||||||
@click='showPreviousInstancesInfoDialog(scope.row.location)')
|
|
||||||
@@ -1,261 +0,0 @@
|
|||||||
mixin notificationsTab
|
|
||||||
.x-container(v-if='menuActiveIndex === "notification"' v-loading='API.isNotificationsLoading')
|
|
||||||
data-tables.notification-table(v-bind='notificationTable' ref='notificationTableRef')
|
|
||||||
template(#tool)
|
|
||||||
div(style='margin: 0 0 10px; display: flex; align-items: center')
|
|
||||||
el-select(
|
|
||||||
v-model='notificationTable.filters[0].value'
|
|
||||||
@change='saveTableFilters'
|
|
||||||
multiple
|
|
||||||
clearable
|
|
||||||
style='flex: 1'
|
|
||||||
:placeholder='$t("view.notification.filter_placeholder")')
|
|
||||||
el-option(
|
|
||||||
v-for='type in ["requestInvite", "invite", "requestInviteResponse", "inviteResponse", "friendRequest", "ignoredFriendRequest", "message", "boop", "groupChange", "group.announcement", "group.informative", "group.invite", "group.joinRequest", "group.transfer", "group.queueReady", "moderation.warning.group", "moderation.report.closed", "instance.closed"]'
|
|
||||||
:key='type'
|
|
||||||
:label='$t("view.notification.filters." + type)'
|
|
||||||
:value='type')
|
|
||||||
el-input(
|
|
||||||
v-model='notificationTable.filters[1].value'
|
|
||||||
:placeholder='$t("view.notification.search_placeholder")'
|
|
||||||
style='flex: none; width: 150px; margin: 0 10px')
|
|
||||||
el-tooltip(
|
|
||||||
placement='bottom'
|
|
||||||
:content='$t("view.notification.refresh_tooltip")'
|
|
||||||
:disabled='hideTooltips')
|
|
||||||
el-button(
|
|
||||||
type='default'
|
|
||||||
:loading='API.isNotificationsLoading'
|
|
||||||
@click='API.refreshNotifications()'
|
|
||||||
icon='el-icon-refresh'
|
|
||||||
circle
|
|
||||||
style='flex: none')
|
|
||||||
el-table-column(:label='$t("table.notification.date")' prop='created_at' sortable='custom' width='120')
|
|
||||||
template(#default='scope')
|
|
||||||
el-tooltip(placement='right')
|
|
||||||
template(#content)
|
|
||||||
span {{ scope.row.created_at | formatDate('long') }}
|
|
||||||
span {{ scope.row.created_at | formatDate('short') }}
|
|
||||||
el-table-column(:label='$t("table.notification.type")' prop='type' width='180')
|
|
||||||
template(#default='scope')
|
|
||||||
span.x-link(
|
|
||||||
v-if='scope.row.type === "invite"'
|
|
||||||
v-text='$t("view.notification.filters." + scope.row.type)'
|
|
||||||
@click='showWorldDialog(scope.row.details.worldId)')
|
|
||||||
el-tooltip(
|
|
||||||
v-else-if='scope.row.type === "group.queueReady" || scope.row.type === "instance.closed"'
|
|
||||||
placement='top')
|
|
||||||
template(#content)
|
|
||||||
location(
|
|
||||||
v-if='scope.row.location'
|
|
||||||
:location='scope.row.location'
|
|
||||||
:hint='scope.row.worldName'
|
|
||||||
:grouphint='scope.row.groupName'
|
|
||||||
:link='false')
|
|
||||||
span.x-link(
|
|
||||||
v-text='$t("view.notification.filters." + scope.row.type)'
|
|
||||||
@click='showWorldDialog(scope.row.location)')
|
|
||||||
el-tooltip(
|
|
||||||
v-else-if='scope.row.link'
|
|
||||||
placement='top'
|
|
||||||
:content='scope.row.linkText'
|
|
||||||
:disabled='hideTooltips')
|
|
||||||
span.x-link(
|
|
||||||
v-text='$t("view.notification.filters." + scope.row.type)'
|
|
||||||
@click='openNotificationLink(scope.row.link)')
|
|
||||||
span(v-else v-text='$t("view.notification.filters." + scope.row.type)')
|
|
||||||
el-table-column(:label='$t("table.notification.user_group")' prop='senderUsername' width='150')
|
|
||||||
template(#default='scope')
|
|
||||||
template(v-if='scope.row.type === "groupChange"')
|
|
||||||
span.x-link(v-text='scope.row.senderUsername' @click='showGroupDialog(scope.row.senderUserId)')
|
|
||||||
template(v-else-if='scope.row.senderUserId')
|
|
||||||
span.x-link(v-text='scope.row.senderUsername' @click='showUserDialog(scope.row.senderUserId)')
|
|
||||||
template(v-else-if='scope.row.link && scope.row.data?.groupName')
|
|
||||||
span.x-link(v-text='scope.row.data?.groupName' @click='openNotificationLink(scope.row.link)')
|
|
||||||
template(v-else-if='scope.row.link')
|
|
||||||
span.x-link(v-text='scope.row.linkText' @click='openNotificationLink(scope.row.link)')
|
|
||||||
el-table-column(:label='$t("table.notification.photo")' width='100' prop='photo')
|
|
||||||
template(#default='scope')
|
|
||||||
template(v-if='scope.row.details && scope.row.details.imageUrl')
|
|
||||||
el-popover(placement='right' width='500px' trigger='click')
|
|
||||||
img.x-link(
|
|
||||||
slot='reference'
|
|
||||||
:src='getSmallThumbnailUrl(scope.row.details.imageUrl)'
|
|
||||||
style='flex: none; height: 50px; border-radius: 4px')
|
|
||||||
img.x-link(
|
|
||||||
v-lazy='scope.row.details.imageUrl'
|
|
||||||
style='width: 500px'
|
|
||||||
@click='showFullscreenImageDialog(scope.row.details.imageUrl)')
|
|
||||||
template(v-else-if='scope.row.imageUrl')
|
|
||||||
el-popover(placement='right' width='500px' trigger='click')
|
|
||||||
img.x-link(
|
|
||||||
slot='reference'
|
|
||||||
:src='getSmallThumbnailUrl(scope.row.imageUrl)'
|
|
||||||
style='flex: none; height: 50px; border-radius: 4px')
|
|
||||||
img.x-link(
|
|
||||||
v-lazy='scope.row.imageUrl'
|
|
||||||
style='width: 500px'
|
|
||||||
@click='showFullscreenImageDialog(scope.row.imageUrl)')
|
|
||||||
el-table-column(:label='$t("table.notification.message")' prop='message')
|
|
||||||
template(#default='scope')
|
|
||||||
span.x-link(v-if='scope.row.type === "invite"' style='display: flex')
|
|
||||||
location(
|
|
||||||
v-if='scope.row.details'
|
|
||||||
:location='scope.row.details.worldId'
|
|
||||||
:hint='scope.row.details.worldName'
|
|
||||||
:grouphint='scope.row.details.groupName'
|
|
||||||
:link='true')
|
|
||||||
br
|
|
||||||
el-tooltip(
|
|
||||||
v-if='scope.row.message && scope.row.message !== `This is a generated invite to ${scope.row.details?.worldName}`'
|
|
||||||
placement='top')
|
|
||||||
template(#content)
|
|
||||||
pre.extra(
|
|
||||||
style='display: inline-block; vertical-align: top; font-family: inherit; font-size: 12px; white-space: pre-wrap; margin: 0') {{ scope.row.message || '-' }}
|
|
||||||
div(v-text='scope.row.message')
|
|
||||||
span(
|
|
||||||
v-else-if='scope.row.details && scope.row.details.inviteMessage'
|
|
||||||
v-text='scope.row.details.inviteMessage')
|
|
||||||
span(
|
|
||||||
v-else-if='scope.row.details && scope.row.details.requestMessage'
|
|
||||||
v-text='scope.row.details.requestMessage')
|
|
||||||
span(
|
|
||||||
v-else-if='scope.row.details && scope.row.details.responseMessage'
|
|
||||||
v-text='scope.row.details.responseMessage')
|
|
||||||
el-table-column(:label='$t("table.notification.action")' width='100' align='right')
|
|
||||||
template(#default='scope')
|
|
||||||
template(v-if='scope.row.senderUserId !== API.currentUser.id && !scope.row.$isExpired')
|
|
||||||
template(v-if='scope.row.type === "friendRequest"')
|
|
||||||
el-tooltip(placement='top' content='Accept' :disabled='hideTooltips')
|
|
||||||
el-button(
|
|
||||||
type='text'
|
|
||||||
icon='el-icon-check'
|
|
||||||
style='color: #67c23a'
|
|
||||||
size='mini'
|
|
||||||
@click='acceptFriendRequestNotification(scope.row)')
|
|
||||||
template(v-else-if='scope.row.type === "invite"')
|
|
||||||
el-tooltip(placement='top' content='Decline with message' :disabled='hideTooltips')
|
|
||||||
el-button(
|
|
||||||
type='text'
|
|
||||||
icon='el-icon-chat-line-square'
|
|
||||||
size='mini'
|
|
||||||
@click='showSendInviteResponseDialog(scope.row)')
|
|
||||||
template(v-else-if='scope.row.type === "requestInvite"')
|
|
||||||
template(
|
|
||||||
v-if='lastLocation.location && isGameRunning && checkCanInvite(lastLocation.location)')
|
|
||||||
el-tooltip(placement='top' content='Invite' :disabled='hideTooltips')
|
|
||||||
el-button(
|
|
||||||
type='text'
|
|
||||||
icon='el-icon-check'
|
|
||||||
style='color: #67c23a'
|
|
||||||
size='mini'
|
|
||||||
@click='acceptRequestInvite(scope.row)')
|
|
||||||
el-tooltip(placement='top' content='Decline with message' :disabled='hideTooltips')
|
|
||||||
el-button(
|
|
||||||
type='text'
|
|
||||||
icon='el-icon-chat-line-square'
|
|
||||||
size='mini'
|
|
||||||
style='margin-left: 5px'
|
|
||||||
@click='showSendInviteRequestResponseDialog(scope.row)')
|
|
||||||
template(v-if='scope.row.responses')
|
|
||||||
template(v-for='response in scope.row.responses')
|
|
||||||
el-tooltip(placement='top' :content='response.text' :disabled='hideTooltips')
|
|
||||||
el-button(
|
|
||||||
v-if='response.icon === "check"'
|
|
||||||
type='text'
|
|
||||||
icon='el-icon-check'
|
|
||||||
size='mini'
|
|
||||||
style='margin-left: 5px'
|
|
||||||
@click='sendNotificationResponse(scope.row.id, scope.row.responses, response.type)')
|
|
||||||
el-button(
|
|
||||||
v-else-if='response.icon === "cancel"'
|
|
||||||
type='text'
|
|
||||||
icon='el-icon-close'
|
|
||||||
size='mini'
|
|
||||||
style='margin-left: 5px'
|
|
||||||
@click='sendNotificationResponse(scope.row.id, scope.row.responses, response.type)')
|
|
||||||
el-button(
|
|
||||||
v-else-if='response.icon === "ban"'
|
|
||||||
type='text'
|
|
||||||
icon='el-icon-circle-close'
|
|
||||||
size='mini'
|
|
||||||
style='margin-left: 5px'
|
|
||||||
@click='sendNotificationResponse(scope.row.id, scope.row.responses, response.type)')
|
|
||||||
el-button(
|
|
||||||
v-else-if='response.icon === "bell-slash"'
|
|
||||||
type='text'
|
|
||||||
icon='el-icon-bell'
|
|
||||||
size='mini'
|
|
||||||
style='margin-left: 5px'
|
|
||||||
@click='sendNotificationResponse(scope.row.id, scope.row.responses, response.type)')
|
|
||||||
el-button(
|
|
||||||
v-else-if='response.icon === "reply" && scope.row.type === "boop"'
|
|
||||||
type='text'
|
|
||||||
icon='el-icon-chat-line-square'
|
|
||||||
size='mini'
|
|
||||||
style='margin-left: 5px'
|
|
||||||
@click='showSendBoopDialog(scope.row.senderUserId)')
|
|
||||||
el-button(
|
|
||||||
v-else-if='response.icon === "reply"'
|
|
||||||
type='text'
|
|
||||||
icon='el-icon-chat-line-square'
|
|
||||||
size='mini'
|
|
||||||
style='margin-left: 5px'
|
|
||||||
@click='sendNotificationResponse(scope.row.id, scope.row.responses, response.type)')
|
|
||||||
el-button(
|
|
||||||
v-else
|
|
||||||
type='text'
|
|
||||||
icon='el-icon-collection-tag'
|
|
||||||
size='mini'
|
|
||||||
style='margin-left: 5px'
|
|
||||||
@click='sendNotificationResponse(scope.row.id, scope.row.responses, response.type)')
|
|
||||||
template(
|
|
||||||
v-if='scope.row.type !== "requestInviteResponse" && scope.row.type !== "inviteResponse" && scope.row.type !== "message" && scope.row.type !== "boop" && scope.row.type !== "groupChange" && !scope.row.type.includes("group.") && !scope.row.type.includes("moderation.") && !scope.row.type.includes("instance.")')
|
|
||||||
el-tooltip(placement='top' content='Decline' :disabled='hideTooltips')
|
|
||||||
el-button(
|
|
||||||
v-if='shiftHeld'
|
|
||||||
style='color: #f56c6c; margin-left: 5px'
|
|
||||||
type='text'
|
|
||||||
icon='el-icon-close'
|
|
||||||
size='mini'
|
|
||||||
@click='hideNotification(scope.row)')
|
|
||||||
el-button(
|
|
||||||
v-else
|
|
||||||
type='text'
|
|
||||||
icon='el-icon-close'
|
|
||||||
size='mini'
|
|
||||||
style='margin-left: 5px'
|
|
||||||
@click='hideNotificationPrompt(scope.row)')
|
|
||||||
template(v-if='scope.row.type === "group.queueReady"')
|
|
||||||
el-tooltip(placement='top' content='Delete log' :disabled='hideTooltips')
|
|
||||||
el-button(
|
|
||||||
v-if='shiftHeld'
|
|
||||||
style='color: #f56c6c; margin-left: 5px'
|
|
||||||
type='text'
|
|
||||||
icon='el-icon-close'
|
|
||||||
size='mini'
|
|
||||||
@click='deleteNotificationLog(scope.row)')
|
|
||||||
el-button(
|
|
||||||
v-else
|
|
||||||
type='text'
|
|
||||||
icon='el-icon-delete'
|
|
||||||
size='mini'
|
|
||||||
style='margin-left: 5px'
|
|
||||||
@click='deleteNotificationLogPrompt(scope.row)')
|
|
||||||
template(
|
|
||||||
v-if='scope.row.type !== "friendRequest" && scope.row.type !== "ignoredFriendRequest" && !scope.row.type.includes("group.") && !scope.row.type.includes("moderation.")')
|
|
||||||
el-tooltip(placement='top' content='Delete log' :disabled='hideTooltips')
|
|
||||||
el-button(
|
|
||||||
v-if='shiftHeld'
|
|
||||||
style='color: #f56c6c; margin-left: 5px'
|
|
||||||
type='text'
|
|
||||||
icon='el-icon-close'
|
|
||||||
size='mini'
|
|
||||||
@click='deleteNotificationLog(scope.row)')
|
|
||||||
el-button(
|
|
||||||
v-else
|
|
||||||
type='text'
|
|
||||||
icon='el-icon-delete'
|
|
||||||
size='mini'
|
|
||||||
style='margin-left: 5px'
|
|
||||||
@click='deleteNotificationLogPrompt(scope.row)')
|
|
||||||
@@ -1,479 +0,0 @@
|
|||||||
mixin playerListTab
|
|
||||||
.x-container(v-show='menuActiveIndex === "playerList"' style='padding-top: 5px')
|
|
||||||
div(style='display: flex; flex-direction: column; height: 100%')
|
|
||||||
div(v-if='currentInstanceWorld.ref.id' style='display: flex')
|
|
||||||
el-popover(placement='right' width='500px' trigger='click' style='height: 120px')
|
|
||||||
img.x-link(
|
|
||||||
slot='reference'
|
|
||||||
v-lazy='currentInstanceWorld.ref.thumbnailImageUrl'
|
|
||||||
style='flex: none; width: 160px; height: 120px; border-radius: 4px')
|
|
||||||
img.x-link(
|
|
||||||
v-lazy='currentInstanceWorld.ref.imageUrl'
|
|
||||||
style='width: 500px; height: 375px'
|
|
||||||
@click='showFullscreenImageDialog(currentInstanceWorld.ref.imageUrl)')
|
|
||||||
div(style='margin-left: 10px; display: flex; flex-direction: column; min-width: 320px; width: 100%')
|
|
||||||
div
|
|
||||||
span.x-link(
|
|
||||||
@click='showWorldDialog(currentInstanceWorld.ref.id)'
|
|
||||||
style='font-weight: bold; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 1')
|
|
||||||
| #[i.el-icon-s-home(v-show='API.currentUser.$homeLocation && API.currentUser.$homeLocation.worldId === currentInstanceWorld.ref.id' style='margin-right: 5px')] {{ currentInstanceWorld.ref.name }}
|
|
||||||
div
|
|
||||||
span.x-link.x-grey(
|
|
||||||
v-text='currentInstanceWorld.ref.authorName'
|
|
||||||
@click='showUserDialog(currentInstanceWorld.ref.authorId)'
|
|
||||||
style='font-family: monospace')
|
|
||||||
div(style='margin-top: 5px')
|
|
||||||
el-tag(
|
|
||||||
v-if='currentInstanceWorld.ref.$isLabs'
|
|
||||||
type='primary'
|
|
||||||
effect='plain'
|
|
||||||
size='mini'
|
|
||||||
style='margin-right: 5px') {{ $t('dialog.world.tags.labs') }}
|
|
||||||
el-tag(
|
|
||||||
v-else-if='currentInstanceWorld.ref.releaseStatus === "public"'
|
|
||||||
type='success'
|
|
||||||
effect='plain'
|
|
||||||
size='mini'
|
|
||||||
style='margin-right: 5px') {{ $t('dialog.world.tags.public') }}
|
|
||||||
el-tag(
|
|
||||||
v-else-if='currentInstanceWorld.ref.releaseStatus === "private"'
|
|
||||||
type='danger'
|
|
||||||
effect='plain'
|
|
||||||
size='mini'
|
|
||||||
style='margin-right: 5px') {{ $t('dialog.world.tags.private') }}
|
|
||||||
el-tag.x-tag-platform-pc(
|
|
||||||
v-if='currentInstanceWorld.isPC'
|
|
||||||
type='info'
|
|
||||||
effect='plain'
|
|
||||||
size='mini'
|
|
||||||
style='margin-right: 5px') PC
|
|
||||||
span.x-grey(
|
|
||||||
v-if='currentInstanceWorld.bundleSizes["standalonewindows"]'
|
|
||||||
style='margin-left: 5px; border-left: inherit; padding-left: 5px') {{ currentInstanceWorld.bundleSizes['standalonewindows'].fileSize }}
|
|
||||||
el-tag.x-tag-platform-quest(
|
|
||||||
v-if='currentInstanceWorld.isQuest'
|
|
||||||
type='info'
|
|
||||||
effect='plain'
|
|
||||||
size='mini'
|
|
||||||
style='margin-right: 5px') Android
|
|
||||||
span.x-grey(
|
|
||||||
v-if='currentInstanceWorld.bundleSizes["android"]'
|
|
||||||
style='margin-left: 5px; border-left: inherit; padding-left: 5px') {{ currentInstanceWorld.bundleSizes['android'].fileSize }}
|
|
||||||
el-tag.x-tag-platform-ios(
|
|
||||||
v-if='currentInstanceWorld.isIOS'
|
|
||||||
type='info'
|
|
||||||
effect='plain'
|
|
||||||
size='mini'
|
|
||||||
style='margin-right: 5px') iOS
|
|
||||||
span.x-grey(
|
|
||||||
v-if='currentInstanceWorld.bundleSizes["ios"]'
|
|
||||||
style='margin-left: 5px; border-left: inherit; padding-left: 5px') {{ currentInstanceWorld.bundleSizes['ios'].fileSize }}
|
|
||||||
el-tag(
|
|
||||||
v-if='currentInstanceWorld.avatarScalingDisabled'
|
|
||||||
type='warning'
|
|
||||||
effect='plain'
|
|
||||||
size='mini'
|
|
||||||
style='margin-right: 5px; margin-top: 5px') {{ $t('dialog.world.tags.avatar_scaling_disabled') }}
|
|
||||||
el-tag(
|
|
||||||
v-if='currentInstanceWorld.inCache'
|
|
||||||
type='info'
|
|
||||||
effect='plain'
|
|
||||||
size='mini'
|
|
||||||
style='margin-right: 5px')
|
|
||||||
span {{ currentInstanceWorld.cacheSize }} {{ $t('dialog.world.tags.cache') }}
|
|
||||||
div(style='margin-top: 5px')
|
|
||||||
location-world(
|
|
||||||
:locationobject='currentInstanceLocation'
|
|
||||||
:currentuserid='API.currentUser.id'
|
|
||||||
@show-launch-dialog='showLaunchDialog')
|
|
||||||
span(v-if='lastLocation.playerList.size > 0' style='margin-left: 5px')
|
|
||||||
| {{ lastLocation.playerList.size }}
|
|
||||||
| #[template(v-if='lastLocation.friendList.size > 0') ({{ lastLocation.friendList.size }})]
|
|
||||||
| ― #[timer(v-if='lastLocation.date' :epoch='lastLocation.date')]
|
|
||||||
div(style='margin-top: 5px')
|
|
||||||
span(
|
|
||||||
v-show='currentInstanceWorld.ref.name !== currentInstanceWorld.ref.description'
|
|
||||||
v-text='currentInstanceWorld.ref.description'
|
|
||||||
:style='{ fontSize: "12px", overflow: "hidden", textOverflow: "ellipsis", display: "-webkit-box", WebkitBoxOrient: "vertical", WebkitLineClamp: currentInstanceWorldDescriptionExpanded ? "none" : "2" }')
|
|
||||||
div(style='display: flex; justify-content: end')
|
|
||||||
el-button(
|
|
||||||
v-if='currentInstanceWorld.ref.description.length > 50 && !currentInstanceWorldDescriptionExpanded'
|
|
||||||
type='text'
|
|
||||||
size='mini'
|
|
||||||
@click='currentInstanceWorldDescriptionExpanded = true') {{ !currentInstanceWorldDescriptionExpanded && 'Show more' }}
|
|
||||||
div(style='display: flex; flex-direction: column; margin-left: 20px')
|
|
||||||
.x-friend-item(style='cursor: default')
|
|
||||||
.detail
|
|
||||||
span.name {{ $t('dialog.world.info.capacity') }}
|
|
||||||
span.extra {{ currentInstanceWorld.ref.recommendedCapacity | commaNumber }} ({{ currentInstanceWorld.ref.capacity | commaNumber }})
|
|
||||||
.x-friend-item(style='cursor: default')
|
|
||||||
.detail
|
|
||||||
span.name {{ $t('dialog.world.info.last_updated') }}
|
|
||||||
span.extra {{ currentInstanceWorld.lastUpdated | formatDate('long') }}
|
|
||||||
.x-friend-item(style='cursor: default')
|
|
||||||
.detail
|
|
||||||
span.name {{ $t('dialog.world.info.created_at') }}
|
|
||||||
span.extra {{ currentInstanceWorld.ref.created_at | formatDate('long') }}
|
|
||||||
.photon-event-table(v-if='photonLoggingEnabled')
|
|
||||||
div(style='position: absolute; width: 600px; margin-left: 215px; z-index: 1')
|
|
||||||
el-select(
|
|
||||||
v-model='photonEventTableTypeFilter'
|
|
||||||
@change='photonEventTableFilterChange'
|
|
||||||
multiple
|
|
||||||
clearable
|
|
||||||
collapse-tags
|
|
||||||
style='flex: 1; width: 220px'
|
|
||||||
:placeholder='$t("view.player_list.photon.filter_placeholder")')
|
|
||||||
el-option(
|
|
||||||
v-for='type in photonEventTableTypeFilterList'
|
|
||||||
:key='type'
|
|
||||||
:label='type'
|
|
||||||
:value='type')
|
|
||||||
el-input(
|
|
||||||
v-model='photonEventTableFilter'
|
|
||||||
@input='photonEventTableFilterChange'
|
|
||||||
:placeholder='$t("view.player_list.photon.search_placeholder")'
|
|
||||||
clearable
|
|
||||||
style='width: 150px; margin-left: 10px')
|
|
||||||
el-button(@click='showChatboxBlacklistDialog' style='margin-left: 10px') {{ $t('view.player_list.photon.chatbox_blacklist') }}
|
|
||||||
el-tooltip(
|
|
||||||
placement='bottom'
|
|
||||||
:content='$t("view.player_list.photon.status_tooltip")'
|
|
||||||
:disabled='hideTooltips')
|
|
||||||
div(
|
|
||||||
style='display: inline-block; margin-left: 15px; font-size: 14px; vertical-align: text-top; margin-top: 1px')
|
|
||||||
span(v-if='ipcEnabled && !photonEventIcon') 🟢
|
|
||||||
span(v-else-if='ipcEnabled') ⚪
|
|
||||||
span(v-else) 🔴
|
|
||||||
el-tabs(type='card')
|
|
||||||
el-tab-pane(:label='$t("view.player_list.photon.current")')
|
|
||||||
data-tables(v-bind='photonEventTable' style='margin-bottom: 10px')
|
|
||||||
el-table-column(:label='$t("table.playerList.date")' prop='created_at' width='120')
|
|
||||||
template(#default='scope')
|
|
||||||
el-tooltip(placement='right')
|
|
||||||
template(#content)
|
|
||||||
span {{ scope.row.created_at | formatDate('long') }}
|
|
||||||
span {{ scope.row.created_at | formatDate('short') }}
|
|
||||||
el-table-column(:label='$t("table.playerList.user")' prop='photonId' width='160')
|
|
||||||
template(#default='scope')
|
|
||||||
span.x-link(
|
|
||||||
v-text='scope.row.displayName'
|
|
||||||
@click='showUserFromPhotonId(scope.row.photonId)'
|
|
||||||
style='padding-right: 10px')
|
|
||||||
el-table-column(:label='$t("table.playerList.type")' prop='type' width='140')
|
|
||||||
el-table-column(:label='$t("table.playerList.detail")' prop='text')
|
|
||||||
template(#default='scope')
|
|
||||||
template(v-if='scope.row.type === "ChangeAvatar"')
|
|
||||||
span.x-link(
|
|
||||||
v-text='scope.row.avatar.name'
|
|
||||||
@click='showAvatarDialog(scope.row.avatar.id)')
|
|
||||||
|
|
|
||||||
span(v-if='!scope.row.inCache' style='color: #aaa') #[i.el-icon-download]
|
|
||||||
span.avatar-info-public(v-if='scope.row.avatar.releaseStatus === "public"') {{ $t('dialog.avatar.labels.public') }}
|
|
||||||
span.avatar-info-own(v-else-if='scope.row.avatar.releaseStatus === "private"') {{ $t('dialog.avatar.labels.private') }}
|
|
||||||
template(
|
|
||||||
v-if='scope.row.avatar.description && scope.row.avatar.name !== scope.row.avatar.description')
|
|
||||||
|
|
|
||||||
| - {{ scope.row.avatar.description }}
|
|
||||||
template(v-else-if='scope.row.type === "ChangeStatus"')
|
|
||||||
template(v-if='scope.row.status !== scope.row.previousStatus')
|
|
||||||
el-tooltip(placement='top')
|
|
||||||
template(#content)
|
|
||||||
span(v-if='scope.row.previousStatus === "active"') {{ $t('dialog.user.status.active') }}
|
|
||||||
span(v-else-if='scope.row.previousStatus === "join me"') {{ $t('dialog.user.status.join_me') }}
|
|
||||||
span(v-else-if='scope.row.previousStatus === "ask me"') {{ $t('dialog.user.status.ask_me') }}
|
|
||||||
span(v-else-if='scope.row.previousStatus === "busy"') {{ $t('dialog.user.status.busy') }}
|
|
||||||
span(v-else) {{ $t('dialog.user.status.offline') }}
|
|
||||||
i.x-user-status(:class='statusClass(scope.row.previousStatus)')
|
|
||||||
span
|
|
||||||
i.el-icon-right
|
|
||||||
el-tooltip(placement='top')
|
|
||||||
template(#content)
|
|
||||||
span(v-if='scope.row.status === "active"') {{ $t('dialog.user.status.active') }}
|
|
||||||
span(v-else-if='scope.row.status === "join me"') {{ $t('dialog.user.status.join_me') }}
|
|
||||||
span(v-else-if='scope.row.status === "ask me"') {{ $t('dialog.user.status.ask_me') }}
|
|
||||||
span(v-else-if='scope.row.status === "busy"') {{ $t('dialog.user.status.busy') }}
|
|
||||||
span(v-else) {{ $t('dialog.user.status.offline') }}
|
|
||||||
i.x-user-status(
|
|
||||||
:class='statusClass(scope.row.status)'
|
|
||||||
style='margin-right: 5px')
|
|
||||||
span(
|
|
||||||
v-if='scope.row.statusDescription !== scope.row.previousStatusDescription'
|
|
||||||
v-text='scope.row.statusDescription')
|
|
||||||
template(v-else-if='scope.row.type === "ChangeGroup"')
|
|
||||||
span.x-link(
|
|
||||||
v-if='scope.row.previousGroupName'
|
|
||||||
v-text='scope.row.previousGroupName'
|
|
||||||
@click='showGroupDialog(scope.row.previousGroupId)'
|
|
||||||
style='margin-right: 5px')
|
|
||||||
span.x-link(
|
|
||||||
v-else
|
|
||||||
v-text='scope.row.previousGroupId'
|
|
||||||
@click='showGroupDialog(scope.row.previousGroupId)'
|
|
||||||
style='margin-right: 5px')
|
|
||||||
span
|
|
||||||
i.el-icon-right
|
|
||||||
span.x-link(
|
|
||||||
v-if='scope.row.groupName'
|
|
||||||
v-text='scope.row.groupName'
|
|
||||||
@click='showGroupDialog(scope.row.groupId)'
|
|
||||||
style='margin-left: 5px')
|
|
||||||
span.x-link(
|
|
||||||
v-else
|
|
||||||
v-text='scope.row.groupId'
|
|
||||||
@click='showGroupDialog(scope.row.groupId)'
|
|
||||||
style='margin-left: 5px')
|
|
||||||
span.x-link(
|
|
||||||
v-else-if='scope.row.type === "PortalSpawn"'
|
|
||||||
@click='showWorldDialog(scope.row.location, scope.row.shortName)')
|
|
||||||
location(
|
|
||||||
:location='scope.row.location'
|
|
||||||
:hint='scope.row.worldName'
|
|
||||||
:grouphint='scope.row.groupName'
|
|
||||||
:link='false')
|
|
||||||
span(v-else-if='scope.row.type === "ChatBoxMessage"' v-text='scope.row.text')
|
|
||||||
span(v-else-if='scope.row.type === "OnPlayerJoined"')
|
|
||||||
span(v-if='scope.row.platform === "Desktop"' style='color: #409eff') Desktop
|
|
||||||
span(v-else-if='scope.row.platform === "VR"' style='color: #409eff') VR
|
|
||||||
span(v-else-if='scope.row.platform === "Quest"' style='color: #67c23a') Android
|
|
||||||
span.x-link(
|
|
||||||
v-text='scope.row.avatar.name'
|
|
||||||
@click='showAvatarDialog(scope.row.avatar.id)')
|
|
||||||
|
|
|
||||||
span(v-if='!scope.row.inCache' style='color: #aaa') #[i.el-icon-download]
|
|
||||||
span.avatar-info-public(v-if='scope.row.avatar.releaseStatus === "public"') {{ $t('dialog.avatar.labels.public') }}
|
|
||||||
span.avatar-info-own(v-else-if='scope.row.avatar.releaseStatus === "private"') {{ $t('dialog.avatar.labels.private') }}
|
|
||||||
span(v-else-if='scope.row.type === "SpawnEmoji"')
|
|
||||||
span(v-if='scope.row.imageUrl')
|
|
||||||
el-tooltip(placement='right')
|
|
||||||
template(#content)
|
|
||||||
img.friends-list-avatar(
|
|
||||||
v-lazy='scope.row.imageUrl'
|
|
||||||
style='height: 500px; cursor: pointer'
|
|
||||||
@click='showFullscreenImageDialog(scope.row.imageUrl)')
|
|
||||||
span(v-text='scope.row.fileId')
|
|
||||||
span(v-else v-text='scope.row.text')
|
|
||||||
span(
|
|
||||||
v-else-if='scope.row.color === "yellow"'
|
|
||||||
v-text='scope.row.text'
|
|
||||||
style='color: yellow')
|
|
||||||
span(v-else v-text='scope.row.text')
|
|
||||||
el-tab-pane(:label='$t("view.player_list.photon.previous")')
|
|
||||||
data-tables(v-bind='photonEventTablePrevious' style='margin-bottom: 10px')
|
|
||||||
el-table-column(:label='$t("table.playerList.date")' prop='created_at' width='120')
|
|
||||||
template(#default='scope')
|
|
||||||
el-tooltip(placement='right')
|
|
||||||
template(#content)
|
|
||||||
span {{ scope.row.created_at | formatDate('long') }}
|
|
||||||
span {{ scope.row.created_at | formatDate('short') }}
|
|
||||||
el-table-column(:label='$t("table.playerList.user")' prop='photonId' width='160')
|
|
||||||
template(#default='scope')
|
|
||||||
span.x-link(
|
|
||||||
v-text='scope.row.displayName'
|
|
||||||
@click='lookupUser(scope.row)'
|
|
||||||
style='padding-right: 10px')
|
|
||||||
el-table-column(:label='$t("table.playerList.type")' prop='type' width='140')
|
|
||||||
el-table-column(:label='$t("table.playerList.detail")' prop='text')
|
|
||||||
template(#default='scope')
|
|
||||||
template(v-if='scope.row.type === "ChangeAvatar"')
|
|
||||||
span.x-link(
|
|
||||||
v-text='scope.row.avatar.name'
|
|
||||||
@click='showAvatarDialog(scope.row.avatar.id)')
|
|
||||||
|
|
|
||||||
span(v-if='!scope.row.inCache' style='color: #aaa') #[i.el-icon-download]
|
|
||||||
span.avatar-info-public(v-if='scope.row.avatar.releaseStatus === "public"') {{ $t('dialog.avatar.labels.public') }}
|
|
||||||
span.avatar-info-own(v-else-if='scope.row.avatar.releaseStatus === "private"') {{ $t('dialog.avatar.labels.private') }}
|
|
||||||
template(
|
|
||||||
v-if='scope.row.avatar.description && scope.row.avatar.name !== scope.row.avatar.description')
|
|
||||||
|
|
|
||||||
| - {{ scope.row.avatar.description }}
|
|
||||||
template(v-else-if='scope.row.type === "ChangeStatus"')
|
|
||||||
template(v-if='scope.row.status !== scope.row.previousStatus')
|
|
||||||
el-tooltip(placement='top')
|
|
||||||
template(#content)
|
|
||||||
span(v-if='scope.row.previousStatus === "active"') {{ $t('dialog.user.status.active') }}
|
|
||||||
span(v-else-if='scope.row.previousStatus === "join me"') {{ $t('dialog.user.status.join_me') }}
|
|
||||||
span(v-else-if='scope.row.previousStatus === "ask me"') {{ $t('dialog.user.status.ask_me') }}
|
|
||||||
span(v-else-if='scope.row.previousStatus === "busy"') {{ $t('dialog.user.status.busy') }}
|
|
||||||
span(v-else) {{ $t('dialog.user.status.offline') }}
|
|
||||||
i.x-user-status(:class='statusClass(scope.row.previousStatus)')
|
|
||||||
span
|
|
||||||
i.el-icon-right
|
|
||||||
el-tooltip(placement='top')
|
|
||||||
template(#content)
|
|
||||||
span(v-if='scope.row.status === "active"') {{ $t('dialog.user.status.active') }}
|
|
||||||
span(v-else-if='scope.row.status === "join me"') {{ $t('dialog.user.status.join_me') }}
|
|
||||||
span(v-else-if='scope.row.status === "ask me"') {{ $t('dialog.user.status.ask_me') }}
|
|
||||||
span(v-else-if='scope.row.status === "busy"') {{ $t('dialog.user.status.busy') }}
|
|
||||||
span(v-else) {{ $t('dialog.user.status.offline') }}
|
|
||||||
i.x-user-status(
|
|
||||||
:class='statusClass(scope.row.status)'
|
|
||||||
style='margin-right: 5px')
|
|
||||||
span(
|
|
||||||
v-if='scope.row.statusDescription !== scope.row.previousStatusDescription'
|
|
||||||
v-text='scope.row.statusDescription')
|
|
||||||
template(v-else-if='scope.row.type === "ChangeGroup"')
|
|
||||||
span.x-link(
|
|
||||||
v-if='scope.row.previousGroupName'
|
|
||||||
v-text='scope.row.previousGroupName'
|
|
||||||
@click='showGroupDialog(scope.row.previousGroupId)'
|
|
||||||
style='margin-right: 5px')
|
|
||||||
span.x-link(
|
|
||||||
v-else
|
|
||||||
v-text='scope.row.previousGroupId'
|
|
||||||
@click='showGroupDialog(scope.row.previousGroupId)'
|
|
||||||
style='margin-right: 5px')
|
|
||||||
span
|
|
||||||
i.el-icon-right
|
|
||||||
span.x-link(
|
|
||||||
v-if='scope.row.groupName'
|
|
||||||
v-text='scope.row.groupName'
|
|
||||||
@click='showGroupDialog(scope.row.groupId)'
|
|
||||||
style='margin-left: 5px')
|
|
||||||
span.x-link(
|
|
||||||
v-else
|
|
||||||
v-text='scope.row.groupId'
|
|
||||||
@click='showGroupDialog(scope.row.groupId)'
|
|
||||||
style='margin-left: 5px')
|
|
||||||
span.x-link(
|
|
||||||
v-else-if='scope.row.type === "PortalSpawn"'
|
|
||||||
@click='showWorldDialog(scope.row.location, scope.row.shortName)')
|
|
||||||
location(
|
|
||||||
:location='scope.row.location'
|
|
||||||
:hint='scope.row.worldName'
|
|
||||||
:grouphint='scope.row.groupName'
|
|
||||||
:link='false')
|
|
||||||
span(v-else-if='scope.row.type === "ChatBoxMessage"' v-text='scope.row.text')
|
|
||||||
span(v-else-if='scope.row.type === "OnPlayerJoined"')
|
|
||||||
span(v-if='scope.row.platform === "Desktop"' style='color: #409eff') Desktop
|
|
||||||
span(v-else-if='scope.row.platform === "VR"' style='color: #409eff') VR
|
|
||||||
span(v-else-if='scope.row.platform === "Quest"' style='color: #67c23a') Android
|
|
||||||
span.x-link(
|
|
||||||
v-text='scope.row.avatar.name'
|
|
||||||
@click='showAvatarDialog(scope.row.avatar.id)')
|
|
||||||
|
|
|
||||||
span(v-if='!scope.row.inCache' style='color: #aaa') #[i.el-icon-download]
|
|
||||||
span.avatar-info-public(v-if='scope.row.avatar.releaseStatus === "public"') {{ $t('dialog.avatar.labels.public') }}
|
|
||||||
span.avatar-info-own(v-else-if='scope.row.avatar.releaseStatus === "private"') {{ $t('dialog.avatar.labels.private') }}
|
|
||||||
span(v-else-if='scope.row.type === "SpawnEmoji"')
|
|
||||||
span(v-if='scope.row.imageUrl')
|
|
||||||
el-tooltip(placement='right')
|
|
||||||
template(#content)
|
|
||||||
img.friends-list-avatar(
|
|
||||||
v-lazy='scope.row.imageUrl'
|
|
||||||
style='height: 500px; cursor: pointer'
|
|
||||||
@click='showFullscreenImageDialog(scope.row.imageUrl)')
|
|
||||||
span(v-text='scope.row.fileId')
|
|
||||||
span(v-else v-text='scope.row.text')
|
|
||||||
span(
|
|
||||||
v-else-if='scope.row.color === "yellow"'
|
|
||||||
v-text='scope.row.text'
|
|
||||||
style='color: yellow')
|
|
||||||
span(v-else v-text='scope.row.text')
|
|
||||||
.current-instance-table
|
|
||||||
data-tables(
|
|
||||||
v-bind='currentInstanceUserList'
|
|
||||||
@row-click='selectCurrentInstanceRow'
|
|
||||||
style='margin-top: 10px; cursor: pointer')
|
|
||||||
el-table-column(:label='$t("table.playerList.avatar")' width='70' prop='photo')
|
|
||||||
template(#default='scope')
|
|
||||||
template(v-if='userImage(scope.row.ref)')
|
|
||||||
el-popover(placement='right' height='500px' trigger='hover')
|
|
||||||
img.friends-list-avatar(slot='reference' v-lazy='userImage(scope.row.ref)')
|
|
||||||
img.friends-list-avatar(
|
|
||||||
v-lazy='userImageFull(scope.row.ref)'
|
|
||||||
style='height: 500px; cursor: pointer'
|
|
||||||
@click='showFullscreenImageDialog(userImageFull(scope.row.ref))')
|
|
||||||
el-table-column(:label='$t("table.playerList.timer")' width='80' prop='timer' sortable)
|
|
||||||
template(#default='scope')
|
|
||||||
timer(:epoch='scope.row.timer')
|
|
||||||
el-table-column(
|
|
||||||
v-if='photonLoggingEnabled'
|
|
||||||
:label='$t("table.playerList.photonId")'
|
|
||||||
width='110'
|
|
||||||
prop='photonId'
|
|
||||||
sortable)
|
|
||||||
template(#default='scope')
|
|
||||||
template(v-if='chatboxUserBlacklist.has(scope.row.ref.id)')
|
|
||||||
el-tooltip(placement='left' content='Unblock chatbox messages')
|
|
||||||
el-button(
|
|
||||||
type='text'
|
|
||||||
icon='el-icon-turn-off-microphone'
|
|
||||||
size='mini'
|
|
||||||
style='color: red; margin-right: 5px'
|
|
||||||
@click.stop='deleteChatboxUserBlacklist(scope.row.ref.id)')
|
|
||||||
template(v-else)
|
|
||||||
el-tooltip(placement='left' content='Block chatbox messages')
|
|
||||||
el-button(
|
|
||||||
type='text'
|
|
||||||
icon='el-icon-microphone'
|
|
||||||
size='mini'
|
|
||||||
style='margin-right: 5px'
|
|
||||||
@click.stop='addChatboxUserBlacklist(scope.row.ref)')
|
|
||||||
span(v-text='scope.row.photonId')
|
|
||||||
el-table-column(:label='$t("table.playerList.icon")' prop='isMaster' width='70' align='center')
|
|
||||||
template(#default='scope')
|
|
||||||
el-tooltip(v-if='scope.row.isMaster' placement='left' content='Instance Master')
|
|
||||||
span 👑
|
|
||||||
el-tooltip(v-if='scope.row.isModerator' placement='left' content='Moderator')
|
|
||||||
span ⚔️
|
|
||||||
el-tooltip(v-if='scope.row.isFriend' placement='left' content='Friend')
|
|
||||||
span 💚
|
|
||||||
el-tooltip(v-if='scope.row.timeoutTime' placement='left' content='Timeout')
|
|
||||||
span(style='color: red') 🔴{{ scope.row.timeoutTime }}s
|
|
||||||
el-table-column(:label='$t("table.playerList.platform")' prop='inVRMode' width='80')
|
|
||||||
template(#default='scope')
|
|
||||||
template(v-if='scope.row.ref.last_platform')
|
|
||||||
span(v-if='scope.row.ref.last_platform === "standalonewindows"' style='color: #409eff') PC
|
|
||||||
span(v-else-if='scope.row.ref.last_platform === "android"' style='color: #67c23a') A
|
|
||||||
span(v-else-if='scope.row.ref.last_platform === "ios"' style='color: #c7c7ce') iOS
|
|
||||||
span(v-else) {{ scope.row.ref.last_platform }}
|
|
||||||
template(v-if='scope.row.inVRMode !== null')
|
|
||||||
span(v-if='scope.row.inVRMode') VR
|
|
||||||
span(
|
|
||||||
v-else-if='scope.row.ref.last_platform === "android" || scope.row.ref.last_platform === "ios"') M
|
|
||||||
span(v-else) D
|
|
||||||
el-table-column(
|
|
||||||
:label='$t("table.playerList.displayName")'
|
|
||||||
min-width='140'
|
|
||||||
prop='displayName'
|
|
||||||
sortable='custom')
|
|
||||||
template(#default='scope')
|
|
||||||
span(
|
|
||||||
v-if='randomUserColours'
|
|
||||||
v-text='scope.row.ref.displayName'
|
|
||||||
:style='{ color: scope.row.ref.$userColour }')
|
|
||||||
span(v-else v-text='scope.row.ref.displayName')
|
|
||||||
el-table-column(:label='$t("table.playerList.status")' min-width='180' prop='ref.status')
|
|
||||||
template(#default='scope')
|
|
||||||
template(v-if='scope.row.ref.status')
|
|
||||||
i.x-user-status(:class='statusClass(scope.row.ref.status)' style='margin-right: 3px')
|
|
||||||
span(v-text='scope.row.ref.statusDescription')
|
|
||||||
//- el-table-column(label="Group" min-width="180" prop="groupOnNameplate" sortable)
|
|
||||||
//- template(v-once #default="scope")
|
|
||||||
//- span(v-text="scope.row.groupOnNameplate")
|
|
||||||
el-table-column(
|
|
||||||
:label='$t("table.playerList.rank")'
|
|
||||||
width='110'
|
|
||||||
prop='$trustSortNum'
|
|
||||||
sortable='custom')
|
|
||||||
template(#default='scope')
|
|
||||||
span.name(v-text='scope.row.ref.$trustLevel' :class='scope.row.ref.$trustClass')
|
|
||||||
el-table-column(:label='$t("table.playerList.language")' width='100' prop='ref.$languages')
|
|
||||||
template(#default='scope')
|
|
||||||
el-tooltip(v-for='item in scope.row.ref.$languages' :key='item.key' placement='top')
|
|
||||||
template(#content)
|
|
||||||
span {{ item.value }} ({{ item.key }})
|
|
||||||
span.flags(
|
|
||||||
:class='languageClass(item.key)'
|
|
||||||
style='display: inline-block; margin-right: 5px')
|
|
||||||
el-table-column(:label='$t("table.playerList.bioLink")' width='100' prop='ref.bioLinks')
|
|
||||||
template(#default='scope')
|
|
||||||
div(style='display: flex; align-items: center')
|
|
||||||
el-tooltip(v-if='link' v-for='(link, index) in scope.row.ref.bioLinks' :key='index')
|
|
||||||
template(#content)
|
|
||||||
span(v-text='link')
|
|
||||||
img(
|
|
||||||
:src='getFaviconUrl(link)'
|
|
||||||
style='width: 16px; height: 16px; vertical-align: middle; margin-right: 5px; cursor: pointer'
|
|
||||||
@click.stop='openExternalLink(link)')
|
|
||||||
@@ -1,366 +0,0 @@
|
|||||||
mixin profileTab
|
|
||||||
.x-container(v-if='menuActiveIndex === "profile"')
|
|
||||||
.options-container(style='margin-top: 0')
|
|
||||||
span.header {{ $t('view.profile.profile.header') }}
|
|
||||||
.x-friend-list(style='margin-top: 10px')
|
|
||||||
.x-friend-item(@click='showUserDialog(API.currentUser.id)')
|
|
||||||
.avatar
|
|
||||||
img(v-lazy='userImage(API.currentUser, true)')
|
|
||||||
.detail
|
|
||||||
span.name(v-text='API.currentUser.displayName')
|
|
||||||
span.extra(v-text='API.currentUser.username')
|
|
||||||
.x-friend-item(style='cursor: default')
|
|
||||||
.detail
|
|
||||||
span.name {{ $t('view.profile.profile.last_activity') }}
|
|
||||||
span.extra {{ API.currentUser.last_activity | formatDate('long') }}
|
|
||||||
.x-friend-item(style='cursor: default')
|
|
||||||
.detail
|
|
||||||
span.name {{ $t('view.profile.profile.two_factor') }}
|
|
||||||
span.extra {{ API.currentUser.twoFactorAuthEnabled ? $t('view.profile.profile.two_factor_enabled') : $t('view.profile.profile.two_factor_disabled') }}
|
|
||||||
.x-friend-item(@click='getVRChatCredits()')
|
|
||||||
.detail
|
|
||||||
span.name {{ $t('view.profile.profile.vrchat_credits') }}
|
|
||||||
span.extra {{ API.currentUser.$vrchatcredits ?? $t('view.profile.profile.refresh') }}
|
|
||||||
div(style='margin-top: 10px')
|
|
||||||
el-button(
|
|
||||||
size='small'
|
|
||||||
type='danger'
|
|
||||||
plain
|
|
||||||
icon='el-icon-switch-button'
|
|
||||||
@click='logout()'
|
|
||||||
style='margin-left: 0; margin-right: 5px; margin-top: 10px') {{ $t('view.profile.profile.logout') }}
|
|
||||||
el-button(
|
|
||||||
size='small'
|
|
||||||
icon='el-icon-picture-outline'
|
|
||||||
@click='showGalleryDialog()'
|
|
||||||
style='margin-left: 0; margin-right: 5px; margin-top: 10px') {{ $t('view.profile.profile.manage_gallery_icon') }}
|
|
||||||
el-button(
|
|
||||||
size='small'
|
|
||||||
icon='el-icon-chat-dot-round'
|
|
||||||
@click='showDiscordNamesDialog()'
|
|
||||||
style='margin-left: 0; margin-right: 5px; margin-top: 10px') {{ $t('view.profile.profile.discord_names') }}
|
|
||||||
el-button(
|
|
||||||
size='small'
|
|
||||||
icon='el-icon-printer'
|
|
||||||
@click='showExportFriendsListDialog()'
|
|
||||||
style='margin-left: 0; margin-right: 5px; margin-top: 10px') {{ $t('view.profile.profile.export_friend_list') }}
|
|
||||||
el-button(
|
|
||||||
size='small'
|
|
||||||
icon='el-icon-user'
|
|
||||||
@click='showExportAvatarsListDialog()'
|
|
||||||
style='margin-left: 0; margin-right: 5px; margin-top: 10px') {{ $t('view.profile.profile.export_own_avatars') }}
|
|
||||||
|
|
||||||
.options-container
|
|
||||||
span.header {{ $t('view.profile.game_info.header') }}
|
|
||||||
.x-friend-list(style='margin-top: 10px')
|
|
||||||
.x-friend-item
|
|
||||||
.detail(@click='API.getVisits()')
|
|
||||||
span.name {{ $t('view.profile.game_info.online_users') }}
|
|
||||||
span.extra(v-if='visits') {{ $t('view.profile.game_info.user_online', { count: visits }) }}
|
|
||||||
span.extra(v-else) {{ $t('view.profile.game_info.refresh') }}
|
|
||||||
.options-container
|
|
||||||
.header-bar
|
|
||||||
span.header {{ $t('view.profile.vrc_sdk_downloads.header') }}
|
|
||||||
el-tooltip(placement='top' :content='$t("view.profile.refresh_tooltip")' :disabled='hideTooltips')
|
|
||||||
el-button(
|
|
||||||
type='default'
|
|
||||||
@click='API.getConfig()'
|
|
||||||
size='mini'
|
|
||||||
icon='el-icon-refresh'
|
|
||||||
circle
|
|
||||||
style='margin-left: 5px')
|
|
||||||
.x-friend-list(style='margin-top: 10px')
|
|
||||||
.x-friend-item(v-for='(link, item) in API.cachedConfig.downloadUrls' :key='item' placement='top')
|
|
||||||
.detail(@click='openExternalLink(link)')
|
|
||||||
span.name(v-text='item')
|
|
||||||
span.extra(v-text='link')
|
|
||||||
.options-container
|
|
||||||
span.header {{ $t('view.profile.direct_access.header') }}
|
|
||||||
div(style='margin-top: 10px')
|
|
||||||
el-button-group
|
|
||||||
el-button(size='small' @click='promptUsernameDialog()') {{ $t('view.profile.direct_access.username') }}
|
|
||||||
el-button(size='small' @click='promptUserIdDialog()') {{ $t('view.profile.direct_access.user_id') }}
|
|
||||||
el-button(size='small' @click='promptWorldDialog()') {{ $t('view.profile.direct_access.world_instance') }}
|
|
||||||
el-button(size='small' @click='promptAvatarDialog()') {{ $t('view.profile.direct_access.avatar') }}
|
|
||||||
.options-container
|
|
||||||
.header-bar
|
|
||||||
span.header {{ $t('view.profile.invite_messages') }}
|
|
||||||
el-tooltip(placement='top' :content='$t("view.profile.refresh_tooltip")' :disabled='hideTooltips')
|
|
||||||
el-button(
|
|
||||||
type='default'
|
|
||||||
@click='inviteMessageTable.visible = true; refreshInviteMessageTable("message")'
|
|
||||||
size='mini'
|
|
||||||
icon='el-icon-refresh'
|
|
||||||
circle
|
|
||||||
style='margin-left: 5px')
|
|
||||||
el-tooltip(
|
|
||||||
placement='top'
|
|
||||||
:content='$t("view.profile.clear_results_tooltip")'
|
|
||||||
:disabled='hideTooltips')
|
|
||||||
el-button(
|
|
||||||
type='default'
|
|
||||||
@click='inviteMessageTable.visible = false'
|
|
||||||
size='mini'
|
|
||||||
icon='el-icon-delete'
|
|
||||||
circle
|
|
||||||
style='margin-left: 5px')
|
|
||||||
data-tables(v-if='inviteMessageTable.visible' v-bind='inviteMessageTable' style='margin-top: 10px')
|
|
||||||
el-table-column(
|
|
||||||
:label='$t("table.profile.invite_messages.slot")'
|
|
||||||
prop='slot'
|
|
||||||
sortable='custom'
|
|
||||||
width='70')
|
|
||||||
el-table-column(:label='$t("table.profile.invite_messages.message")' prop='message')
|
|
||||||
el-table-column(
|
|
||||||
:label='$t("table.profile.invite_messages.cool_down")'
|
|
||||||
prop='updatedAt'
|
|
||||||
sortable='custom'
|
|
||||||
width='110'
|
|
||||||
align='right')
|
|
||||||
template(#default='scope')
|
|
||||||
countdown-timer(:datetime='scope.row.updatedAt' :hours='1')
|
|
||||||
el-table-column(:label='$t("table.profile.invite_messages.action")' width='60' align='right')
|
|
||||||
template(#default='scope')
|
|
||||||
el-button(
|
|
||||||
type='text'
|
|
||||||
icon='el-icon-edit'
|
|
||||||
size='mini'
|
|
||||||
@click='showEditInviteMessageDialog("message", scope.row)')
|
|
||||||
.options-container
|
|
||||||
.header-bar
|
|
||||||
span.header {{ $t('view.profile.invite_response_messages') }}
|
|
||||||
el-tooltip(placement='top' :content='$t("view.profile.refresh_tooltip")' :disabled='hideTooltips')
|
|
||||||
el-button(
|
|
||||||
type='default'
|
|
||||||
@click='inviteResponseMessageTable.visible = true; refreshInviteMessageTable("response")'
|
|
||||||
size='mini'
|
|
||||||
icon='el-icon-refresh'
|
|
||||||
circle
|
|
||||||
style='margin-left: 5px')
|
|
||||||
el-tooltip(
|
|
||||||
placement='top'
|
|
||||||
:content='$t("view.profile.clear_results_tooltip")'
|
|
||||||
:disabled='hideTooltips')
|
|
||||||
el-button(
|
|
||||||
type='default'
|
|
||||||
@click='inviteResponseMessageTable.visible = false'
|
|
||||||
size='mini'
|
|
||||||
icon='el-icon-delete'
|
|
||||||
circle
|
|
||||||
style='margin-left: 5px')
|
|
||||||
data-tables(
|
|
||||||
v-if='inviteResponseMessageTable.visible'
|
|
||||||
v-bind='inviteResponseMessageTable'
|
|
||||||
style='margin-top: 10px')
|
|
||||||
el-table-column(
|
|
||||||
:label='$t("table.profile.invite_messages.slot")'
|
|
||||||
prop='slot'
|
|
||||||
sortable='custom'
|
|
||||||
width='70')
|
|
||||||
el-table-column(:label='$t("table.profile.invite_messages.message")' prop='message')
|
|
||||||
el-table-column(
|
|
||||||
:label='$t("table.profile.invite_messages.cool_down")'
|
|
||||||
prop='updatedAt'
|
|
||||||
sortable='custom'
|
|
||||||
width='110'
|
|
||||||
align='right')
|
|
||||||
template(#default='scope')
|
|
||||||
countdown-timer(:datetime='scope.row.updatedAt' :hours='1')
|
|
||||||
el-table-column(:label='$t("table.profile.invite_messages.action")' width='60' align='right')
|
|
||||||
template(#default='scope')
|
|
||||||
el-button(
|
|
||||||
type='text'
|
|
||||||
icon='el-icon-edit'
|
|
||||||
size='mini'
|
|
||||||
@click='showEditInviteMessageDialog("response", scope.row)')
|
|
||||||
.options-container
|
|
||||||
.header-bar
|
|
||||||
span.header {{ $t('view.profile.invite_request_messages') }}
|
|
||||||
el-tooltip(placement='top' :content='$t("view.profile.refresh_tooltip")' :disabled='hideTooltips')
|
|
||||||
el-button(
|
|
||||||
type='default'
|
|
||||||
@click='inviteRequestMessageTable.visible = true; refreshInviteMessageTable("request")'
|
|
||||||
size='mini'
|
|
||||||
icon='el-icon-refresh'
|
|
||||||
circle
|
|
||||||
style='margin-left: 5px')
|
|
||||||
el-tooltip(
|
|
||||||
placement='top'
|
|
||||||
:content='$t("view.profile.clear_results_tooltip")'
|
|
||||||
:disabled='hideTooltips')
|
|
||||||
el-button(
|
|
||||||
type='default'
|
|
||||||
@click='inviteRequestMessageTable.visible = false'
|
|
||||||
size='mini'
|
|
||||||
icon='el-icon-delete'
|
|
||||||
circle
|
|
||||||
style='margin-left: 5px')
|
|
||||||
data-tables(
|
|
||||||
v-if='inviteRequestMessageTable.visible'
|
|
||||||
v-bind='inviteRequestMessageTable'
|
|
||||||
style='margin-top: 10px')
|
|
||||||
el-table-column(
|
|
||||||
:label='$t("table.profile.invite_messages.slot")'
|
|
||||||
prop='slot'
|
|
||||||
sortable='custom'
|
|
||||||
width='70')
|
|
||||||
el-table-column(:label='$t("table.profile.invite_messages.message")' prop='message')
|
|
||||||
el-table-column(
|
|
||||||
:label='$t("table.profile.invite_messages.cool_down")'
|
|
||||||
prop='updatedAt'
|
|
||||||
sortable='custom'
|
|
||||||
width='110'
|
|
||||||
align='right')
|
|
||||||
template(#default='scope')
|
|
||||||
countdown-timer(:datetime='scope.row.updatedAt' :hours='1')
|
|
||||||
el-table-column(:label='$t("table.profile.invite_messages.action")' width='60' align='right')
|
|
||||||
template(#default='scope')
|
|
||||||
el-button(
|
|
||||||
type='text'
|
|
||||||
icon='el-icon-edit'
|
|
||||||
size='mini'
|
|
||||||
@click='showEditInviteMessageDialog("request", scope.row)')
|
|
||||||
.options-container
|
|
||||||
.header-bar
|
|
||||||
span.header {{ $t('view.profile.invite_request_response_messages') }}
|
|
||||||
el-tooltip(placement='top' :content='$t("view.profile.refresh_tooltip")' :disabled='hideTooltips')
|
|
||||||
el-button(
|
|
||||||
type='default'
|
|
||||||
@click='inviteRequestResponseMessageTable.visible = true; refreshInviteMessageTable("requestResponse")'
|
|
||||||
size='mini'
|
|
||||||
icon='el-icon-refresh'
|
|
||||||
circle
|
|
||||||
style='margin-left: 5px')
|
|
||||||
el-tooltip(
|
|
||||||
placement='top'
|
|
||||||
:content='$t("view.profile.clear_results_tooltip")'
|
|
||||||
:disabled='hideTooltips')
|
|
||||||
el-button(
|
|
||||||
type='default'
|
|
||||||
@click='inviteRequestResponseMessageTable.visible = false'
|
|
||||||
size='mini'
|
|
||||||
icon='el-icon-delete'
|
|
||||||
circle
|
|
||||||
style='margin-left: 5px')
|
|
||||||
data-tables(
|
|
||||||
v-if='inviteRequestResponseMessageTable.visible'
|
|
||||||
v-bind='inviteRequestResponseMessageTable'
|
|
||||||
style='margin-top: 10px')
|
|
||||||
el-table-column(
|
|
||||||
:label='$t("table.profile.invite_messages.slot")'
|
|
||||||
prop='slot'
|
|
||||||
sortable='custom'
|
|
||||||
width='70')
|
|
||||||
el-table-column(:label='$t("table.profile.invite_messages.message")' prop='message')
|
|
||||||
el-table-column(
|
|
||||||
:label='$t("table.profile.invite_messages.cool_down")'
|
|
||||||
prop='updatedAt'
|
|
||||||
sortable='custom'
|
|
||||||
width='110'
|
|
||||||
align='right')
|
|
||||||
template(#default='scope')
|
|
||||||
countdown-timer(:datetime='scope.row.updatedAt' :hours='1')
|
|
||||||
el-table-column(:label='$t("table.profile.invite_messages.action")' width='60' align='right')
|
|
||||||
template(#default='scope')
|
|
||||||
el-button(
|
|
||||||
type='text'
|
|
||||||
icon='el-icon-edit'
|
|
||||||
size='mini'
|
|
||||||
@click='showEditInviteMessageDialog("requestResponse", scope.row)')
|
|
||||||
.options-container
|
|
||||||
span.header {{ $t('view.profile.past_display_names') }}
|
|
||||||
data-tables(v-bind='pastDisplayNameTable' style='margin-top: 10px')
|
|
||||||
el-table-column(
|
|
||||||
:label='$t("table.profile.previous_display_name.date")'
|
|
||||||
prop='updated_at'
|
|
||||||
sortable='custom')
|
|
||||||
template(#default='scope')
|
|
||||||
span {{ scope.row.updated_at | formatDate('long') }}
|
|
||||||
el-table-column(:label='$t("table.profile.previous_display_name.name")' prop='displayName')
|
|
||||||
.options-container
|
|
||||||
.header-bar
|
|
||||||
span.header {{ $t('view.profile.config_json') }}
|
|
||||||
el-tooltip(placement='top' :content='$t("view.profile.refresh_tooltip")' :disabled='hideTooltips')
|
|
||||||
el-button(
|
|
||||||
type='default'
|
|
||||||
@click='refreshConfigTreeData()'
|
|
||||||
size='mini'
|
|
||||||
icon='el-icon-refresh'
|
|
||||||
circle
|
|
||||||
style='margin-left: 5px')
|
|
||||||
el-tooltip(
|
|
||||||
placement='top'
|
|
||||||
:content='$t("view.profile.clear_results_tooltip")'
|
|
||||||
:disabled='hideTooltips')
|
|
||||||
el-button(
|
|
||||||
type='default'
|
|
||||||
@click='configTreeData = []'
|
|
||||||
size='mini'
|
|
||||||
icon='el-icon-delete'
|
|
||||||
circle
|
|
||||||
style='margin-left: 5px')
|
|
||||||
el-tree(v-if='configTreeData.length > 0' :data='configTreeData' style='margin-top: 10px; font-size: 12px')
|
|
||||||
template(#default='scope')
|
|
||||||
span
|
|
||||||
span(v-text='scope.data.key' style='font-weight: bold; margin-right: 5px')
|
|
||||||
span(v-if='!scope.data.children' v-text='scope.data.value')
|
|
||||||
.options-container
|
|
||||||
.header-bar
|
|
||||||
span.header {{ $t('view.profile.current_user_json') }}
|
|
||||||
el-tooltip(placement='top' :content='$t("view.profile.refresh_tooltip")' :disabled='hideTooltips')
|
|
||||||
el-button(
|
|
||||||
type='default'
|
|
||||||
@click='refreshCurrentUserTreeData()'
|
|
||||||
size='mini'
|
|
||||||
icon='el-icon-refresh'
|
|
||||||
circle
|
|
||||||
style='margin-left: 5px')
|
|
||||||
el-tooltip(
|
|
||||||
placement='top'
|
|
||||||
:content='$t("view.profile.clear_results_tooltip")'
|
|
||||||
:disabled='hideTooltips')
|
|
||||||
el-button(
|
|
||||||
type='default'
|
|
||||||
@click='currentUserTreeData = []'
|
|
||||||
size='mini'
|
|
||||||
icon='el-icon-delete'
|
|
||||||
circle
|
|
||||||
style='margin-left: 5px')
|
|
||||||
el-tree(
|
|
||||||
v-if='currentUserTreeData.length > 0'
|
|
||||||
:data='currentUserTreeData'
|
|
||||||
style='margin-top: 10px; font-size: 12px')
|
|
||||||
template(#default='scope')
|
|
||||||
span
|
|
||||||
span(v-text='scope.data.key' style='font-weight: bold; margin-right: 5px')
|
|
||||||
span(v-if='!scope.data.children' v-text='scope.data.value')
|
|
||||||
.options-container
|
|
||||||
.header-bar
|
|
||||||
span.header {{ $t('view.profile.feedback') }}
|
|
||||||
el-tooltip(placement='top' :content='$t("view.profile.refresh_tooltip")' :disabled='hideTooltips')
|
|
||||||
el-button(
|
|
||||||
type='default'
|
|
||||||
@click='getCurrentUserFeedback()'
|
|
||||||
size='mini'
|
|
||||||
icon='el-icon-refresh'
|
|
||||||
circle
|
|
||||||
style='margin-left: 5px')
|
|
||||||
el-tooltip(
|
|
||||||
placement='top'
|
|
||||||
:content='$t("view.profile.clear_results_tooltip")'
|
|
||||||
:disabled='hideTooltips')
|
|
||||||
el-button(
|
|
||||||
type='default'
|
|
||||||
@click='currentUserFeedbackData = []'
|
|
||||||
size='mini'
|
|
||||||
icon='el-icon-delete'
|
|
||||||
circle
|
|
||||||
style='margin-left: 5px')
|
|
||||||
el-tree(
|
|
||||||
v-if='currentUserFeedbackData.length > 0'
|
|
||||||
:data='currentUserFeedbackData'
|
|
||||||
style='margin-top: 10px; font-size: 12px')
|
|
||||||
template(#default='scope')
|
|
||||||
span
|
|
||||||
span(v-text='scope.data.key' style='font-weight: bold; margin-right: 5px')
|
|
||||||
span(v-if='!scope.data.children' v-text='scope.data.value')
|
|
||||||
@@ -1,205 +0,0 @@
|
|||||||
mixin searchTab
|
|
||||||
.x-container(v-show='menuActiveIndex === "search"')
|
|
||||||
div(style='margin: 0 0 10px; display: flex; align-items: center')
|
|
||||||
el-input(
|
|
||||||
v-model='searchText'
|
|
||||||
:placeholder='$t("view.search.search_placeholder")'
|
|
||||||
@keyup.native.13='search()'
|
|
||||||
style='flex: 1')
|
|
||||||
el-tooltip(placement='bottom' :content='$t("view.search.clear_results_tooltip")' :disabled='hideTooltips')
|
|
||||||
el-button(
|
|
||||||
type='default'
|
|
||||||
@click='clearSearch()'
|
|
||||||
icon='el-icon-delete'
|
|
||||||
circle
|
|
||||||
style='flex: none; margin-left: 10px')
|
|
||||||
el-tabs(ref='searchTab' type='card' style='margin-top: 15px' @tab-click='searchText = ""')
|
|
||||||
el-tab-pane(
|
|
||||||
:label='$t("view.search.user.header")'
|
|
||||||
v-loading='isSearchUserLoading'
|
|
||||||
style='min-height: 60px')
|
|
||||||
el-checkbox(v-model='searchUserByBio' style='margin-left: 10px') {{ $t('view.search.user.search_by_bio') }}
|
|
||||||
el-checkbox(v-model='searchUserSortByLastLoggedIn' style='margin-left: 10px') {{ $t('view.search.user.sort_by_last_logged_in') }}
|
|
||||||
.x-friend-list(style='min-height: 500px')
|
|
||||||
.x-friend-item(v-for='user in searchUserResults' :key='user.id' @click='showUserDialog(user.id)')
|
|
||||||
template
|
|
||||||
.avatar
|
|
||||||
img(v-lazy='userImage(user, true)')
|
|
||||||
.detail
|
|
||||||
span.name(v-text='user.displayName')
|
|
||||||
span.extra(
|
|
||||||
v-if='randomUserColours'
|
|
||||||
v-text='user.$trustLevel'
|
|
||||||
:class='user.$trustClass')
|
|
||||||
span.extra(v-else v-text='user.$trustLevel' :style='{ color: user.$userColour }')
|
|
||||||
el-button-group(style='margin-top: 15px' v-if='searchUserResults.length')
|
|
||||||
el-button(
|
|
||||||
:disabled='!searchUserParams.offset'
|
|
||||||
@click='moreSearchUser(-1)'
|
|
||||||
icon='el-icon-back'
|
|
||||||
size='small') {{ $t('view.search.prev_page') }}
|
|
||||||
el-button(
|
|
||||||
:disabled='searchUserResults.length < 10'
|
|
||||||
@click='moreSearchUser(1)'
|
|
||||||
icon='el-icon-right'
|
|
||||||
size='small') {{ $t('view.search.next_page') }}
|
|
||||||
el-tab-pane(
|
|
||||||
:label='$t("view.search.world.header")'
|
|
||||||
v-loading='isSearchWorldLoading'
|
|
||||||
style='min-height: 60px')
|
|
||||||
el-dropdown(
|
|
||||||
@command='(row) => searchWorld(row)'
|
|
||||||
size='small'
|
|
||||||
trigger='click'
|
|
||||||
style='margin-bottom: 15px')
|
|
||||||
el-button(size='small') {{ $t('view.search.world.category') }} #[i.el-icon-arrow-down.el-icon--right]
|
|
||||||
el-dropdown-menu(#default='dropdown')
|
|
||||||
el-dropdown-item(
|
|
||||||
v-for='row in API.cachedConfig.dynamicWorldRows'
|
|
||||||
:key='row.index'
|
|
||||||
v-text='row.name'
|
|
||||||
:command='row')
|
|
||||||
el-checkbox(v-model='searchWorldLabs' style='margin-left: 10px') {{ $t('view.search.world.community_lab') }}
|
|
||||||
.x-friend-list(style='min-height: 500px')
|
|
||||||
.x-friend-item(
|
|
||||||
v-for='world in searchWorldResults'
|
|
||||||
:key='world.id'
|
|
||||||
@click='showWorldDialog(world.id)')
|
|
||||||
template
|
|
||||||
.avatar
|
|
||||||
img(v-lazy='world.thumbnailImageUrl')
|
|
||||||
.detail
|
|
||||||
span.name(v-text='world.name')
|
|
||||||
span.extra(v-if='world.occupants') {{ world.authorName }} ({{ world.occupants }})
|
|
||||||
span.extra(v-else v-text='world.authorName')
|
|
||||||
el-button-group(style='margin-top: 15px' v-if='searchWorldResults.length')
|
|
||||||
el-button(
|
|
||||||
:disabled='!searchWorldParams.offset'
|
|
||||||
@click='moreSearchWorld(-1)'
|
|
||||||
icon='el-icon-back'
|
|
||||||
size='small') {{ $t('view.search.prev_page') }}
|
|
||||||
el-button(
|
|
||||||
:disabled='searchWorldResults.length < 10'
|
|
||||||
@click='moreSearchWorld(1)'
|
|
||||||
icon='el-icon-right'
|
|
||||||
size='small') {{ $t('view.search.next_page') }}
|
|
||||||
el-tab-pane(
|
|
||||||
:label='$t("view.search.avatar.header")'
|
|
||||||
v-loading='isSearchAvatarLoading'
|
|
||||||
style='min-height: 60px')
|
|
||||||
div(style='display: flex; align-items: center; justify-content: space-between')
|
|
||||||
div(style='display: flex; align-items: center')
|
|
||||||
el-dropdown(
|
|
||||||
v-if='avatarRemoteDatabaseProviderList.length > 1'
|
|
||||||
trigger='click'
|
|
||||||
@click.native.stop
|
|
||||||
size='mini'
|
|
||||||
style='margin-right: 5px')
|
|
||||||
el-button(size='small') {{ $t('view.search.avatar.search_provider') }} #[i.el-icon-arrow-down.el-icon--right]
|
|
||||||
el-dropdown-menu(#default='dropdown')
|
|
||||||
el-dropdown-item(
|
|
||||||
v-for='provider in avatarRemoteDatabaseProviderList'
|
|
||||||
:key='provider'
|
|
||||||
@click.native='setAvatarProvider(provider)') #[i.el-icon-check.el-icon--left(v-if='provider === avatarRemoteDatabaseProvider')] {{ provider }}
|
|
||||||
el-tooltip(
|
|
||||||
placement='bottom'
|
|
||||||
:content='$t("view.search.avatar.refresh_tooltip")'
|
|
||||||
:disabled='hideTooltips')
|
|
||||||
el-button(
|
|
||||||
type='default'
|
|
||||||
:loading='userDialog.isAvatarsLoading'
|
|
||||||
@click='refreshUserDialogAvatars()'
|
|
||||||
size='mini'
|
|
||||||
icon='el-icon-refresh'
|
|
||||||
circle)
|
|
||||||
span(style='font-size: 14px; margin-left: 5px; margin-right: 5px') {{ $t('view.search.avatar.result_count', { count: searchAvatarResults.length }) }}
|
|
||||||
div(style='display: flex; align-items: center')
|
|
||||||
el-radio-group(
|
|
||||||
v-model='searchAvatarFilter'
|
|
||||||
size='mini'
|
|
||||||
style='margin: 5px; display: block'
|
|
||||||
@change='searchAvatar')
|
|
||||||
el-radio(label='all') {{ $t('view.search.avatar.all') }}
|
|
||||||
el-radio(label='public') {{ $t('view.search.avatar.public') }}
|
|
||||||
el-radio(label='private') {{ $t('view.search.avatar.private') }}
|
|
||||||
el-divider(direction='vertical')
|
|
||||||
el-radio-group(
|
|
||||||
v-model='searchAvatarFilterRemote'
|
|
||||||
size='mini'
|
|
||||||
style='margin: 5px; display: block'
|
|
||||||
@change='searchAvatar')
|
|
||||||
el-radio(label='all') {{ $t('view.search.avatar.all') }}
|
|
||||||
el-radio(label='local') {{ $t('view.search.avatar.local') }}
|
|
||||||
el-radio(label='remote' :disabled='!avatarRemoteDatabase') {{ $t('view.search.avatar.remote') }}
|
|
||||||
div(style='display: flex; justify-content: end')
|
|
||||||
el-radio-group(
|
|
||||||
:disabled='searchAvatarFilterRemote !== "local"'
|
|
||||||
v-model='searchAvatarSort'
|
|
||||||
size='mini'
|
|
||||||
style='margin: 5px; display: block'
|
|
||||||
@change='searchAvatar')
|
|
||||||
el-radio(label='name') {{ $t('view.search.avatar.sort_name') }}
|
|
||||||
el-radio(label='update') {{ $t('view.search.avatar.sort_update') }}
|
|
||||||
el-radio(label='created') {{ $t('view.search.avatar.sort_created') }}
|
|
||||||
.x-friend-list(style='margin-top: 20px; min-height: 500px')
|
|
||||||
.x-friend-item(
|
|
||||||
v-for='avatar in searchAvatarPage'
|
|
||||||
:key='avatar.id'
|
|
||||||
@click='showAvatarDialog(avatar.id)')
|
|
||||||
template
|
|
||||||
.avatar
|
|
||||||
img(v-if='avatar.thumbnailImageUrl' v-lazy='avatar.thumbnailImageUrl')
|
|
||||||
img(v-else-if='avatar.imageUrl' v-lazy='avatar.imageUrl')
|
|
||||||
.detail
|
|
||||||
span.name(v-text='avatar.name')
|
|
||||||
span.extra(
|
|
||||||
v-text='avatar.releaseStatus'
|
|
||||||
v-if='avatar.releaseStatus === "public"'
|
|
||||||
style='color: #67c23a')
|
|
||||||
span.extra(
|
|
||||||
v-text='avatar.releaseStatus'
|
|
||||||
v-else-if='avatar.releaseStatus === "private"'
|
|
||||||
style='color: #f56c6c')
|
|
||||||
span.extra(v-text='avatar.releaseStatus' v-else)
|
|
||||||
span.extra(v-text='avatar.authorName')
|
|
||||||
el-button-group(style='margin-top: 15px' v-if='searchAvatarPage.length')
|
|
||||||
el-button(
|
|
||||||
:disabled='!searchAvatarPageNum'
|
|
||||||
@click='moreSearchAvatar(-1)'
|
|
||||||
icon='el-icon-back'
|
|
||||||
size='small') {{ $t('view.search.prev_page') }}
|
|
||||||
el-button(
|
|
||||||
:disabled='searchAvatarResults.length < 10 || (searchAvatarPageNum + 1) * 10 >= searchAvatarResults.length'
|
|
||||||
@click='moreSearchAvatar(1)'
|
|
||||||
icon='el-icon-right'
|
|
||||||
size='small') {{ $t('view.search.next_page') }}
|
|
||||||
el-tab-pane(
|
|
||||||
:label='$t("view.search.group.header")'
|
|
||||||
v-loading='isSearchGroupLoading'
|
|
||||||
style='min-height: 60px')
|
|
||||||
.x-friend-list(style='min-height: 500px')
|
|
||||||
.x-friend-item(
|
|
||||||
v-for='group in searchGroupResults'
|
|
||||||
:key='group.id'
|
|
||||||
@click='showGroupDialog(group.id)')
|
|
||||||
template
|
|
||||||
.avatar
|
|
||||||
img(v-lazy='getSmallThumbnailUrl(group.iconUrl)')
|
|
||||||
.detail
|
|
||||||
span.name
|
|
||||||
span(v-text='group.name')
|
|
||||||
span(style='margin-left: 5px; font-weight: normal') ({{ group.memberCount }})
|
|
||||||
span(
|
|
||||||
style='margin-left: 5px; color: #909399; font-weight: normal; font-family: monospace; font-size: 12px') {{ group.shortCode }}.{{ group.discriminator }}
|
|
||||||
span.extra(v-text='group.description')
|
|
||||||
el-button-group(style='margin-top: 15px' v-if='searchGroupResults.length')
|
|
||||||
el-button(
|
|
||||||
:disabled='!searchGroupParams.offset'
|
|
||||||
@click='moreSearchGroup(-1)'
|
|
||||||
icon='el-icon-back'
|
|
||||||
size='small') {{ $t('view.search.prev_page') }}
|
|
||||||
el-button(
|
|
||||||
:disabled='searchGroupResults.length < 10'
|
|
||||||
@click='moreSearchGroup(1)'
|
|
||||||
icon='el-icon-right'
|
|
||||||
size='small') {{ $t('view.search.next_page') }}
|
|
||||||
@@ -116,6 +116,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
import { parseLocation } from '../../../composables/instance/utils';
|
||||||
import database from '../../../service/database';
|
import database from '../../../service/database';
|
||||||
import utils from '../../../classes/utils';
|
import utils from '../../../classes/utils';
|
||||||
import configRepository from '../../../service/config';
|
import configRepository from '../../../service/config';
|
||||||
@@ -379,7 +380,7 @@
|
|||||||
const timeString = utils.timeToText(param.data, true);
|
const timeString = utils.timeToText(param.data, true);
|
||||||
const color = param.color;
|
const color = param.color;
|
||||||
const name = param.name;
|
const name = param.name;
|
||||||
const location = utils.parseLocation(instanceData.location);
|
const location = parseLocation(instanceData.location);
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div style="display: flex; align-items: center;">
|
<div style="display: flex; align-items: center;">
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<safe-dialog :visible.sync="isDialogVisible" :title="$t('dialog.avatar_export.header')" width="650px">
|
||||||
:before-close="beforeDialogClose"
|
|
||||||
:visible.sync="isDialogVisible"
|
|
||||||
:title="$t('dialog.avatar_export.header')"
|
|
||||||
width="650px"
|
|
||||||
@mousedown.native="dialogMouseDown"
|
|
||||||
@mouseup.native="dialogMouseUp">
|
|
||||||
<el-checkbox-group
|
<el-checkbox-group
|
||||||
v-model="exportSelectedOptions"
|
v-model="exportSelectedOptions"
|
||||||
style="margin-bottom: 10px"
|
style="margin-bottom: 10px"
|
||||||
@@ -82,13 +76,13 @@
|
|||||||
readonly
|
readonly
|
||||||
style="margin-top: 15px"
|
style="margin-top: 15px"
|
||||||
@click.native="handleCopyAvatarExportData"></el-input>
|
@click.native="handleCopyAvatarExportData"></el-input>
|
||||||
</el-dialog>
|
</safe-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'AvatarExportDialog',
|
name: 'AvatarExportDialog',
|
||||||
inject: ['API', 'beforeDialogClose', 'dialogMouseDown', 'dialogMouseUp'],
|
inject: ['API'],
|
||||||
props: {
|
props: {
|
||||||
avatarExportDialogVisible: Boolean,
|
avatarExportDialogVisible: Boolean,
|
||||||
favoriteAvatars: Array,
|
favoriteAvatars: Array,
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<safe-dialog
|
||||||
ref="avatarImportDialog"
|
ref="avatarImportDialog"
|
||||||
:before-close="beforeDialogClose"
|
|
||||||
:visible.sync="isVisible"
|
:visible.sync="isVisible"
|
||||||
:title="$t('dialog.avatar_import.header')"
|
:title="$t('dialog.avatar_import.header')"
|
||||||
width="650px"
|
width="650px">
|
||||||
@mousedown.native="dialogMouseDown"
|
|
||||||
@mouseup.native="dialogMouseUp">
|
|
||||||
<div style="display: flex; align-items: center; justify-content: space-between">
|
<div style="display: flex; align-items: center; justify-content: space-between">
|
||||||
<div style="font-size: 12px">{{ $t('dialog.avatar_import.description') }}</div>
|
<div style="font-size: 12px">{{ $t('dialog.avatar_import.description') }}</div>
|
||||||
<div style="display: flex; align-items: center">
|
<div style="display: flex; align-items: center">
|
||||||
@@ -171,7 +168,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</data-tables>
|
</data-tables>
|
||||||
</el-dialog>
|
</safe-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -180,16 +177,7 @@
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'AvatarImportDialog',
|
name: 'AvatarImportDialog',
|
||||||
inject: [
|
inject: ['API', 'adjustDialogZ', 'showFullscreenImageDialog', 'showUserDialog', 'showAvatarDialog'],
|
||||||
'API',
|
|
||||||
'beforeDialogClose',
|
|
||||||
'dialogMouseDown',
|
|
||||||
'dialogMouseUp',
|
|
||||||
'adjustDialogZ',
|
|
||||||
'showFullscreenImageDialog',
|
|
||||||
'showUserDialog',
|
|
||||||
'showAvatarDialog'
|
|
||||||
],
|
|
||||||
props: {
|
props: {
|
||||||
getLocalAvatarFavoriteGroupLength: Function,
|
getLocalAvatarFavoriteGroupLength: Function,
|
||||||
localAvatarFavoriteGroups: Array,
|
localAvatarFavoriteGroups: Array,
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<safe-dialog
|
||||||
:before-close="beforeDialogClose"
|
|
||||||
:visible.sync="isDialogVisible"
|
:visible.sync="isDialogVisible"
|
||||||
class="x-dialog"
|
class="x-dialog"
|
||||||
:title="$t('dialog.friend_export.header')"
|
:title="$t('dialog.friend_export.header')"
|
||||||
width="650px"
|
width="650px"
|
||||||
destroy-on-close
|
destroy-on-close>
|
||||||
@mousedown.native="dialogMouseDown"
|
|
||||||
@mouseup.native="dialogMouseUp">
|
|
||||||
<el-dropdown trigger="click" size="small" @click.native.stop>
|
<el-dropdown trigger="click" size="small" @click.native.stop>
|
||||||
<el-button size="mini">
|
<el-button size="mini">
|
||||||
<span v-if="friendExportFavoriteGroup">
|
<span v-if="friendExportFavoriteGroup">
|
||||||
@@ -42,13 +39,13 @@
|
|||||||
readonly
|
readonly
|
||||||
style="margin-top: 15px"
|
style="margin-top: 15px"
|
||||||
@click.native="handleCopyFriendExportData"></el-input>
|
@click.native="handleCopyFriendExportData"></el-input>
|
||||||
</el-dialog>
|
</safe-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'FriendExportDialog',
|
name: 'FriendExportDialog',
|
||||||
inject: ['API', 'beforeDialogClose', 'dialogMouseDown', 'dialogMouseUp'],
|
inject: ['API'],
|
||||||
props: {
|
props: {
|
||||||
friendExportDialogVisible: Boolean,
|
friendExportDialogVisible: Boolean,
|
||||||
favoriteFriends: Array
|
favoriteFriends: Array
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<safe-dialog
|
||||||
ref="friendImportDialog"
|
ref="friendImportDialog"
|
||||||
:before-close="beforeDialogClose"
|
|
||||||
:visible.sync="isVisible"
|
:visible.sync="isVisible"
|
||||||
:title="$t('dialog.friend_import.header')"
|
:title="$t('dialog.friend_import.header')"
|
||||||
width="650px"
|
width="650px">
|
||||||
@mousedown.native="dialogMouseDown"
|
|
||||||
@mouseup.native="dialogMouseUp">
|
|
||||||
<div style="display: flex; align-items: center; justify-content: space-between">
|
<div style="display: flex; align-items: center; justify-content: space-between">
|
||||||
<div style="font-size: 12px">{{ $t('dialog.friend_import.description') }}</div>
|
<div style="font-size: 12px">{{ $t('dialog.friend_import.description') }}</div>
|
||||||
<div style="display: flex; align-items: center">
|
<div style="display: flex; align-items: center">
|
||||||
@@ -122,7 +119,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</data-tables>
|
</data-tables>
|
||||||
</el-dialog>
|
</safe-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -131,17 +128,7 @@
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'FriendImportDialog',
|
name: 'FriendImportDialog',
|
||||||
inject: [
|
inject: ['API', 'userImage', 'userImageFull', 'showFullscreenImageDialog', 'showUserDialog', 'adjustDialogZ'],
|
||||||
'API',
|
|
||||||
'userImage',
|
|
||||||
'userImageFull',
|
|
||||||
'showFullscreenImageDialog',
|
|
||||||
'showUserDialog',
|
|
||||||
'beforeDialogClose',
|
|
||||||
'dialogMouseDown',
|
|
||||||
'dialogMouseUp',
|
|
||||||
'adjustDialogZ'
|
|
||||||
],
|
|
||||||
props: {
|
props: {
|
||||||
friendImportDialogVisible: {
|
friendImportDialogVisible: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<safe-dialog :visible.sync="isDialogVisible" :title="$t('dialog.world_export.header')" width="650px">
|
||||||
:before-close="beforeDialogClose"
|
|
||||||
:visible.sync="isDialogVisible"
|
|
||||||
:title="$t('dialog.world_export.header')"
|
|
||||||
width="650px"
|
|
||||||
@mousedown.native="dialogMouseDown"
|
|
||||||
@mouseup.native="dialogMouseUp">
|
|
||||||
<el-checkbox-group
|
<el-checkbox-group
|
||||||
v-model="exportSelectedOptions"
|
v-model="exportSelectedOptions"
|
||||||
style="margin-bottom: 10px"
|
style="margin-bottom: 10px"
|
||||||
@@ -84,13 +78,13 @@
|
|||||||
readonly
|
readonly
|
||||||
style="margin-top: 15px"
|
style="margin-top: 15px"
|
||||||
@click.native="handleCopyWorldExportData"></el-input>
|
@click.native="handleCopyWorldExportData"></el-input>
|
||||||
</el-dialog>
|
</safe-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'WorldExportDialog',
|
name: 'WorldExportDialog',
|
||||||
inject: ['API', 'beforeDialogClose', 'dialogMouseDown', 'dialogMouseUp'],
|
inject: ['API'],
|
||||||
props: {
|
props: {
|
||||||
favoriteWorlds: Array,
|
favoriteWorlds: Array,
|
||||||
worldExportDialogVisible: Boolean,
|
worldExportDialogVisible: Boolean,
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<safe-dialog
|
||||||
ref="worldImportDialog"
|
ref="worldImportDialog"
|
||||||
:before-close="beforeDialogClose"
|
|
||||||
:visible.sync="isVisible"
|
:visible.sync="isVisible"
|
||||||
:title="$t('dialog.world_import.header')"
|
:title="$t('dialog.world_import.header')"
|
||||||
width="650px"
|
width="650px"
|
||||||
top="10vh"
|
top="10vh"
|
||||||
class="x-dialog"
|
class="x-dialog">
|
||||||
@mousedown.native="dialogMouseDown"
|
|
||||||
@mouseup.native="dialogMouseUp">
|
|
||||||
<div style="display: flex; align-items: center; justify-content: space-between">
|
<div style="display: flex; align-items: center; justify-content: space-between">
|
||||||
<div style="font-size: 12px">{{ $t('dialog.world_import.description') }}</div>
|
<div style="font-size: 12px">{{ $t('dialog.world_import.description') }}</div>
|
||||||
<div style="display: flex; align-items: center">
|
<div style="display: flex; align-items: center">
|
||||||
@@ -176,7 +173,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</data-tables>
|
</data-tables>
|
||||||
</el-dialog>
|
</safe-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -185,16 +182,7 @@
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'WorldImportDialog',
|
name: 'WorldImportDialog',
|
||||||
inject: [
|
inject: ['API', 'showFullscreenImageDialog', 'showUserDialog', 'adjustDialogZ', 'showWorldDialog'],
|
||||||
'API',
|
|
||||||
'beforeDialogClose',
|
|
||||||
'dialogMouseDown',
|
|
||||||
'dialogMouseUp',
|
|
||||||
'showFullscreenImageDialog',
|
|
||||||
'showUserDialog',
|
|
||||||
'adjustDialogZ',
|
|
||||||
'showWorldDialog'
|
|
||||||
],
|
|
||||||
props: {
|
props: {
|
||||||
worldImportDialogVisible: Boolean,
|
worldImportDialogVisible: Boolean,
|
||||||
worldImportDialogInput: String,
|
worldImportDialogInput: String,
|
||||||
|
|||||||
475
src/views/Feed/Feed.vue
Normal file
475
src/views/Feed/Feed.vue
Normal file
@@ -0,0 +1,475 @@
|
|||||||
|
<template>
|
||||||
|
<div v-show="menuActiveIndex === 'feed'" class="x-container feed">
|
||||||
|
<div style="margin: 0 0 10px; display: flex; align-items: center">
|
||||||
|
<div style="flex: none; margin-right: 10px; display: flex; align-items: center">
|
||||||
|
<el-tooltip
|
||||||
|
placement="bottom"
|
||||||
|
:content="t('view.feed.favorites_only_tooltip')"
|
||||||
|
:disabled="hideTooltips">
|
||||||
|
<el-switch v-model="feedTable.vip" active-color="#13ce66" @change="feedTableLookup"></el-switch>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
<el-select
|
||||||
|
v-model="feedTable.filter"
|
||||||
|
multiple
|
||||||
|
clearable
|
||||||
|
style="flex: 1; height: 40px"
|
||||||
|
:placeholder="t('view.feed.filter_placeholder')"
|
||||||
|
@change="feedTableLookup">
|
||||||
|
<el-option
|
||||||
|
v-for="type in ['GPS', 'Online', 'Offline', 'Status', 'Avatar', 'Bio']"
|
||||||
|
:key="type"
|
||||||
|
:label="t('view.feed.filters.' + type)"
|
||||||
|
:value="type"></el-option>
|
||||||
|
</el-select>
|
||||||
|
<el-input
|
||||||
|
v-model="feedTable.search"
|
||||||
|
:placeholder="t('view.feed.search_placeholder')"
|
||||||
|
clearable
|
||||||
|
style="flex: none; width: 150px; margin: 0 10px"
|
||||||
|
@keyup.native.13="feedTableLookup"
|
||||||
|
@change="feedTableLookup"></el-input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<data-tables v-loading="feedTable.loading" v-bind="feedTable" lazy>
|
||||||
|
<el-table-column type="expand" width="20">
|
||||||
|
<template #default="scope">
|
||||||
|
<div style="position: relative; font-size: 14px">
|
||||||
|
<template v-if="scope.row.type === 'GPS'">
|
||||||
|
<location
|
||||||
|
v-if="scope.row.previousLocation"
|
||||||
|
:location="scope.row.previousLocation"
|
||||||
|
style="display: inline-block"></location>
|
||||||
|
<el-tag type="info" effect="plain" size="mini" style="margin-left: 5px">{{
|
||||||
|
timeToText(scope.row.time)
|
||||||
|
}}</el-tag>
|
||||||
|
<br />
|
||||||
|
<span style="margin-right: 5px">
|
||||||
|
<i class="el-icon-right"></i>
|
||||||
|
</span>
|
||||||
|
<location
|
||||||
|
v-if="scope.row.location"
|
||||||
|
:location="scope.row.location"
|
||||||
|
:hint="scope.row.worldName"
|
||||||
|
:grouphint="scope.row.groupName"></location>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="scope.row.type === 'Offline'">
|
||||||
|
<template v-if="scope.row.location">
|
||||||
|
<location
|
||||||
|
:location="scope.row.location"
|
||||||
|
:hint="scope.row.worldName"
|
||||||
|
:grouphint="scope.row.groupName"></location>
|
||||||
|
<el-tag type="info" effect="plain" size="mini" style="margin-left: 5px">{{
|
||||||
|
timeToText(scope.row.time)
|
||||||
|
}}</el-tag>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="scope.row.type === 'Online'">
|
||||||
|
<location
|
||||||
|
v-if="scope.row.location"
|
||||||
|
:location="scope.row.location"
|
||||||
|
:hint="scope.row.worldName"
|
||||||
|
:grouphint="scope.row.groupName"></location>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="scope.row.type === 'Avatar'">
|
||||||
|
<div style="display: flex; align-items: center">
|
||||||
|
<el-popover placement="right" width="500px" trigger="click">
|
||||||
|
<div
|
||||||
|
slot="reference"
|
||||||
|
style="display: inline-block; vertical-align: top; width: 160px">
|
||||||
|
<template v-if="scope.row.previousCurrentAvatarThumbnailImageUrl">
|
||||||
|
<img
|
||||||
|
v-lazy="scope.row.previousCurrentAvatarThumbnailImageUrl"
|
||||||
|
class="x-link"
|
||||||
|
style="flex: none; width: 160px; height: 120px; border-radius: 4px" />
|
||||||
|
<br />
|
||||||
|
<avatar-info
|
||||||
|
:imageurl="scope.row.previousCurrentAvatarThumbnailImageUrl"
|
||||||
|
:userid="scope.row.userId"
|
||||||
|
:hintownerid="scope.row.previousOwnerId"
|
||||||
|
:hintavatarname="scope.row.previousAvatarName"
|
||||||
|
:avatartags="scope.row.previousCurrentAvatarTags"></avatar-info>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<img
|
||||||
|
v-lazy="scope.row.previousCurrentAvatarImageUrl"
|
||||||
|
class="x-link"
|
||||||
|
style="width: 500px; height: 375px"
|
||||||
|
@click="showFullscreenImageDialog(scope.row.previousCurrentAvatarImageUrl)" />
|
||||||
|
</el-popover>
|
||||||
|
<span style="position: relative; margin: 0 10px">
|
||||||
|
<i class="el-icon-right"></i>
|
||||||
|
</span>
|
||||||
|
<el-popover placement="right" width="500px" trigger="click">
|
||||||
|
<div
|
||||||
|
slot="reference"
|
||||||
|
style="display: inline-block; vertical-align: top; width: 160px">
|
||||||
|
<template v-if="scope.row.currentAvatarThumbnailImageUrl">
|
||||||
|
<img
|
||||||
|
v-lazy="scope.row.currentAvatarThumbnailImageUrl"
|
||||||
|
class="x-link"
|
||||||
|
style="flex: none; width: 160px; height: 120px; border-radius: 4px" />
|
||||||
|
<br />
|
||||||
|
<avatar-info
|
||||||
|
:imageurl="scope.row.currentAvatarThumbnailImageUrl"
|
||||||
|
:userid="scope.row.userId"
|
||||||
|
:hintownerid="scope.row.ownerId"
|
||||||
|
:hintavatarname="scope.row.avatarName"
|
||||||
|
:avatartags="scope.row.currentAvatarTags"></avatar-info>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<img
|
||||||
|
v-lazy="scope.row.currentAvatarImageUrl"
|
||||||
|
class="x-link"
|
||||||
|
style="width: 500px; height: 375px"
|
||||||
|
@click="showFullscreenImageDialog(scope.row.currentAvatarImageUrl)" />
|
||||||
|
</el-popover>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="scope.row.type === 'Status'">
|
||||||
|
<el-tooltip placement="top">
|
||||||
|
<template #content>
|
||||||
|
<span v-if="scope.row.previousStatus === 'active'">{{
|
||||||
|
t('dialog.user.status.active')
|
||||||
|
}}</span>
|
||||||
|
<span v-else-if="scope.row.previousStatus === 'join me'">{{
|
||||||
|
t('dialog.user.status.join_me')
|
||||||
|
}}</span>
|
||||||
|
<span v-else-if="scope.row.previousStatus === 'ask me'">{{
|
||||||
|
t('dialog.user.status.ask_me')
|
||||||
|
}}</span>
|
||||||
|
<span v-else-if="scope.row.previousStatus === 'busy'">{{
|
||||||
|
t('dialog.user.status.busy')
|
||||||
|
}}</span>
|
||||||
|
<span v-else>{{ t('dialog.user.status.offline') }}</span>
|
||||||
|
</template>
|
||||||
|
<i class="x-user-status" :class="statusClass(scope.row.previousStatus)"></i>
|
||||||
|
</el-tooltip>
|
||||||
|
<span style="margin-left: 5px" v-text="scope.row.previousStatusDescription"></span>
|
||||||
|
<br />
|
||||||
|
<span>
|
||||||
|
<i class="el-icon-right"></i>
|
||||||
|
</span>
|
||||||
|
<el-tooltip placement="top">
|
||||||
|
<template #content>
|
||||||
|
<span v-if="scope.row.status === 'active'">{{
|
||||||
|
t('dialog.user.status.active')
|
||||||
|
}}</span>
|
||||||
|
<span v-else-if="scope.row.status === 'join me'">{{
|
||||||
|
t('dialog.user.status.join_me')
|
||||||
|
}}</span>
|
||||||
|
<span v-else-if="scope.row.status === 'ask me'">{{
|
||||||
|
t('dialog.user.status.ask_me')
|
||||||
|
}}</span>
|
||||||
|
<span v-else-if="scope.row.status === 'busy'">{{
|
||||||
|
t('dialog.user.status.busy')
|
||||||
|
}}</span>
|
||||||
|
<span v-else>{{ t('dialog.user.status.offline') }}</span>
|
||||||
|
</template>
|
||||||
|
<i
|
||||||
|
class="x-user-status"
|
||||||
|
:class="statusClass(scope.row.status)"
|
||||||
|
style="margin: 0 5px"></i>
|
||||||
|
</el-tooltip>
|
||||||
|
<span v-text="scope.row.statusDescription"></span>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="scope.row.type === 'Bio'">
|
||||||
|
<pre
|
||||||
|
style="
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 12px;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
line-height: 25px;
|
||||||
|
line-height: 22px;
|
||||||
|
"
|
||||||
|
v-html="formatDifference(scope.row.previousBio, scope.row.bio)"></pre>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column :label="t('table.feed.date')" prop="created_at" sortable="custom" width="120">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-tooltip placement="right">
|
||||||
|
<template #content>
|
||||||
|
<span>{{ scope.row.created_at | formatDate('long') }}</span>
|
||||||
|
</template>
|
||||||
|
<span>{{ scope.row.created_at | formatDate('short') }}</span>
|
||||||
|
</el-tooltip>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column :label="t('table.feed.type')" prop="type" width="80">
|
||||||
|
<template #default="scope">
|
||||||
|
<span class="x-link" v-text="t('view.feed.filters.' + scope.row.type)"></span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column :label="t('table.feed.user')" prop="displayName" width="180">
|
||||||
|
<template #default="scope">
|
||||||
|
<span
|
||||||
|
class="x-link"
|
||||||
|
style="padding-right: 10px"
|
||||||
|
@click="showUserDialog(scope.row.userId)"
|
||||||
|
v-text="scope.row.displayName"></span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column :label="t('table.feed.detail')">
|
||||||
|
<template #default="scope">
|
||||||
|
<template v-if="scope.row.type === 'GPS'">
|
||||||
|
<location
|
||||||
|
v-if="scope.row.location"
|
||||||
|
:location="scope.row.location"
|
||||||
|
:hint="scope.row.worldName"
|
||||||
|
:grouphint="scope.row.groupName"></location>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="scope.row.type === 'Offline' || scope.row.type === 'Online'">
|
||||||
|
<location
|
||||||
|
v-if="scope.row.location"
|
||||||
|
:location="scope.row.location"
|
||||||
|
:hint="scope.row.worldName"
|
||||||
|
:grouphint="scope.row.groupName"></location>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="scope.row.type === 'Status'">
|
||||||
|
<template v-if="scope.row.statusDescription === scope.row.previousStatusDescription">
|
||||||
|
<el-tooltip placement="top">
|
||||||
|
<template #content>
|
||||||
|
<span v-if="scope.row.previousStatus === 'active'">{{
|
||||||
|
t('dialog.user.status.active')
|
||||||
|
}}</span>
|
||||||
|
<span v-else-if="scope.row.previousStatus === 'join me'">{{
|
||||||
|
t('dialog.user.status.join_me')
|
||||||
|
}}</span>
|
||||||
|
<span v-else-if="scope.row.previousStatus === 'ask me'">{{
|
||||||
|
t('dialog.user.status.ask_me')
|
||||||
|
}}</span>
|
||||||
|
<span v-else-if="scope.row.previousStatus === 'busy'">{{
|
||||||
|
t('dialog.user.status.busy')
|
||||||
|
}}</span>
|
||||||
|
<span v-else>{{ t('dialog.user.status.offline') }}</span>
|
||||||
|
</template>
|
||||||
|
<i class="x-user-status" :class="statusClass(scope.row.previousStatus)"></i>
|
||||||
|
</el-tooltip>
|
||||||
|
<span style="margin: 0 5px">
|
||||||
|
<i class="el-icon-right"></i>
|
||||||
|
</span>
|
||||||
|
<el-tooltip placement="top">
|
||||||
|
<template #content>
|
||||||
|
<span v-if="scope.row.status === 'active'">{{
|
||||||
|
t('dialog.user.status.active')
|
||||||
|
}}</span>
|
||||||
|
<span v-else-if="scope.row.status === 'join me'">{{
|
||||||
|
t('dialog.user.status.join_me')
|
||||||
|
}}</span>
|
||||||
|
<span v-else-if="scope.row.status === 'ask me'">{{
|
||||||
|
t('dialog.user.status.ask_me')
|
||||||
|
}}</span>
|
||||||
|
<span v-else-if="scope.row.status === 'busy'">{{
|
||||||
|
t('dialog.user.status.busy')
|
||||||
|
}}</span>
|
||||||
|
<span v-else>{{ t('dialog.user.status.offline') }}</span>
|
||||||
|
</template>
|
||||||
|
<i class="x-user-status" :class="statusClass(scope.row.status)"></i>
|
||||||
|
</el-tooltip>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<el-tooltip placement="top">
|
||||||
|
<template #content>
|
||||||
|
<span v-if="scope.row.status === 'active'">{{
|
||||||
|
t('dialog.user.status.active')
|
||||||
|
}}</span>
|
||||||
|
<span v-else-if="scope.row.status === 'join me'">{{
|
||||||
|
t('dialog.user.status.join_me')
|
||||||
|
}}</span>
|
||||||
|
<span v-else-if="scope.row.status === 'ask me'">{{
|
||||||
|
t('dialog.user.status.ask_me')
|
||||||
|
}}</span>
|
||||||
|
<span v-else-if="scope.row.status === 'busy'">{{
|
||||||
|
t('dialog.user.status.busy')
|
||||||
|
}}</span>
|
||||||
|
<span v-else>{{ t('dialog.user.status.offline') }}</span>
|
||||||
|
</template>
|
||||||
|
<i
|
||||||
|
class="x-user-status"
|
||||||
|
:class="statusClass(scope.row.status)"
|
||||||
|
style="margin-right: 3px"></i>
|
||||||
|
</el-tooltip>
|
||||||
|
<span v-text="scope.row.statusDescription"></span>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="scope.row.type === 'Avatar'">
|
||||||
|
<avatar-info
|
||||||
|
:imageurl="scope.row.currentAvatarImageUrl"
|
||||||
|
:userid="scope.row.userId"
|
||||||
|
:hintownerid="scope.row.ownerId"
|
||||||
|
:hintavatarname="scope.row.avatarName"
|
||||||
|
:avatartags="scope.row.currentAvatarTags"></avatar-info>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="scope.row.type === 'Bio'">
|
||||||
|
<span v-text="scope.row.bio"></span>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</data-tables>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'FeedTab'
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { inject } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n-bridge';
|
||||||
|
import utils from '../../classes/utils';
|
||||||
|
import Location from '../../components/Location.vue';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const showFullscreenImageDialog = inject('showFullscreenImageDialog');
|
||||||
|
const statusClass = inject('statusClass');
|
||||||
|
const showUserDialog = inject('showUserDialog');
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
menuActiveIndex: {
|
||||||
|
type: String,
|
||||||
|
default: 'feed'
|
||||||
|
},
|
||||||
|
hideTooltips: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
feedTable: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['feedTableLookup']);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function that format the differences between two strings with HTML tags
|
||||||
|
* markerStartTag and markerEndTag are optional, if emitted, the differences will be highlighted with yellow and underlined.
|
||||||
|
* @param {*} s1
|
||||||
|
* @param {*} s2
|
||||||
|
* @param {*} markerStartTag
|
||||||
|
* @param {*} markerEndTag
|
||||||
|
* @returns An array that contains both the string 1 and string 2, which the differences are formatted with HTML tags
|
||||||
|
*/
|
||||||
|
|
||||||
|
//function getWordDifferences
|
||||||
|
function formatDifference(
|
||||||
|
oldString,
|
||||||
|
newString,
|
||||||
|
markerAddition = '<span class="x-text-added">{{text}}</span>',
|
||||||
|
markerDeletion = '<span class="x-text-removed">{{text}}</span>'
|
||||||
|
) {
|
||||||
|
[oldString, newString] = [oldString, newString].map((s) =>
|
||||||
|
s
|
||||||
|
.replaceAll(/&/g, '&')
|
||||||
|
.replaceAll(/</g, '<')
|
||||||
|
.replaceAll(/>/g, '>')
|
||||||
|
.replaceAll(/"/g, '"')
|
||||||
|
.replaceAll(/'/g, ''')
|
||||||
|
.replaceAll(/\n/g, '<br>')
|
||||||
|
);
|
||||||
|
|
||||||
|
const oldWords = oldString.split(/\s+/).flatMap((word) => word.split(/(<br>)/));
|
||||||
|
const newWords = newString.split(/\s+/).flatMap((word) => word.split(/(<br>)/));
|
||||||
|
|
||||||
|
function findLongestMatch(oldStart, oldEnd, newStart, newEnd) {
|
||||||
|
let bestOldStart = oldStart;
|
||||||
|
let bestNewStart = newStart;
|
||||||
|
let bestSize = 0;
|
||||||
|
|
||||||
|
const lookup = new Map();
|
||||||
|
for (let i = oldStart; i < oldEnd; i++) {
|
||||||
|
const word = oldWords[i];
|
||||||
|
if (!lookup.has(word)) lookup.set(word, []);
|
||||||
|
lookup.get(word).push(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let j = newStart; j < newEnd; j++) {
|
||||||
|
const word = newWords[j];
|
||||||
|
if (!lookup.has(word)) continue;
|
||||||
|
|
||||||
|
for (const i of lookup.get(word)) {
|
||||||
|
let size = 0;
|
||||||
|
while (i + size < oldEnd && j + size < newEnd && oldWords[i + size] === newWords[j + size]) {
|
||||||
|
size++;
|
||||||
|
}
|
||||||
|
if (size > bestSize) {
|
||||||
|
bestOldStart = i;
|
||||||
|
bestNewStart = j;
|
||||||
|
bestSize = size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
oldStart: bestOldStart,
|
||||||
|
newStart: bestNewStart,
|
||||||
|
size: bestSize
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildDiff(oldStart, oldEnd, newStart, newEnd) {
|
||||||
|
const result = [];
|
||||||
|
const match = findLongestMatch(oldStart, oldEnd, newStart, newEnd);
|
||||||
|
|
||||||
|
if (match.size > 0) {
|
||||||
|
// Handle differences before the match
|
||||||
|
if (oldStart < match.oldStart || newStart < match.newStart) {
|
||||||
|
result.push(...buildDiff(oldStart, match.oldStart, newStart, match.newStart));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the matched words
|
||||||
|
result.push(oldWords.slice(match.oldStart, match.oldStart + match.size).join(' '));
|
||||||
|
|
||||||
|
// Handle differences after the match
|
||||||
|
if (match.oldStart + match.size < oldEnd || match.newStart + match.size < newEnd) {
|
||||||
|
result.push(...buildDiff(match.oldStart + match.size, oldEnd, match.newStart + match.size, newEnd));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
function build(words, start, end, pattern) {
|
||||||
|
let r = [];
|
||||||
|
let ts = words
|
||||||
|
.slice(start, end)
|
||||||
|
.filter((w) => w.length > 0)
|
||||||
|
.join(' ')
|
||||||
|
.split('<br>');
|
||||||
|
for (let i = 0; i < ts.length; i++) {
|
||||||
|
if (i > 0) r.push('<br>');
|
||||||
|
if (ts[i].length < 1) continue;
|
||||||
|
r.push(pattern.replace('{{text}}', ts[i]));
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add deletions
|
||||||
|
if (oldStart < oldEnd) result.push(...build(oldWords, oldStart, oldEnd, markerDeletion));
|
||||||
|
|
||||||
|
// Add insertions
|
||||||
|
if (newStart < newEnd) result.push(...build(newWords, newStart, newEnd, markerAddition));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return buildDiff(0, oldWords.length, 0, newWords.length)
|
||||||
|
.join(' ')
|
||||||
|
.replace(/<br>[ ]+<br>/g, '<br><br>')
|
||||||
|
.replace(/<br> /g, '<br>');
|
||||||
|
}
|
||||||
|
|
||||||
|
function feedTableLookup() {
|
||||||
|
emit('feedTableLookup');
|
||||||
|
}
|
||||||
|
|
||||||
|
function timeToText(time) {
|
||||||
|
return utils.timeToText(time);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -270,9 +270,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import removeConfusables, { removeWhitespace } from '../../service/confusables';
|
|
||||||
import utils from '../../classes/utils';
|
|
||||||
import { friendRequest, userRequest } from '../../api';
|
import { friendRequest, userRequest } from '../../api';
|
||||||
|
import utils from '../../classes/utils';
|
||||||
|
import { languageClass as _languageClass } from '../../composables/user/utils';
|
||||||
|
import removeConfusables, { removeWhitespace } from '../../service/confusables';
|
||||||
|
import { getFaviconUrl as _getFaviconUrl } from '../../composables/shared/utils';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'FriendListTab',
|
name: 'FriendListTab',
|
||||||
@@ -282,8 +284,7 @@
|
|||||||
'showFullscreenImageDialog',
|
'showFullscreenImageDialog',
|
||||||
'showUserDialog',
|
'showUserDialog',
|
||||||
'statusClass',
|
'statusClass',
|
||||||
'openExternalLink',
|
'openExternalLink'
|
||||||
'languageClass'
|
|
||||||
],
|
],
|
||||||
props: {
|
props: {
|
||||||
friends: {
|
friends: {
|
||||||
@@ -322,7 +323,7 @@
|
|||||||
friendsListLoading: false,
|
friendsListLoading: false,
|
||||||
friendsListLoadingProgress: '',
|
friendsListLoadingProgress: '',
|
||||||
friendsListSearchFilterVIP: false,
|
friendsListSearchFilterVIP: false,
|
||||||
// emm
|
// TODO
|
||||||
friendsListBulkUnfriendForceUpdate: 0
|
friendsListBulkUnfriendForceUpdate: 0
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -336,6 +337,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
languageClass(key) {
|
||||||
|
return _languageClass(key);
|
||||||
|
},
|
||||||
friendsListSearchChange() {
|
friendsListSearchChange() {
|
||||||
this.friendsListLoading = true;
|
this.friendsListLoading = true;
|
||||||
let query = '';
|
let query = '';
|
||||||
@@ -505,7 +509,7 @@
|
|||||||
return utils.timeToText(val);
|
return utils.timeToText(val);
|
||||||
},
|
},
|
||||||
getFaviconUrl(link) {
|
getFaviconUrl(link) {
|
||||||
return utils.getFaviconUrl(link);
|
return _getFaviconUrl(link);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
140
src/views/FriendLog/FriendLog.vue
Normal file
140
src/views/FriendLog/FriendLog.vue
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="menuActiveIndex === 'friendLog'" class="x-container">
|
||||||
|
<data-tables v-bind="friendLogTable">
|
||||||
|
<template #tool>
|
||||||
|
<div style="margin: 0 0 10px; display: flex; align-items: center">
|
||||||
|
<el-select
|
||||||
|
v-model="friendLogTable.filters[0].value"
|
||||||
|
multiple
|
||||||
|
clearable
|
||||||
|
style="flex: 1"
|
||||||
|
:placeholder="t('view.friend_log.filter_placeholder')"
|
||||||
|
@change="saveTableFilters">
|
||||||
|
<el-option
|
||||||
|
v-for="type in [
|
||||||
|
'Friend',
|
||||||
|
'Unfriend',
|
||||||
|
'FriendRequest',
|
||||||
|
'CancelFriendRequest',
|
||||||
|
'DisplayName',
|
||||||
|
'TrustLevel'
|
||||||
|
]"
|
||||||
|
:key="type"
|
||||||
|
:label="t('view.friend_log.filters.' + type)"
|
||||||
|
:value="type" />
|
||||||
|
</el-select>
|
||||||
|
<el-input
|
||||||
|
v-model="friendLogTable.filters[1].value"
|
||||||
|
:placeholder="t('view.friend_log.search_placeholder')"
|
||||||
|
style="flex: none; width: 150px; margin-left: 10px" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<el-table-column :label="t('table.friendLog.date')" prop="created_at" sortable="custom" width="200">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-tooltip placement="right">
|
||||||
|
<template #content>
|
||||||
|
<span>{{ scope.row.created_at | formatDate('long') }}</span>
|
||||||
|
</template>
|
||||||
|
<span>{{ scope.row.created_at | formatDate('short') }}</span>
|
||||||
|
</el-tooltip>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column :label="t('table.friendLog.type')" prop="type" width="150">
|
||||||
|
<template #default="scope">
|
||||||
|
<span v-text="t('view.friend_log.filters.' + scope.row.type)"></span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column :label="t('table.friendLog.user')" prop="displayName">
|
||||||
|
<template #default="scope">
|
||||||
|
<span v-if="scope.row.type === 'DisplayName'">
|
||||||
|
{{ scope.row.previousDisplayName }} <i class="el-icon-right"></i>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="x-link"
|
||||||
|
style="padding-right: 10px"
|
||||||
|
@click="showUserDialog(scope.row.userId)"
|
||||||
|
v-text="scope.row.displayName || scope.row.userId"></span>
|
||||||
|
<template v-if="scope.row.type === 'TrustLevel'">
|
||||||
|
<span>
|
||||||
|
({{ scope.row.previousTrustLevel }} <i class="el-icon-right"></i>
|
||||||
|
{{ scope.row.trustLevel }})</span
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column :label="t('table.friendLog.action')" width="80" align="right">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button
|
||||||
|
v-if="shiftHeld"
|
||||||
|
style="color: #f56c6c"
|
||||||
|
type="text"
|
||||||
|
icon="el-icon-close"
|
||||||
|
size="mini"
|
||||||
|
@click="deleteFriendLog(scope.row)"></el-button>
|
||||||
|
<el-button
|
||||||
|
v-else
|
||||||
|
type="text"
|
||||||
|
icon="el-icon-delete"
|
||||||
|
size="mini"
|
||||||
|
@click="deleteFriendLogPrompt(scope.row)"></el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</data-tables>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'FriendLogTab'
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { getCurrentInstance, inject } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n-bridge';
|
||||||
|
import utils from '../../classes/utils';
|
||||||
|
import configRepository from '../../service/config';
|
||||||
|
import database from '../../service/database';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const { proxy } = getCurrentInstance();
|
||||||
|
const { $confirm } = proxy;
|
||||||
|
|
||||||
|
const showUserDialog = inject('showUserDialog');
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
menuActiveIndex: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
friendLogTable: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
shiftHeld: { type: Boolean, default: false }
|
||||||
|
});
|
||||||
|
|
||||||
|
function saveTableFilters() {
|
||||||
|
configRepository.setString('VRCX_friendLogTableFilters', JSON.stringify(props.friendLogTable.filters[0].value));
|
||||||
|
}
|
||||||
|
function deleteFriendLogPrompt(row) {
|
||||||
|
$confirm('Continue? Delete Log', 'Confirm', {
|
||||||
|
confirmButtonText: 'Confirm',
|
||||||
|
cancelButtonText: 'Cancel',
|
||||||
|
type: 'info',
|
||||||
|
callback: (action) => {
|
||||||
|
if (action === 'confirm') {
|
||||||
|
deleteFriendLog(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function deleteFriendLog(row) {
|
||||||
|
utils.removeFromArray(props.friendLogTable.data, row);
|
||||||
|
database.deleteFriendLogHistory(row.rowId);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user