mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-09 18:03:49 +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,
|
||||
params
|
||||
};
|
||||
window.API.$emit('FRIEND:REQUEST:CANCEL', args);
|
||||
// window.API.$emit('FRIEND:REQUEST:CANCEL', args);
|
||||
return args;
|
||||
});
|
||||
},
|
||||
|
||||
@@ -14,7 +14,7 @@ const imageReq = {
|
||||
method: 'PUT'
|
||||
});
|
||||
window.$app.avatarDialog.loading = false;
|
||||
window.$app.changeAvatarImageDialogLoading = false;
|
||||
// window.$app.changeAvatarImageDialogLoading = false;
|
||||
},
|
||||
|
||||
async uploadAvatarImage(params, fileId) {
|
||||
@@ -28,14 +28,15 @@ const imageReq = {
|
||||
params,
|
||||
fileId
|
||||
};
|
||||
window.API.$emit('AVATARIMAGE:INIT', args);
|
||||
// window.API.$emit('AVATARIMAGE:INIT', args);
|
||||
return args;
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
window.API.uploadAvatarFailCleanup(fileId);
|
||||
imageReq.uploadAvatarFailCleanup(fileId);
|
||||
throw err;
|
||||
}
|
||||
return void 0;
|
||||
// return void 0;
|
||||
},
|
||||
|
||||
async uploadAvatarImageFileStart(params) {
|
||||
@@ -50,12 +51,12 @@ const imageReq = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
window.API.$emit('AVATARIMAGE:FILESTART', args);
|
||||
// window.API.$emit('AVATARIMAGE:FILESTART', args);
|
||||
return args;
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
window.API.uploadAvatarFailCleanup(params.fileId);
|
||||
imageReq.uploadAvatarFailCleanup(params.fileId);
|
||||
}
|
||||
return void 0;
|
||||
},
|
||||
@@ -75,7 +76,7 @@ const imageReq = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
window.API.$emit('AVATARIMAGE:FILEFINISH', args);
|
||||
// window.API.$emit('AVATARIMAGE:FILEFINISH', args);
|
||||
return args;
|
||||
});
|
||||
},
|
||||
@@ -92,12 +93,12 @@ const imageReq = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
window.API.$emit('AVATARIMAGE:SIGSTART', args);
|
||||
// window.API.$emit('AVATARIMAGE:SIGSTART', args);
|
||||
return args;
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
window.API.uploadAvatarFailCleanup(params.fileId);
|
||||
imageReq.uploadAvatarFailCleanup(params.fileId);
|
||||
}
|
||||
return void 0;
|
||||
},
|
||||
@@ -117,7 +118,7 @@ const imageReq = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
window.API.$emit('AVATARIMAGE:SIGFINISH', args);
|
||||
// window.API.$emit('AVATARIMAGE:SIGFINISH', args);
|
||||
return args;
|
||||
});
|
||||
},
|
||||
@@ -131,7 +132,7 @@ const imageReq = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
window.API.$emit('AVATARIMAGE:SET', args);
|
||||
// window.API.$emit('AVATARIMAGE:SET', args);
|
||||
window.API.$emit('AVATAR', args);
|
||||
return args;
|
||||
});
|
||||
@@ -152,7 +153,7 @@ const imageReq = {
|
||||
method: 'PUT'
|
||||
});
|
||||
window.$app.worldDialog.loading = false;
|
||||
window.$app.changeWorldImageDialogLoading = false;
|
||||
// window.$app.changeWorldImageDialogLoading = false;
|
||||
},
|
||||
|
||||
async uploadWorldImage(params, fileId) {
|
||||
@@ -166,12 +167,12 @@ const imageReq = {
|
||||
params,
|
||||
fileId
|
||||
};
|
||||
window.API.$emit('WORLDIMAGE:INIT', args);
|
||||
// window.API.$emit('WORLDIMAGE:INIT', args);
|
||||
return args;
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
window.API.uploadWorldFailCleanup(fileId);
|
||||
imageReq.uploadWorldFailCleanup(fileId);
|
||||
}
|
||||
return void 0;
|
||||
},
|
||||
@@ -188,12 +189,12 @@ const imageReq = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
window.API.$emit('WORLDIMAGE:FILESTART', args);
|
||||
// window.API.$emit('WORLDIMAGE:FILESTART', args);
|
||||
return args;
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
window.API.uploadWorldFailCleanup(params.fileId);
|
||||
imageReq.uploadWorldFailCleanup(params.fileId);
|
||||
}
|
||||
return void 0;
|
||||
},
|
||||
@@ -213,7 +214,7 @@ const imageReq = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
window.API.$emit('WORLDIMAGE:FILEFINISH', args);
|
||||
// window.API.$emit('WORLDIMAGE:FILEFINISH', args);
|
||||
return args;
|
||||
});
|
||||
},
|
||||
@@ -230,12 +231,12 @@ const imageReq = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
window.API.$emit('WORLDIMAGE:SIGSTART', args);
|
||||
// window.API.$emit('WORLDIMAGE:SIGSTART', args);
|
||||
return args;
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
window.API.uploadWorldFailCleanup(params.fileId);
|
||||
imageReq.uploadWorldFailCleanup(params.fileId);
|
||||
}
|
||||
return void 0;
|
||||
},
|
||||
@@ -255,7 +256,7 @@ const imageReq = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
window.API.$emit('WORLDIMAGE:SIGFINISH', args);
|
||||
// window.API.$emit('WORLDIMAGE:SIGFINISH', args);
|
||||
return args;
|
||||
});
|
||||
},
|
||||
@@ -269,7 +270,7 @@ const imageReq = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
window.API.$emit('WORLDIMAGE:SET', args);
|
||||
// window.API.$emit('WORLDIMAGE:SET', args);
|
||||
window.API.$emit('WORLD', args);
|
||||
return args;
|
||||
});
|
||||
@@ -283,7 +284,7 @@ const imageReq = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
window.API.$emit('AVATARIMAGE:GET', args);
|
||||
// window.API.$emit('AVATARIMAGE:GET', args);
|
||||
return args;
|
||||
});
|
||||
},
|
||||
@@ -297,7 +298,7 @@ const imageReq = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
window.API.$emit('WORLDIMAGE:GET', args);
|
||||
// window.API.$emit('WORLDIMAGE:GET', args);
|
||||
return args;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ const miscReq = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
window.API.$emit('NOTE', args);
|
||||
// window.API.$emit('NOTE', args);
|
||||
return args;
|
||||
});
|
||||
},
|
||||
@@ -46,7 +46,7 @@ const miscReq = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
window.API.$emit('FEEDBACK:REPORT:USER', args);
|
||||
// window.API.$emit('FEEDBACK:REPORT:USER', args);
|
||||
return args;
|
||||
});
|
||||
},
|
||||
@@ -81,7 +81,7 @@ const miscReq = {
|
||||
const args = {
|
||||
json
|
||||
};
|
||||
window.API.$emit('VRCCREDITS', args);
|
||||
// window.API.$emit('VRCCREDITS', args);
|
||||
return args;
|
||||
});
|
||||
},
|
||||
@@ -170,10 +170,43 @@ const miscReq = {
|
||||
json,
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @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;
|
||||
|
||||
@@ -265,7 +265,8 @@ const notificationReq = {
|
||||
notificationId
|
||||
}
|
||||
};
|
||||
window.API.$emit('NOTIFICATION:V2:HIDE', args);
|
||||
// useless
|
||||
// window.API.$emit('NOTIFICATION:V2:HIDE', args);
|
||||
return args;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ const playerModerationReq = {
|
||||
json,
|
||||
params
|
||||
};
|
||||
window.API.$emit('PLAYER-MODERATION:SEND', args);
|
||||
// window.API.$emit('PLAYER-MODERATION:SEND', args);
|
||||
return args;
|
||||
});
|
||||
},
|
||||
|
||||
@@ -126,7 +126,33 @@ const userReq = {
|
||||
json,
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ const vrcPlusImageReq = {
|
||||
json,
|
||||
printId
|
||||
};
|
||||
window.API.$emit('PRINT:DELETE', args);
|
||||
// window.API.$emit('PRINT:DELETE', 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
|
||||
#x-app.x-app(@dragenter.prevent @dragover.prevent @drop.prevent)
|
||||
//- login
|
||||
include ./mixins/loginPage.pug
|
||||
+loginPage
|
||||
LoginPage(v-if="!API.isLoggedIn" v-bind="loginPageBind" v-on="loginPageEvent")
|
||||
|
||||
VRCXUpdateDialog(v-bind="vrcxUpdateDialogBind" v-on="vrcxUpdateDialogEvent")
|
||||
|
||||
//- menu
|
||||
.x-menu-container
|
||||
@@ -23,47 +23,30 @@ doctype html
|
||||
circle
|
||||
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 ###
|
||||
template(v-if='API.isLoggedIn')
|
||||
//- feed
|
||||
include ./mixins/tabs/feed.pug
|
||||
+feedTab
|
||||
FeedTab(v-bind='feedTabBind' v-on='feedTabEvent')
|
||||
|
||||
//- gameLog
|
||||
include ./mixins/tabs/gameLog.pug
|
||||
+gameLogTab
|
||||
GameLogTab(v-bind='gameLogTabBind' v-on='gameLogTabEvent')
|
||||
|
||||
//- playerList
|
||||
include ./mixins/tabs/playerList.pug
|
||||
+playerListTab
|
||||
PlayerListTab(v-bind='playerListTabBind' v-on='playerListTabEvent')
|
||||
|
||||
//- search
|
||||
include ./mixins/tabs/search.pug
|
||||
+searchTab
|
||||
SearchTab(v-bind='searchTabBind' v-on='searchTabEvent')
|
||||
|
||||
FavoritesTab(v-bind='favoritesTabBind' v-on='favoritesTabEvent')
|
||||
|
||||
//- friendLog
|
||||
include ./mixins/tabs/friendLog.pug
|
||||
+friendLogTab
|
||||
FriendLogTab(v-bind='friendLogTabBind')
|
||||
|
||||
//- moderation
|
||||
ModerationTab(v-bind='moderationTabBind')
|
||||
|
||||
//- notification
|
||||
include ./mixins/tabs/notifications.pug
|
||||
+notificationsTab
|
||||
NotificationTab(v-bind='notificationTabBind' v-on='notificationTabEvent')
|
||||
|
||||
//- profile
|
||||
include ./mixins/tabs/profile.pug
|
||||
+profileTab
|
||||
ProfileTab(v-bind='profileTabBind' v-on='profileTabEvent')
|
||||
|
||||
//- friends list
|
||||
FriendListTab(v-bind='friendsListTabBind' v-on='friendsListTabEvent')
|
||||
|
||||
//- charts
|
||||
KeepAlive
|
||||
ChartsTab(v-if='menuActiveIndex === "charts"' v-bind='chartsTabBind' v-on='chartsTabEvent')
|
||||
|
||||
@@ -73,89 +56,8 @@ doctype html
|
||||
|
||||
SideBar(v-bind='sideBarTabBind' v-on='sideBarTabEvent')
|
||||
|
||||
//- ## Dialogs ## -\\
|
||||
include ./mixins/dialogs/userDialog.pug
|
||||
+userDialog
|
||||
|
||||
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")
|
||||
//- ## Dialogs ## -\\
|
||||
include ./mixins/dialogs/dialogs.pug
|
||||
+dialogs
|
||||
|
||||
//- 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;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: hidden auto;
|
||||
overflow: hidden;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
|
||||
@@ -590,6 +590,10 @@ input[type='number'],
|
||||
.el-table table tr td:first-child {
|
||||
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 table tr:last-child td,
|
||||
.el-table tr,
|
||||
|
||||
@@ -239,13 +239,12 @@ export default class extends baseClass {
|
||||
API.websocketDomain = API.websocketDomainVrchat;
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
this.loginForm.loading = true;
|
||||
if (this.enablePrimaryPassword) {
|
||||
this.checkPrimaryPassword(loginParmas)
|
||||
.then((pwd) => {
|
||||
this.loginForm.loading = true;
|
||||
return API.getConfig()
|
||||
.catch((err) => {
|
||||
this.loginForm.loading = false;
|
||||
reject(err);
|
||||
})
|
||||
.then(() => {
|
||||
@@ -257,12 +256,10 @@ export default class extends baseClass {
|
||||
websocket: loginParmas.websocket
|
||||
})
|
||||
.catch((err2) => {
|
||||
this.loginForm.loading = false;
|
||||
// API.logout();
|
||||
reject(err2);
|
||||
})
|
||||
.then(() => {
|
||||
this.loginForm.loading = false;
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
@@ -277,7 +274,6 @@ export default class extends baseClass {
|
||||
} else {
|
||||
API.getConfig()
|
||||
.catch((err) => {
|
||||
this.loginForm.loading = false;
|
||||
reject(err);
|
||||
})
|
||||
.then(() => {
|
||||
@@ -288,17 +284,15 @@ export default class extends baseClass {
|
||||
websocket: loginParmas.websocket
|
||||
})
|
||||
.catch((err2) => {
|
||||
this.loginForm.loading = false;
|
||||
API.logout();
|
||||
reject(err2);
|
||||
})
|
||||
.then(() => {
|
||||
this.loginForm.loading = false;
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}).finally(() => (this.loginForm.loading = false));
|
||||
},
|
||||
|
||||
async deleteSavedLogin(userId) {
|
||||
@@ -325,100 +319,90 @@ export default class extends baseClass {
|
||||
|
||||
async login() {
|
||||
await webApiService.clearCookies();
|
||||
this.$refs.loginForm.validate((valid) => {
|
||||
if (valid && !this.loginForm.loading) {
|
||||
this.loginForm.loading = true;
|
||||
if (this.loginForm.endpoint) {
|
||||
API.endpointDomain = this.loginForm.endpoint;
|
||||
API.websocketDomain = this.loginForm.websocket;
|
||||
} else {
|
||||
API.endpointDomain = API.endpointDomainVrchat;
|
||||
API.websocketDomain = API.websocketDomainVrchat;
|
||||
}
|
||||
API.getConfig()
|
||||
.catch((err) => {
|
||||
this.loginForm.loading = false;
|
||||
throw err;
|
||||
})
|
||||
.then((args) => {
|
||||
if (
|
||||
this.loginForm.saveCredentials &&
|
||||
this.enablePrimaryPassword
|
||||
) {
|
||||
$app.$prompt(
|
||||
$t('prompt.primary_password.description'),
|
||||
$t('prompt.primary_password.header'),
|
||||
{
|
||||
inputType: 'password',
|
||||
inputPattern: /[\s\S]{1,32}/
|
||||
}
|
||||
)
|
||||
.then(({ value }) => {
|
||||
let saveCredential =
|
||||
this.loginForm.savedCredentials[
|
||||
Object.keys(
|
||||
this.loginForm
|
||||
.savedCredentials
|
||||
)[0]
|
||||
];
|
||||
security
|
||||
.decrypt(
|
||||
saveCredential.loginParmas
|
||||
.password,
|
||||
value
|
||||
)
|
||||
.then(() => {
|
||||
security
|
||||
.encrypt(
|
||||
this.loginForm.password,
|
||||
value
|
||||
)
|
||||
.then((pwd) => {
|
||||
API.login({
|
||||
username:
|
||||
this.loginForm
|
||||
.username,
|
||||
password:
|
||||
this.loginForm
|
||||
.password,
|
||||
endpoint:
|
||||
this.loginForm
|
||||
.endpoint,
|
||||
websocket:
|
||||
this.loginForm
|
||||
.websocket,
|
||||
saveCredentials:
|
||||
this.loginForm
|
||||
.saveCredentials,
|
||||
cipher: pwd
|
||||
}).then(() => {
|
||||
this.$refs.loginForm.resetFields();
|
||||
});
|
||||
if (!this.loginForm.loading) {
|
||||
this.loginForm.loading = true;
|
||||
if (this.loginForm.endpoint) {
|
||||
API.endpointDomain = this.loginForm.endpoint;
|
||||
API.websocketDomain = this.loginForm.websocket;
|
||||
} else {
|
||||
API.endpointDomain = API.endpointDomainVrchat;
|
||||
API.websocketDomain = API.websocketDomainVrchat;
|
||||
}
|
||||
API.getConfig()
|
||||
.catch((err) => {
|
||||
this.loginForm.loading = false;
|
||||
throw err;
|
||||
})
|
||||
.then((args) => {
|
||||
if (
|
||||
this.loginForm.saveCredentials &&
|
||||
this.enablePrimaryPassword
|
||||
) {
|
||||
$app.$prompt(
|
||||
$t('prompt.primary_password.description'),
|
||||
$t('prompt.primary_password.header'),
|
||||
{
|
||||
inputType: 'password',
|
||||
inputPattern: /[\s\S]{1,32}/
|
||||
}
|
||||
)
|
||||
.then(({ value }) => {
|
||||
let saveCredential =
|
||||
this.loginForm.savedCredentials[
|
||||
Object.keys(
|
||||
this.loginForm.savedCredentials
|
||||
)[0]
|
||||
];
|
||||
security
|
||||
.decrypt(
|
||||
saveCredential.loginParmas.password,
|
||||
value
|
||||
)
|
||||
.then(() => {
|
||||
security
|
||||
.encrypt(
|
||||
this.loginForm.password,
|
||||
value
|
||||
)
|
||||
.then((pwd) => {
|
||||
API.login({
|
||||
username:
|
||||
this.loginForm
|
||||
.username,
|
||||
password:
|
||||
this.loginForm
|
||||
.password,
|
||||
endpoint:
|
||||
this.loginForm
|
||||
.endpoint,
|
||||
websocket:
|
||||
this.loginForm
|
||||
.websocket,
|
||||
saveCredentials:
|
||||
this.loginForm
|
||||
.saveCredentials,
|
||||
cipher: pwd
|
||||
});
|
||||
});
|
||||
})
|
||||
.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(() => {
|
||||
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
|
||||
}).finally(() => {
|
||||
this.loginForm.loading = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
return args;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
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 {
|
||||
constructor(_app, _API, _t) {
|
||||
@@ -83,8 +84,8 @@ export default class extends baseClass {
|
||||
args.ref = this.applyCurrentUser(json);
|
||||
|
||||
// when isGameRunning use gameLog instead of API
|
||||
var $location = $app.parseLocation($app.lastLocation.location);
|
||||
var $travelingLocation = $app.parseLocation(
|
||||
var $location = parseLocation($app.lastLocation.location);
|
||||
var $travelingLocation = parseLocation(
|
||||
$app.lastLocationDestination
|
||||
);
|
||||
var location = $app.lastLocation.location;
|
||||
@@ -94,12 +95,12 @@ export default class extends baseClass {
|
||||
var travelingToWorld = $travelingLocation.worldId;
|
||||
var travelingToInstance = $travelingLocation.instanceId;
|
||||
if (!$app.isGameRunning && json.presence) {
|
||||
if ($utils.isRealInstance(json.presence.world)) {
|
||||
if (isRealInstance(json.presence.world)) {
|
||||
location = `${json.presence.world}:${json.presence.instance}`;
|
||||
} else {
|
||||
location = json.presence.world;
|
||||
}
|
||||
if ($utils.isRealInstance(json.presence.travelingToWorld)) {
|
||||
if (isRealInstance(json.presence.travelingToWorld)) {
|
||||
travelingToLocation = `${json.presence.travelingToWorld}:${json.presence.travelingToInstance}`;
|
||||
} else {
|
||||
travelingToLocation = json.presence.travelingToWorld;
|
||||
@@ -175,7 +176,7 @@ export default class extends baseClass {
|
||||
}
|
||||
Object.assign(ref, json);
|
||||
if (ref.homeLocation !== ref.$homeLocation.tag) {
|
||||
ref.$homeLocation = $app.parseLocation(ref.homeLocation);
|
||||
ref.$homeLocation = parseLocation(ref.homeLocation);
|
||||
// apply home location name to user dialog
|
||||
if (
|
||||
$app.userDialog.visible &&
|
||||
@@ -295,13 +296,12 @@ export default class extends baseClass {
|
||||
$languages: [],
|
||||
$locationTag: '',
|
||||
$travelingToLocation: '',
|
||||
$vrchatcredits: null,
|
||||
...json
|
||||
};
|
||||
if ($app.isGameRunning) {
|
||||
ref.$previousAvatarSwapTime = Date.now();
|
||||
}
|
||||
ref.$homeLocation = $app.parseLocation(ref.homeLocation);
|
||||
ref.$homeLocation = parseLocation(ref.homeLocation);
|
||||
ref.$isVRCPlus = ref.tags.includes('system_supporter');
|
||||
this.applyUserTrustLevel(ref);
|
||||
this.applyUserLanguage(ref);
|
||||
@@ -316,32 +316,6 @@ export default class extends baseClass {
|
||||
}
|
||||
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 = {};
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import configRepository from '../service/config.js';
|
||||
import { baseClass, $app, API, $t, $utils } from './baseClass.js';
|
||||
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 {
|
||||
constructor(_app, _API, _t) {
|
||||
@@ -36,7 +38,7 @@ export default class extends baseClass {
|
||||
var L = this.lastLocation$;
|
||||
if (currentLocation !== this.lastLocation$.tag) {
|
||||
Discord.SetTimestamps(timeStamp, 0);
|
||||
L = $app.parseLocation(currentLocation);
|
||||
L = parseLocation(currentLocation);
|
||||
L.worldName = '';
|
||||
L.thumbnailImageUrl = '';
|
||||
L.worldCapacity = 0;
|
||||
@@ -76,7 +78,7 @@ export default class extends baseClass {
|
||||
}
|
||||
switch (L.accessType) {
|
||||
case 'public':
|
||||
L.joinUrl = $utils.getLaunchURL(L);
|
||||
L.joinUrl = getLaunchURL(L);
|
||||
L.accessName = `Public #${L.instanceName} (${platform})`;
|
||||
break;
|
||||
case 'invite+':
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import * as workerTimers from 'worker-timers';
|
||||
import { parseLocation } from '../composables/instance/utils';
|
||||
import gameLogService from '../service/gamelog.js';
|
||||
import configRepository from '../service/config.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 dayjs from 'dayjs';
|
||||
|
||||
@@ -80,7 +81,7 @@ export default class extends baseClass {
|
||||
this.lastLocation.location,
|
||||
gameLog.dt
|
||||
);
|
||||
var worldName = this.replaceBioSymbols(gameLog.worldName);
|
||||
var worldName = $utils.replaceBioSymbols(gameLog.worldName);
|
||||
if (this.isGameRunning) {
|
||||
this.lastLocationReset(gameLog.dt);
|
||||
this.clearNowPlaying();
|
||||
@@ -100,7 +101,7 @@ export default class extends baseClass {
|
||||
this.applyGroupDialogInstances();
|
||||
}
|
||||
this.addInstanceJoinHistory(gameLog.location, gameLog.dt);
|
||||
var L = $utils.parseLocation(gameLog.location);
|
||||
var L = parseLocation(gameLog.location);
|
||||
var entry = {
|
||||
created_at: gameLog.dt,
|
||||
type: 'Location',
|
||||
@@ -789,7 +790,7 @@ export default class extends baseClass {
|
||||
var videoPos = Number(data[1]);
|
||||
var videoLength = Number(data[2]);
|
||||
var displayName = data[3];
|
||||
var videoName = this.replaceBioSymbols(data[4]);
|
||||
var videoName = $utils.replaceBioSymbols(data[4]);
|
||||
var videoUrl = videoName;
|
||||
var videoId = 'LSMedia';
|
||||
if (videoUrl === this.nowPlaying.url) {
|
||||
@@ -981,29 +982,6 @@ export default class extends baseClass {
|
||||
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) {
|
||||
var value = this.gameLogTable.search.toUpperCase();
|
||||
if (!value) {
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
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 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 {
|
||||
photonEmojis,
|
||||
photonEventType
|
||||
} from '../composables/shared/constants/photon.js';
|
||||
|
||||
export default class extends baseClass {
|
||||
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: '',
|
||||
photonEventTableTypeFilter: [],
|
||||
photonEventTableTypeOverlayFilter: [],
|
||||
@@ -894,7 +795,7 @@ export default class extends baseClass {
|
||||
var imageUrl = '';
|
||||
if (type === 0) {
|
||||
var emojiId = data.Parameters[245][2];
|
||||
emojiName = this.photonEmojis[emojiId];
|
||||
emojiName = photonEmojis[emojiId];
|
||||
} else if (type === 1) {
|
||||
emojiName = 'Custom';
|
||||
var fileId = data.Parameters[245][1];
|
||||
@@ -982,7 +883,7 @@ export default class extends baseClass {
|
||||
if (this.debugPhotonLogging) {
|
||||
var displayName = this.getDisplayNameFromPhotonId(senderId);
|
||||
var feed = `RPC ${displayName} ${
|
||||
this.photonEventType[eventData.EventType]
|
||||
photonEventType[eventData.EventType]
|
||||
}${eventName}`;
|
||||
console.log('VrcRpc:', feed);
|
||||
}
|
||||
@@ -1026,7 +927,7 @@ export default class extends baseClass {
|
||||
shortName
|
||||
});
|
||||
var location = instance.json.location;
|
||||
var L = $utils.parseLocation(location);
|
||||
var L = parseLocation(location);
|
||||
var groupName = '';
|
||||
if (L.groupId) {
|
||||
groupName = await this.getGroupName(L.groupId);
|
||||
@@ -1040,14 +941,14 @@ export default class extends baseClass {
|
||||
// if (shortName === newShortName) {
|
||||
// portalType = 'Unlocked';
|
||||
// }
|
||||
var displayLocation = this.displayLocation(
|
||||
var _displayLocation = displayLocation(
|
||||
location,
|
||||
worldName,
|
||||
groupName
|
||||
);
|
||||
this.addEntryPhotonEvent({
|
||||
photonId: this.getPhotonIdFromUserId(userId),
|
||||
text: `PortalSpawn to ${displayLocation}`,
|
||||
text: `PortalSpawn to ${_displayLocation}`,
|
||||
type: 'PortalSpawn',
|
||||
shortName,
|
||||
location,
|
||||
@@ -1210,10 +1111,10 @@ export default class extends baseClass {
|
||||
type: 'ChangeStatus',
|
||||
status: photonUser.status,
|
||||
previousStatus: ref.status,
|
||||
statusDescription: this.replaceBioSymbols(
|
||||
statusDescription: $utils.replaceBioSymbols(
|
||||
photonUser.statusDescription
|
||||
),
|
||||
previousStatusDescription: this.replaceBioSymbols(
|
||||
previousStatusDescription: $utils.replaceBioSymbols(
|
||||
ref.statusDescription
|
||||
),
|
||||
created_at: Date.parse(gameLogDate)
|
||||
@@ -1227,8 +1128,8 @@ export default class extends baseClass {
|
||||
return;
|
||||
}
|
||||
var avatar = user.avatarDict;
|
||||
avatar.name = this.replaceBioSymbols(avatar.name);
|
||||
avatar.description = this.replaceBioSymbols(avatar.description);
|
||||
avatar.name = $utils.replaceBioSymbols(avatar.name);
|
||||
avatar.description = $utils.replaceBioSymbols(avatar.description);
|
||||
var platform = '';
|
||||
if (user.last_platform === 'android') {
|
||||
platform = 'Android';
|
||||
@@ -1240,7 +1141,7 @@ export default class extends baseClass {
|
||||
platform = 'Desktop';
|
||||
}
|
||||
this.photonUserSusieCheck(photonId, user, gameLogDate);
|
||||
$utils.checkVRChatCache(avatar).then((cacheInfo) => {
|
||||
checkVRChatCache(avatar).then((cacheInfo) => {
|
||||
var inCache = false;
|
||||
if (cacheInfo.Item1 > 0) {
|
||||
inCache = true;
|
||||
@@ -1410,9 +1311,11 @@ export default class extends baseClass {
|
||||
oldAvatarId !== avatar.id &&
|
||||
photonId !== this.photonLobbyCurrentUser
|
||||
) {
|
||||
avatar.name = this.replaceBioSymbols(avatar.name);
|
||||
avatar.description = this.replaceBioSymbols(avatar.description);
|
||||
$utils.checkVRChatCache(avatar).then((cacheInfo) => {
|
||||
avatar.name = $utils.replaceBioSymbols(avatar.name);
|
||||
avatar.description = $utils.replaceBioSymbols(
|
||||
avatar.description
|
||||
);
|
||||
checkVRChatCache(avatar).then((cacheInfo) => {
|
||||
var inCache = false;
|
||||
if (cacheInfo.Item1 > 0) {
|
||||
inCache = true;
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
instanceRequest,
|
||||
groupRequest
|
||||
} from '../api';
|
||||
import $utils from './utils';
|
||||
|
||||
export default class extends baseClass {
|
||||
constructor(_app, _API, _t) {
|
||||
@@ -171,8 +172,8 @@ export default class extends baseClass {
|
||||
var D = $app.groupDialog;
|
||||
if (D.id === args.params.groupId) {
|
||||
for (var post of args.posts) {
|
||||
post.title = $app.replaceBioSymbols(post.title);
|
||||
post.text = $app.replaceBioSymbols(post.text);
|
||||
post.title = $utils.replaceBioSymbols(post.title);
|
||||
post.text = $utils.replaceBioSymbols(post.text);
|
||||
}
|
||||
if (args.posts.length > 0) {
|
||||
D.announcement = args.posts[0];
|
||||
@@ -189,8 +190,8 @@ export default class extends baseClass {
|
||||
}
|
||||
|
||||
var newPost = args.json;
|
||||
newPost.title = $app.replaceBioSymbols(newPost.title);
|
||||
newPost.text = $app.replaceBioSymbols(newPost.text);
|
||||
newPost.title = $utils.replaceBioSymbols(newPost.title);
|
||||
newPost.text = $utils.replaceBioSymbols(newPost.text);
|
||||
var hasPost = false;
|
||||
// update existing post
|
||||
for (var post of D.posts) {
|
||||
@@ -275,9 +276,9 @@ export default class extends baseClass {
|
||||
|
||||
API.applyGroup = function (json) {
|
||||
var ref = this.cachedGroups.get(json.id);
|
||||
json.rules = $app.replaceBioSymbols(json.rules);
|
||||
json.name = $app.replaceBioSymbols(json.name);
|
||||
json.description = $app.replaceBioSymbols(json.description);
|
||||
json.rules = $utils.replaceBioSymbols(json.rules);
|
||||
json.name = $utils.replaceBioSymbols(json.name);
|
||||
json.description = $utils.replaceBioSymbols(json.description);
|
||||
if (typeof ref === 'undefined') {
|
||||
ref = {
|
||||
id: '',
|
||||
@@ -912,9 +913,6 @@ export default class extends baseClass {
|
||||
case 'Unsubscribe To Announcements':
|
||||
this.setGroupSubscription(D.id, false);
|
||||
break;
|
||||
case 'Invite To Group':
|
||||
this.showInviteGroupDialog(D.id, '');
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1050,17 +1048,6 @@ export default class extends baseClass {
|
||||
this.userDialog.representedGroup = args.json;
|
||||
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;
|
||||
});
|
||||
|
||||
API.$on('LOGOUT', function () {
|
||||
$app.languageDialog.visible = false;
|
||||
});
|
||||
}
|
||||
|
||||
_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: [],
|
||||
|
||||
languageDialog: {
|
||||
@@ -113,54 +37,5 @@ export default class extends baseClass {
|
||||
}
|
||||
};
|
||||
|
||||
_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;
|
||||
}
|
||||
};
|
||||
_methods = {};
|
||||
}
|
||||
|
||||
@@ -27,11 +27,6 @@ export default class extends baseClass {
|
||||
}
|
||||
},
|
||||
|
||||
onUserMemoChange() {
|
||||
var D = this.userDialog;
|
||||
this.saveUserMemo(D.id, D.memo);
|
||||
},
|
||||
|
||||
async getUserMemo(userId) {
|
||||
try {
|
||||
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() {
|
||||
this.$prompt(
|
||||
$t('prompt.direct_access_omni.description'),
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import Vue from 'vue';
|
||||
import VueMarkdown from 'vue-markdown';
|
||||
import { baseClass, $app, API, $t, $utils } from './baseClass.js';
|
||||
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 {
|
||||
constructor(_app, _API, _t) {
|
||||
@@ -60,7 +61,7 @@ export default class extends baseClass {
|
||||
this.selfInvite(this.location, this.shortname);
|
||||
},
|
||||
selfInvite(location, shortName) {
|
||||
const L = utils.parseLocation(location);
|
||||
const L = parseLocation(location);
|
||||
if (!L.isRealInstance) {
|
||||
return;
|
||||
}
|
||||
@@ -165,7 +166,7 @@ export default class extends baseClass {
|
||||
if (!this.location) {
|
||||
return;
|
||||
}
|
||||
var L = $utils.parseLocation(this.location);
|
||||
var L = parseLocation(this.location);
|
||||
if (!L.groupId) {
|
||||
return;
|
||||
}
|
||||
@@ -320,7 +321,7 @@ export default class extends baseClass {
|
||||
// check group perms
|
||||
var groupId = this.instance.ownerId;
|
||||
var group = API.cachedGroups.get(groupId);
|
||||
this.canCloseInstance = utils.hasGroupPermission(
|
||||
this.canCloseInstance = hasGroupPermission(
|
||||
group,
|
||||
'group-instance-moderate'
|
||||
);
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import Noty from 'noty';
|
||||
|
||||
let echarts = null;
|
||||
|
||||
// messy here, organize later
|
||||
const _utils = {
|
||||
removeFromArray(array, item) {
|
||||
var { length } = array;
|
||||
@@ -14,7 +11,6 @@ const _utils = {
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
arraysMatch(a, b) {
|
||||
if (!Array.isArray(a) || !Array.isArray(b)) {
|
||||
return false;
|
||||
@@ -27,12 +23,10 @@ const _utils = {
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
escapeTag(tag) {
|
||||
var s = String(tag);
|
||||
return s.replace(/["&'<>]/g, (c) => `&#${c.charCodeAt(0)};`);
|
||||
},
|
||||
|
||||
escapeTagRecursive(obj) {
|
||||
if (typeof obj === 'string') {
|
||||
return this.escapeTag(obj);
|
||||
@@ -44,7 +38,6 @@ const _utils = {
|
||||
}
|
||||
return obj;
|
||||
},
|
||||
|
||||
timeToText(sec, isNeedSeconds = false) {
|
||||
let n = Number(sec);
|
||||
if (isNaN(n)) {
|
||||
@@ -72,7 +65,6 @@ const _utils = {
|
||||
}
|
||||
return arr.join(' ');
|
||||
},
|
||||
|
||||
textToHex(text) {
|
||||
var s = String(text);
|
||||
return s
|
||||
@@ -80,7 +72,6 @@ const _utils = {
|
||||
.map((c) => c.charCodeAt(0).toString(16))
|
||||
.join(' ');
|
||||
},
|
||||
|
||||
commaNumber(num) {
|
||||
if (!num) {
|
||||
return '0';
|
||||
@@ -88,182 +79,6 @@ const _utils = {
|
||||
var s = String(Number(num));
|
||||
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) {
|
||||
var node = [];
|
||||
for (var key in json) {
|
||||
@@ -325,8 +140,6 @@ const _utils = {
|
||||
});
|
||||
return node;
|
||||
},
|
||||
|
||||
// app.js 4900ln
|
||||
// descending
|
||||
compareByCreatedAt(a, b) {
|
||||
if (
|
||||
@@ -406,298 +219,69 @@ const _utils = {
|
||||
}
|
||||
return false;
|
||||
},
|
||||
convertFileUrlToImageUrl(url, resolution = 128) {
|
||||
if (!url) {
|
||||
compareByName(a, b) {
|
||||
if (typeof a.name !== 'string' || typeof b.name !== 'string') {
|
||||
return 0;
|
||||
}
|
||||
return a.name.localeCompare(b.name);
|
||||
},
|
||||
replaceBioSymbols(text) {
|
||||
if (!text) {
|
||||
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}`;
|
||||
var symbolList = {
|
||||
'@': '@',
|
||||
'#': '#',
|
||||
$: '$',
|
||||
'%': '%',
|
||||
'&': '&',
|
||||
'=': '=',
|
||||
'+': '+',
|
||||
'/': '⁄',
|
||||
'\\': '\',
|
||||
';': ';',
|
||||
':': '˸',
|
||||
',': '‚',
|
||||
'?': '?',
|
||||
'!': 'ǃ',
|
||||
'"': '"',
|
||||
'<': '≺',
|
||||
'>': '≻',
|
||||
'.': '․',
|
||||
'^': '^',
|
||||
'{': '{',
|
||||
'}': '}',
|
||||
'[': '[',
|
||||
']': ']',
|
||||
'(': '(',
|
||||
')': ')',
|
||||
'|': '|',
|
||||
'*': '∗'
|
||||
};
|
||||
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 url;
|
||||
return newText.replace(/ {1,}/g, ' ').trimRight();
|
||||
},
|
||||
replaceVrcPackageUrl(url) {
|
||||
if (!url) {
|
||||
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) {
|
||||
// descending
|
||||
compareByUpdatedAt(a, b) {
|
||||
if (
|
||||
ref &&
|
||||
ref.myMember &&
|
||||
ref.myMember.permissions &&
|
||||
(ref.myMember.permissions.includes('*') ||
|
||||
ref.myMember.permissions.includes(permission))
|
||||
typeof a.updated_at !== 'string' ||
|
||||
typeof b.updated_at !== 'string'
|
||||
) {
|
||||
return true;
|
||||
return 0;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
compareUnityVersion(unitySortNumber) {
|
||||
if (!window.API.cachedConfig.sdkUnityVersion) {
|
||||
console.error('No cachedConfig.sdkUnityVersion');
|
||||
return false;
|
||||
var A = a.updated_at.toUpperCase();
|
||||
var B = b.updated_at.toUpperCase();
|
||||
if (A < B) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 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 = API.cachedConfig.sdkUnityVersion.split('.');
|
||||
if (array.length < 3) {
|
||||
console.error('Invalid cachedConfig.sdkUnityVersion');
|
||||
return false;
|
||||
if (A > B) {
|
||||
return -1;
|
||||
}
|
||||
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;
|
||||
},
|
||||
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 };
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { baseClass, $app, API, $t, $utils } from './baseClass.js';
|
||||
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 {
|
||||
constructor(_app, _API, _t) {
|
||||
@@ -356,8 +358,8 @@ export default class extends baseClass {
|
||||
|
||||
async notySaveImage(noty) {
|
||||
var imageUrl = await this.notyGetImage(noty);
|
||||
var fileId = this.extractFileId(imageUrl);
|
||||
var fileVersion = this.extractFileVersion(imageUrl);
|
||||
var fileId = extractFileId(imageUrl);
|
||||
var fileVersion = extractFileVersion(imageUrl);
|
||||
var imageLocation = '';
|
||||
try {
|
||||
if (fileId && fileVersion) {
|
||||
@@ -414,7 +416,7 @@ export default class extends baseClass {
|
||||
break;
|
||||
case 'GPS':
|
||||
this.speak(
|
||||
`${displayName} is in ${this.displayLocation(
|
||||
`${displayName} is in ${displayLocation(
|
||||
noty.location,
|
||||
noty.worldName,
|
||||
noty.groupName
|
||||
@@ -424,7 +426,7 @@ export default class extends baseClass {
|
||||
case 'Online':
|
||||
var locationName = '';
|
||||
if (noty.worldName) {
|
||||
locationName = ` to ${this.displayLocation(
|
||||
locationName = ` to ${displayLocation(
|
||||
noty.location,
|
||||
noty.worldName,
|
||||
noty.groupName
|
||||
@@ -442,7 +444,7 @@ export default class extends baseClass {
|
||||
break;
|
||||
case 'invite':
|
||||
this.speak(
|
||||
`${displayName} has invited you to ${this.displayLocation(
|
||||
`${displayName} has invited you to ${displayLocation(
|
||||
noty.details.worldId,
|
||||
noty.details.worldName,
|
||||
noty.groupName
|
||||
@@ -513,7 +515,7 @@ export default class extends baseClass {
|
||||
case 'PortalSpawn':
|
||||
if (displayName) {
|
||||
this.speak(
|
||||
`${displayName} has spawned a portal to ${this.displayLocation(
|
||||
`${displayName} has spawned a portal to ${displayLocation(
|
||||
noty.instanceId,
|
||||
noty.worldName,
|
||||
noty.groupName
|
||||
@@ -599,7 +601,7 @@ export default class extends baseClass {
|
||||
case 'GPS':
|
||||
AppApi.XSNotification(
|
||||
'VRCX',
|
||||
`${noty.displayName} is in ${this.displayLocation(
|
||||
`${noty.displayName} is in ${displayLocation(
|
||||
noty.location,
|
||||
noty.worldName,
|
||||
noty.groupName
|
||||
@@ -611,7 +613,7 @@ export default class extends baseClass {
|
||||
case 'Online':
|
||||
var locationName = '';
|
||||
if (noty.worldName) {
|
||||
locationName = ` to ${this.displayLocation(
|
||||
locationName = ` to ${displayLocation(
|
||||
noty.location,
|
||||
noty.worldName,
|
||||
noty.groupName
|
||||
@@ -645,7 +647,7 @@ export default class extends baseClass {
|
||||
'VRCX',
|
||||
`${
|
||||
noty.senderUsername
|
||||
} has invited you to ${this.displayLocation(
|
||||
} has invited you to ${displayLocation(
|
||||
noty.details.worldId,
|
||||
noty.details.worldName
|
||||
)}${message}`,
|
||||
@@ -755,7 +757,7 @@ export default class extends baseClass {
|
||||
'VRCX',
|
||||
`${
|
||||
noty.displayName
|
||||
} has spawned a portal to ${this.displayLocation(
|
||||
} has spawned a portal to ${displayLocation(
|
||||
noty.instanceId,
|
||||
noty.worldName,
|
||||
noty.groupName
|
||||
@@ -915,7 +917,7 @@ export default class extends baseClass {
|
||||
playOvrtHudNotifications,
|
||||
playOvrtWristNotifications,
|
||||
'VRCX',
|
||||
`${noty.displayName} is in ${this.displayLocation(
|
||||
`${noty.displayName} is in ${displayLocation(
|
||||
noty.location,
|
||||
noty.worldName,
|
||||
noty.groupName
|
||||
@@ -927,7 +929,7 @@ export default class extends baseClass {
|
||||
case 'Online':
|
||||
var locationName = '';
|
||||
if (noty.worldName) {
|
||||
locationName = ` to ${this.displayLocation(
|
||||
locationName = ` to ${displayLocation(
|
||||
noty.location,
|
||||
noty.worldName,
|
||||
noty.groupName
|
||||
@@ -969,7 +971,7 @@ export default class extends baseClass {
|
||||
'VRCX',
|
||||
`${
|
||||
noty.senderUsername
|
||||
} has invited you to ${this.displayLocation(
|
||||
} has invited you to ${displayLocation(
|
||||
noty.details.worldId,
|
||||
noty.details.worldName
|
||||
)}${message}`,
|
||||
@@ -1155,7 +1157,7 @@ export default class extends baseClass {
|
||||
'VRCX',
|
||||
`${
|
||||
noty.displayName
|
||||
} has spawned a portal to ${this.displayLocation(
|
||||
} has spawned a portal to ${displayLocation(
|
||||
noty.instanceId,
|
||||
noty.worldName,
|
||||
noty.groupName
|
||||
@@ -1345,7 +1347,7 @@ export default class extends baseClass {
|
||||
case 'GPS':
|
||||
this.desktopNotification(
|
||||
noty.displayName,
|
||||
`is in ${this.displayLocation(
|
||||
`is in ${displayLocation(
|
||||
noty.location,
|
||||
noty.worldName,
|
||||
noty.groupName
|
||||
@@ -1356,7 +1358,7 @@ export default class extends baseClass {
|
||||
case 'Online':
|
||||
var locationName = '';
|
||||
if (noty.worldName) {
|
||||
locationName = ` to ${this.displayLocation(
|
||||
locationName = ` to ${displayLocation(
|
||||
noty.location,
|
||||
noty.worldName,
|
||||
noty.groupName
|
||||
@@ -1385,7 +1387,7 @@ export default class extends baseClass {
|
||||
case 'invite':
|
||||
this.desktopNotification(
|
||||
noty.senderUsername,
|
||||
`has invited you to ${this.displayLocation(
|
||||
`has invited you to ${displayLocation(
|
||||
noty.details.worldId,
|
||||
noty.details.worldName
|
||||
)}${message}`,
|
||||
@@ -1515,7 +1517,7 @@ export default class extends baseClass {
|
||||
if (noty.displayName) {
|
||||
this.desktopNotification(
|
||||
noty.displayName,
|
||||
`has spawned a portal to ${this.displayLocation(
|
||||
`has spawned a portal to ${displayLocation(
|
||||
noty.instanceId,
|
||||
noty.worldName,
|
||||
noty.groupName
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as workerTimers from 'worker-timers';
|
||||
import Noty from 'noty';
|
||||
import { parseLocation } from '../composables/instance/utils';
|
||||
import { baseClass, $app, API, $utils } from './baseClass.js';
|
||||
import { groupRequest } from '../api';
|
||||
|
||||
@@ -265,8 +266,8 @@ export default class extends baseClass {
|
||||
case 'friend-online':
|
||||
// Where is instanceId, travelingToWorld, travelingToInstance?
|
||||
// More JANK, what a mess
|
||||
var $location = $utils.parseLocation(content.location);
|
||||
var $travelingToLocation = $utils.parseLocation(
|
||||
var $location = parseLocation(content.location);
|
||||
var $travelingToLocation = parseLocation(
|
||||
content.travelingToLocation
|
||||
);
|
||||
if (content?.user?.id) {
|
||||
@@ -367,8 +368,8 @@ export default class extends baseClass {
|
||||
break;
|
||||
|
||||
case 'friend-location':
|
||||
var $location = $utils.parseLocation(content.location);
|
||||
var $travelingToLocation = $utils.parseLocation(
|
||||
var $location = parseLocation(content.location);
|
||||
var $travelingToLocation = parseLocation(
|
||||
content.travelingToLocation
|
||||
);
|
||||
if (!content?.user?.id) {
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import utils from '../classes/utils';
|
||||
import { parseLocation } from '../composables/instance/utils';
|
||||
|
||||
export default {
|
||||
// eslint-disable-next-line vue/multi-word-component-names
|
||||
@@ -74,7 +74,7 @@
|
||||
instanceId = this.traveling;
|
||||
this.isTraveling = true;
|
||||
}
|
||||
const L = utils.parseLocation(instanceId);
|
||||
const L = parseLocation(instanceId);
|
||||
if (L.isOffline) {
|
||||
this.text = 'Offline';
|
||||
} else if (L.isPrivate) {
|
||||
@@ -150,7 +150,7 @@
|
||||
if (!location || !this.link) {
|
||||
return;
|
||||
}
|
||||
const L = utils.parseLocation(location);
|
||||
const L = parseLocation(location);
|
||||
if (!L.groupId) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
<safe-dialog
|
||||
ref="avatarDialogRef"
|
||||
class="x-dialog x-avatar-dialog"
|
||||
:before-close="beforeDialogClose"
|
||||
:visible.sync="avatarDialog.visible"
|
||||
:show-close="false"
|
||||
width="600px"
|
||||
@mousedown.native="dialogMouseDown"
|
||||
@mouseup.native="dialogMouseUp">
|
||||
width="600px">
|
||||
<div v-loading="avatarDialog.loading">
|
||||
<div style="display: flex">
|
||||
<el-popover placement="right" width="500px" trigger="click">
|
||||
@@ -506,26 +503,40 @@
|
||||
</div>
|
||||
<SetAvatarTagsDialog :set-avatar-tags-dialog="setAvatarTagsDialog" />
|
||||
<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>
|
||||
|
||||
<script setup>
|
||||
import { inject, computed, getCurrentInstance, reactive, nextTick, watch, ref } from 'vue';
|
||||
import utils from '../../../classes/utils';
|
||||
import database from '../../../service/database';
|
||||
import { avatarModerationRequest, avatarRequest, favoriteRequest, miscRequest } from '../../../api';
|
||||
import { computed, getCurrentInstance, inject, nextTick, reactive, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
|
||||
import SetAvatarTagsDialog from './SetAvatarTagsDialog.vue';
|
||||
import { avatarModerationRequest, avatarRequest, favoriteRequest, imageRequest, miscRequest } from '../../../api';
|
||||
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 SetAvatarTagsDialog from './SetAvatarTagsDialog.vue';
|
||||
|
||||
const API = inject('API');
|
||||
const beforeDialogClose = inject('beforeDialogClose');
|
||||
const dialogMouseDown = inject('dialogMouseDown');
|
||||
const dialogMouseUp = inject('dialogMouseUp');
|
||||
const showFullscreenImageDialog = inject('showFullscreenImageDialog');
|
||||
const showUserDialog = inject('showUserDialog');
|
||||
const displayPreviousImages = inject('displayPreviousImages');
|
||||
const showAvatarDialog = inject('showAvatarDialog');
|
||||
const showFavoriteDialog = inject('showFavoriteDialog');
|
||||
const openExternalLink = inject('openExternalLink');
|
||||
@@ -533,11 +544,9 @@
|
||||
|
||||
const { t } = useI18n();
|
||||
const instance = getCurrentInstance();
|
||||
const $message = instance.proxy.$message;
|
||||
const $confirm = instance.proxy.$confirm;
|
||||
const $prompt = instance.proxy.$prompt;
|
||||
const { $message, $confirm, $prompt } = instance.proxy;
|
||||
|
||||
const emit = defineEmits(['openFolderGeneric', 'deleteVRChatCache']);
|
||||
const emit = defineEmits(['openFolderGeneric', 'deleteVRChatCache', 'openPreviousImagesDialog']);
|
||||
|
||||
const props = defineProps({
|
||||
avatarDialog: {
|
||||
@@ -555,6 +564,10 @@
|
||||
});
|
||||
|
||||
const avatarDialogRef = ref(null);
|
||||
const changeAvatarImageDialogVisible = ref(false);
|
||||
const previousImagesFileId = ref('');
|
||||
const previousImagesDialogVisible = ref(false);
|
||||
const previousImagesTable = ref([]);
|
||||
|
||||
const treeData = ref([]);
|
||||
const timeSpent = ref(0);
|
||||
@@ -671,16 +684,16 @@
|
||||
showAvatarDialog(D.id);
|
||||
break;
|
||||
case 'Share':
|
||||
utils.copyToClipboard(D.id);
|
||||
copyToClipboard(D.id);
|
||||
break;
|
||||
case 'Rename':
|
||||
promptRenameAvatar(D);
|
||||
break;
|
||||
case 'Change Image':
|
||||
displayPreviousImages('Avatar', 'Change');
|
||||
displayPreviousImages('Change');
|
||||
break;
|
||||
case 'Previous Images':
|
||||
displayPreviousImages('Avatar', 'Display');
|
||||
displayPreviousImages('Display');
|
||||
break;
|
||||
case 'Change Description':
|
||||
promptChangeAvatarDescription(D);
|
||||
@@ -692,7 +705,7 @@
|
||||
showSetAvatarStylesDialog(D.id);
|
||||
break;
|
||||
case 'Download Unity Package':
|
||||
openExternalLink(utils.replaceVrcPackageUrl(props.avatarDialog.ref.unityPackageUrl));
|
||||
openExternalLink(replaceVrcPackageUrl(props.avatarDialog.ref.unityPackageUrl));
|
||||
break;
|
||||
case 'Add Favorite':
|
||||
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) {
|
||||
avatarRequest
|
||||
.selectAvatar({
|
||||
@@ -937,11 +998,11 @@
|
||||
}
|
||||
|
||||
function copyAvatarId(id) {
|
||||
utils.copyToClipboard(id);
|
||||
copyToClipboard(id);
|
||||
}
|
||||
|
||||
function copyAvatarUrl(id) {
|
||||
utils.copyToClipboard(`https://vrchat.com/home/avatar/${id}`);
|
||||
copyToClipboard(`https://vrchat.com/home/avatar/${id}`);
|
||||
}
|
||||
|
||||
function timeToText(time) {
|
||||
@@ -963,10 +1024,7 @@
|
||||
if (unityPackage.variant !== 'security') {
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
unityPackage.platform === 'standalonewindows' &&
|
||||
utils.compareUnityVersion(unityPackage.unitySortNumber)
|
||||
) {
|
||||
if (unityPackage.platform === 'standalonewindows' && compareUnityVersion(unityPackage.unitySortNumber)) {
|
||||
assetUrl = unityPackage.assetUrl;
|
||||
break;
|
||||
}
|
||||
@@ -979,7 +1037,7 @@
|
||||
}
|
||||
if (
|
||||
unityPackage.platform === 'standalonewindows' &&
|
||||
utils.compareUnityVersion(unityPackage.unitySortNumber)
|
||||
compareUnityVersion(unityPackage.unitySortNumber)
|
||||
) {
|
||||
variant = 'standard';
|
||||
assetUrl = unityPackage.assetUrl;
|
||||
@@ -990,8 +1048,8 @@
|
||||
if (!assetUrl) {
|
||||
assetUrl = D.ref.assetUrl;
|
||||
}
|
||||
const fileId = utils.extractFileId(assetUrl);
|
||||
const version = parseInt(utils.extractFileVersion(assetUrl), 10);
|
||||
const fileId = extractFileId(assetUrl);
|
||||
const version = parseInt(extractFileVersion(assetUrl), 10);
|
||||
if (!fileId || !version) {
|
||||
$message({
|
||||
message: 'File Analysis unavailable',
|
||||
@@ -1100,8 +1158,4 @@
|
||||
D.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
function downloadAndSaveJson(fileName, data) {
|
||||
utils.downloadAndSaveJson(fileName, data);
|
||||
}
|
||||
</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>
|
||||
<el-dialog
|
||||
<safe-dialog
|
||||
ref="setAvatarStylesDialog"
|
||||
class="x-dialog"
|
||||
:before-close="beforeDialogClose"
|
||||
:visible.sync="setAvatarStylesDialog.visible"
|
||||
:title="t('dialog.set_avatar_styles.header')"
|
||||
width="400px"
|
||||
append-to-body
|
||||
@mousedown.native="dialogMouseDown"
|
||||
@mouseup.native="dialogMouseUp">
|
||||
append-to-body>
|
||||
<template v-if="setAvatarStylesDialog.visible">
|
||||
<div>
|
||||
<span>{{ t('dialog.set_avatar_styles.primary_style') }}</span>
|
||||
@@ -50,19 +47,15 @@
|
||||
t('dialog.set_avatar_styles.save')
|
||||
}}</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</safe-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { inject, watch, getCurrentInstance } from 'vue';
|
||||
import { watch, getCurrentInstance } from 'vue';
|
||||
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { avatarRequest } from '../../../api';
|
||||
|
||||
const beforeDialogClose = inject('beforeDialogClose');
|
||||
const dialogMouseDown = inject('dialogMouseDown');
|
||||
const dialogMouseUp = inject('dialogMouseUp');
|
||||
|
||||
const { t } = useI18n();
|
||||
const instance = getCurrentInstance();
|
||||
const $message = instance.proxy.$message;
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
<safe-dialog
|
||||
ref="setAvatarTagsDialog"
|
||||
class="x-dialog"
|
||||
:before-close="beforeDialogClose"
|
||||
:visible.sync="setAvatarTagsDialog.visible"
|
||||
:title="t('dialog.set_avatar_tags.header')"
|
||||
width="770px"
|
||||
append-to-body
|
||||
@mousedown.native="dialogMouseDown"
|
||||
@mouseup.native="dialogMouseUp">
|
||||
append-to-body>
|
||||
<template v-if="setAvatarTagsDialog.visible">
|
||||
<el-checkbox v-model="setAvatarTagsDialog.contentHorror" @change="updateSelectedAvatarTags">{{
|
||||
t('dialog.set_avatar_tags.content_horror')
|
||||
@@ -93,7 +90,7 @@
|
||||
t('dialog.set_avatar_tags.save')
|
||||
}}</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</safe-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@@ -102,9 +99,6 @@
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import { avatarRequest } from '../../../api';
|
||||
|
||||
const beforeDialogClose = inject('beforeDialogClose');
|
||||
const dialogMouseDown = inject('dialogMouseDown');
|
||||
const dialogMouseUp = inject('dialogMouseUp');
|
||||
const showAvatarDialog = inject('showAvatarDialog');
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
ref="favoriteDialog"
|
||||
:before-close="beforeDialogClose"
|
||||
:visible.sync="isVisible"
|
||||
:title="$t('dialog.favorite.header')"
|
||||
width="300px"
|
||||
@mousedown.native="dialogMouseDown"
|
||||
@mouseup.native="dialogMouseUp">
|
||||
<safe-dialog ref="favoriteDialog" :visible.sync="isVisible" :title="$t('dialog.favorite.header')" width="300px">
|
||||
<div v-loading="loading">
|
||||
<span style="display: block; text-align: center">{{ $t('dialog.favorite.vrchat_favorites') }}</span>
|
||||
<template v-if="favoriteDialog.currentGroup && favoriteDialog.currentGroup.key">
|
||||
@@ -69,7 +62,7 @@
|
||||
</el-button>
|
||||
</template>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</safe-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -78,7 +71,7 @@
|
||||
|
||||
export default {
|
||||
name: 'ChooseFavoriteGroupDialog',
|
||||
inject: ['API', 'beforeDialogClose', 'dialogMouseDown', 'dialogMouseUp', 'adjustDialogZ'],
|
||||
inject: ['API', 'adjustDialogZ'],
|
||||
props: {
|
||||
favoriteDialog: {
|
||||
type: Object,
|
||||
@@ -186,7 +179,7 @@
|
||||
this.$emit('remove-local-avatar-favorite', ...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>
|
||||
<el-dialog
|
||||
<safe-dialog
|
||||
ref="groupDialogRef"
|
||||
:before-close="beforeDialogClose"
|
||||
:visible.sync="groupDialog.visible"
|
||||
:show-close="false"
|
||||
width="770px"
|
||||
top="10vh"
|
||||
class="x-dialog x-group-dialog"
|
||||
@mousedown.native="dialogMouseDown"
|
||||
@mouseup.native="dialogMouseUp">
|
||||
class="x-dialog x-group-dialog">
|
||||
<div class="group-banner-image">
|
||||
<el-popover placement="right" width="500px" trigger="click">
|
||||
<img
|
||||
@@ -1151,10 +1148,7 @@
|
||||
</el-tabs>
|
||||
</div>
|
||||
<!--Nested-->
|
||||
<GroupPostEditDialog
|
||||
:gallery-select-dialog="gallerySelectDialog"
|
||||
:dialog-data.sync="groupPostEditDialog"
|
||||
@clear-image-gallery-select="clearImageGallerySelect" />
|
||||
<GroupPostEditDialog :dialog-data.sync="groupPostEditDialog" :selected-gallery-file="selectedGalleryFile" />
|
||||
<GroupMemberModerationDialog
|
||||
:group-dialog="groupDialog"
|
||||
:is-group-members-loading.sync="isGroupMembersLoading"
|
||||
@@ -1167,25 +1161,32 @@
|
||||
@load-all-group-members="loadAllGroupMembers"
|
||||
@set-group-member-filter="setGroupMemberFilter"
|
||||
@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>
|
||||
|
||||
<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 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 { 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 beforeDialogClose = inject('beforeDialogClose');
|
||||
const dialogMouseDown = inject('dialogMouseDown');
|
||||
const dialogMouseUp = inject('dialogMouseUp');
|
||||
const showFullscreenImageDialog = inject('showFullscreenImageDialog');
|
||||
const languageClass = inject('languageClass');
|
||||
const showUserDialog = inject('showUserDialog');
|
||||
const userStatusClass = inject('userStatusClass');
|
||||
const userImage = inject('userImage');
|
||||
@@ -1222,28 +1223,33 @@
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
gallerySelectDialog: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
randomUserColours: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
vipFriends: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
onlineFriends: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
offlineFriends: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
activeFriends: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits([
|
||||
'update:group-dialog',
|
||||
'update:gallery-select-dialog',
|
||||
'update:group-member-moderation',
|
||||
'group-dialog-command',
|
||||
'update:group-dialog',
|
||||
'groupDialogCommand',
|
||||
'get-group-dialog-group',
|
||||
'get-group-dialog-group-members',
|
||||
'refresh-instance-player-count',
|
||||
'update-group-post-search',
|
||||
'set-group-member-sort-order',
|
||||
'clear-image-gallery-select'
|
||||
'updateGroupPostSearch'
|
||||
]);
|
||||
|
||||
const groupDialogRef = ref(null);
|
||||
@@ -1254,6 +1260,10 @@
|
||||
const groupDialogGalleryCurrentName = ref('0');
|
||||
const groupDialogTabCurrentName = ref('0');
|
||||
const isGroupGalleryLoading = ref(false);
|
||||
const selectedGalleryFile = ref({
|
||||
selectedFileId: '',
|
||||
selectedImageUrl: ''
|
||||
});
|
||||
const groupPostEditDialog = reactive({
|
||||
visible: false,
|
||||
groupRef: {},
|
||||
@@ -1273,6 +1283,16 @@
|
||||
auditLogTypes: []
|
||||
});
|
||||
|
||||
const inviteGroupDialog = ref({
|
||||
visible: false,
|
||||
loading: false,
|
||||
groupId: '',
|
||||
groupName: '',
|
||||
userId: '',
|
||||
userIds: [],
|
||||
userObject: {}
|
||||
});
|
||||
|
||||
let loadMoreGroupMembersParams = {};
|
||||
|
||||
watch(
|
||||
@@ -1293,8 +1313,15 @@
|
||||
}
|
||||
);
|
||||
|
||||
function getFaviconUrl(link) {
|
||||
return utils.getFaviconUrl(link);
|
||||
function showInviteGroupDialog(groupId, userId) {
|
||||
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) {
|
||||
@@ -1439,9 +1466,7 @@
|
||||
}
|
||||
});
|
||||
}
|
||||
function copyToClipboard(text) {
|
||||
utils.copyToClipboard(text);
|
||||
}
|
||||
|
||||
function groupGalleryStatus(gallery) {
|
||||
const style = {};
|
||||
if (!gallery.membersOnly) {
|
||||
@@ -1455,6 +1480,10 @@
|
||||
}
|
||||
|
||||
function groupDialogCommand(command) {
|
||||
const D = props.groupDialog;
|
||||
if (D.visible === false) {
|
||||
return;
|
||||
}
|
||||
switch (command) {
|
||||
case 'Share':
|
||||
copyToClipboard(props.groupDialog.ref.$url);
|
||||
@@ -1465,8 +1494,11 @@
|
||||
case 'Moderation Tools':
|
||||
showGroupMemberModerationDialog(props.groupDialog.id);
|
||||
break;
|
||||
case 'Invite To Group':
|
||||
showInviteGroupDialog(D.id, '');
|
||||
break;
|
||||
default:
|
||||
emit('group-dialog-command', command);
|
||||
emit('groupDialogCommand', command);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1481,7 +1513,7 @@
|
||||
D.auditLogTypes = [];
|
||||
API.getCachedGroup({ groupId }).then((args) => {
|
||||
D.groupRef = args.ref;
|
||||
if (utils.hasGroupPermission(D.groupRef, 'group-audit-view')) {
|
||||
if (hasGroupPermission(D.groupRef, 'group-audit-view')) {
|
||||
groupRequest.getGroupAuditLogTypes({ groupId }).then((args) => {
|
||||
// API.$on('GROUP:AUDITLOGTYPES', function (args) {
|
||||
if (groupMemberModeration.id !== args.params.groupId) {
|
||||
@@ -1569,18 +1601,21 @@
|
||||
D.roleIds = [];
|
||||
D.postId = '';
|
||||
D.groupId = groupId;
|
||||
emit('update:gallery-select-dialog', { ...D, selectedFileId: '', selectedImageUrl: '' });
|
||||
selectedGalleryFile.value = {
|
||||
selectedFileId: '',
|
||||
selectedImageUrl: ''
|
||||
};
|
||||
|
||||
if (post) {
|
||||
D.title = post.title;
|
||||
D.text = post.text;
|
||||
D.visibility = post.visibility;
|
||||
D.roleIds = post.roleIds;
|
||||
D.postId = post.id;
|
||||
emit('update:gallery-select-dialog', {
|
||||
...D,
|
||||
selectedGalleryFile.value = {
|
||||
selectedFileId: post.imageId,
|
||||
selectedImageUrl: post.imageUrl
|
||||
});
|
||||
};
|
||||
}
|
||||
API.getCachedGroup({ groupId }).then((args) => {
|
||||
D.groupRef = args.ref;
|
||||
@@ -1763,9 +1798,6 @@
|
||||
await getGroupDialogGroupMembers();
|
||||
}
|
||||
|
||||
function hasGroupPermission(ref, permission) {
|
||||
return utils.hasGroupPermission(ref, permission);
|
||||
}
|
||||
function updateGroupDialogData(obj) {
|
||||
// Be careful with the deep merge
|
||||
emit('update:group-dialog', obj);
|
||||
@@ -1773,16 +1805,7 @@
|
||||
function getGroupDialogGroup(groupId) {
|
||||
emit('get-group-dialog-group', groupId);
|
||||
}
|
||||
function refreshInstancePlayerCount(tag) {
|
||||
emit('refresh-instance-player-count', tag);
|
||||
}
|
||||
function updateGroupPostSearch() {
|
||||
emit('update-group-post-search');
|
||||
}
|
||||
function downloadAndSaveJson(fileName, data) {
|
||||
utils.downloadAndSaveJson(fileName, data);
|
||||
}
|
||||
function clearImageGallerySelect() {
|
||||
emit('clear-image-gallery-select');
|
||||
emit('updateGroupPostSearch');
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
<safe-dialog
|
||||
class="x-dialog"
|
||||
:before-close="beforeDialogClose"
|
||||
:visible="groupMemberModeration.visible"
|
||||
:title="t('dialog.group_member_moderation.header')"
|
||||
append-to-body
|
||||
top="5vh"
|
||||
width="90vw"
|
||||
@close="closeDialog"
|
||||
@mousedown.native="dialogMouseDown"
|
||||
@mouseup.native="dialogMouseUp">
|
||||
@close="closeDialog">
|
||||
<div>
|
||||
<h3>{{ groupMemberModeration.groupRef.name }}</h3>
|
||||
<el-tabs type="card" style="height: 100%">
|
||||
@@ -806,21 +803,18 @@
|
||||
<group-member-moderation-export-dialog
|
||||
:is-group-logs-export-dialog-visible.sync="isGroupLogsExportDialogVisible"
|
||||
:group-logs-moderation-table="groupLogsModerationTable" />
|
||||
</el-dialog>
|
||||
</safe-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { getCurrentInstance, inject, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import utils from '../../../classes/utils';
|
||||
import { groupRequest, userRequest } from '../../../api';
|
||||
import { useModerationTable, useSelectedUsers } from '../../../composables/group/useGroupMemberModeration';
|
||||
import { hasGroupPermission } from '../../../composables/group/utils';
|
||||
import GroupMemberModerationExportDialog from './GroupMemberModerationExportDialog.vue';
|
||||
import { useModerationTable, useSelectedUsers } from '../../../composables/groups/useGroupMemberModeration';
|
||||
|
||||
const API = inject('API');
|
||||
const beforeDialogClose = inject('beforeDialogClose');
|
||||
const dialogMouseDown = inject('dialogMouseDown');
|
||||
const dialogMouseUp = inject('dialogMouseUp');
|
||||
const showUserDialog = inject('showUserDialog');
|
||||
const userImage = inject('userImage');
|
||||
const userImageFull = inject('userImageFull');
|
||||
@@ -1687,8 +1681,4 @@
|
||||
.replace(/\./g, ' ')
|
||||
.replace(/\b\w/g, (l) => l.toUpperCase());
|
||||
}
|
||||
|
||||
function hasGroupPermission(ref, permission) {
|
||||
return utils.hasGroupPermission(ref, permission);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
<safe-dialog
|
||||
class="x-dialog"
|
||||
:before-close="beforeDialogClose"
|
||||
:visible="isGroupLogsExportDialogVisible"
|
||||
:title="t('dialog.group_member_moderation.export_logs')"
|
||||
width="650px"
|
||||
append-to-body
|
||||
@close="setIsGroupLogsExportDialogVisible"
|
||||
@mousedown.native="dialogMouseDown"
|
||||
@mouseup.native="dialogMouseUp">
|
||||
@close="setIsGroupLogsExportDialogVisible">
|
||||
<el-checkbox-group
|
||||
v-model="checkedGroupLogsExportLogsOptions"
|
||||
style="margin-bottom: 10px"
|
||||
@@ -29,17 +26,14 @@
|
||||
readonly
|
||||
style="margin-top: 15px"
|
||||
@click.native="handleCopyGroupLogsExportContent" />
|
||||
</el-dialog>
|
||||
</safe-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { inject, ref, watch } from 'vue';
|
||||
import { ref, watch } from 'vue';
|
||||
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 props = defineProps({
|
||||
@@ -101,7 +95,7 @@
|
||||
}
|
||||
|
||||
function handleCopyGroupLogsExportContent() {
|
||||
utils.copyToClipboard(groupLogsExportContent.value);
|
||||
copyToClipboard(groupLogsExportContent.value);
|
||||
}
|
||||
|
||||
function setIsGroupLogsExportDialogVisible() {
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
ref="groupPostEditDialog"
|
||||
:before-close="beforeDialogClose"
|
||||
<safe-dialog
|
||||
:visible.sync="groupPostEditDialog.visible"
|
||||
:title="$t('dialog.group_post_edit.header')"
|
||||
width="650px"
|
||||
append-to-body
|
||||
@mousedown.native="dialogMouseDown"
|
||||
@mouseup.native="dialogMouseUp">
|
||||
append-to-body>
|
||||
<div v-if="groupPostEditDialog.visible">
|
||||
<h3 v-text="groupPostEditDialog.groupRef.name"></h3>
|
||||
<el-form :model="groupPostEditDialog" label-width="150px">
|
||||
@@ -107,30 +103,39 @@
|
||||
{{ $t('dialog.group_post_edit.create_post') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<GallerySelectDialog
|
||||
:gallery-select-dialog="gallerySelectDialog"
|
||||
:gallery-table="galleryTable"
|
||||
@refresh-gallery-table="refreshGalleryTable" />
|
||||
</safe-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { groupRequest } from '../../../api';
|
||||
import { groupRequest, vrcPlusIconRequest } from '../../../api';
|
||||
import GallerySelectDialog from './GallerySelectDialog.vue';
|
||||
|
||||
export default {
|
||||
name: 'GroupPostEditDialog',
|
||||
inject: [
|
||||
'beforeDialogClose',
|
||||
'showFullscreenImageDialog',
|
||||
'dialogMouseDown',
|
||||
'dialogMouseUp',
|
||||
'showGallerySelectDialog'
|
||||
],
|
||||
components: {
|
||||
GallerySelectDialog
|
||||
},
|
||||
inject: ['showFullscreenImageDialog'],
|
||||
props: {
|
||||
dialogData: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
gallerySelectDialog: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
selectedGalleryFile: { type: Object, default: () => ({}) }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
gallerySelectDialog: {
|
||||
visible: false,
|
||||
selectedFileId: '',
|
||||
selectedImageUrl: ''
|
||||
},
|
||||
galleryTable: []
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
groupPostEditDialog: {
|
||||
@@ -143,6 +148,22 @@
|
||||
}
|
||||
},
|
||||
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() {
|
||||
const D = this.groupPostEditDialog;
|
||||
if (!D.groupId || !D.postId) {
|
||||
@@ -193,7 +214,9 @@
|
||||
D.visible = false;
|
||||
},
|
||||
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>
|
||||
<el-dialog
|
||||
<safe-dialog
|
||||
ref="inviteGroupDialog"
|
||||
:visible.sync="inviteGroupDialog.visible"
|
||||
:before-close="beforeDialogClose"
|
||||
:title="$t('dialog.invite_to_group.header')"
|
||||
width="450px"
|
||||
@mousedown.native="dialogMouseDown"
|
||||
@mouseup.native="dialogMouseUp">
|
||||
append-to-body>
|
||||
<div v-if="inviteGroupDialog.visible" v-loading="inviteGroupDialog.loading">
|
||||
<span>{{ $t('dialog.invite_to_group.description') }}</span>
|
||||
<br />
|
||||
@@ -165,24 +163,16 @@
|
||||
Invite
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</safe-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { groupRequest, userRequest } from '../../../api';
|
||||
import utils from '../../../classes/utils';
|
||||
import { groupRequest, userRequest } from '../../api';
|
||||
import { hasGroupPermission } from '../../composables/group/utils';
|
||||
|
||||
export default {
|
||||
name: 'InviteGroupDialog',
|
||||
inject: [
|
||||
'API',
|
||||
'dialogMouseDown',
|
||||
'dialogMouseUp',
|
||||
'beforeDialogClose',
|
||||
'userStatusClass',
|
||||
'userImage',
|
||||
'adjustDialogZ'
|
||||
],
|
||||
inject: ['API', 'userStatusClass', 'userImage', 'adjustDialogZ'],
|
||||
props: {
|
||||
dialogData: {
|
||||
type: Object,
|
||||
@@ -256,7 +246,7 @@
|
||||
groupRequest
|
||||
.getGroup({ groupId })
|
||||
.then((args) => {
|
||||
if (utils.hasGroupPermission(args.ref, 'group-invites-manage')) {
|
||||
if (hasGroupPermission(args.ref, 'group-invites-manage')) {
|
||||
return args;
|
||||
}
|
||||
// not allowed to invite
|
||||
@@ -1,12 +1,5 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
ref="launchDialog"
|
||||
:before-close="beforeDialogClose"
|
||||
:visible.sync="isVisible"
|
||||
:title="$t('dialog.launch.header')"
|
||||
width="450px"
|
||||
@mousedown.native="dialogMouseDown"
|
||||
@mouseup.native="dialogMouseUp">
|
||||
<safe-dialog ref="launchDialog" :visible.sync="isVisible" :title="$t('dialog.launch.header')" width="450px">
|
||||
<el-form :model="launchDialog" label-width="100px">
|
||||
<el-form-item :label="$t('dialog.launch.url')">
|
||||
<el-input
|
||||
@@ -84,30 +77,58 @@
|
||||
{{ $t('dialog.launch.launch') }}
|
||||
</el-button>
|
||||
</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>
|
||||
|
||||
<script>
|
||||
import utils from '../classes/utils';
|
||||
import configRepository from '../service/config';
|
||||
import { instanceRequest } from '../api';
|
||||
import { instanceRequest, worldRequest } from '../../api';
|
||||
import { isRealInstance, parseLocation } from '../../composables/instance/utils';
|
||||
import { getLaunchURL } from '../../composables/shared/utils';
|
||||
import configRepository from '../../service/config';
|
||||
import InviteDialog from './InviteDialog/InviteDialog.vue';
|
||||
|
||||
export default {
|
||||
name: 'LaunchDialog',
|
||||
inject: [
|
||||
'beforeDialogClose',
|
||||
'dialogMouseDown',
|
||||
'dialogMouseUp',
|
||||
'showPreviousInstancesInfoDialog',
|
||||
'showInviteDialog',
|
||||
'adjustDialogZ'
|
||||
],
|
||||
components: { InviteDialog },
|
||||
inject: ['showPreviousInstancesInfoDialog', 'adjustDialogZ'],
|
||||
props: {
|
||||
hideTooltips: Boolean,
|
||||
launchDialogData: { type: Object, required: true },
|
||||
checkCanInvite: {
|
||||
type: Function,
|
||||
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() {
|
||||
@@ -121,6 +142,14 @@
|
||||
shortName: '',
|
||||
shortUrl: '',
|
||||
secureOrShortName: ''
|
||||
},
|
||||
inviteDialog: {
|
||||
visible: false,
|
||||
loading: false,
|
||||
worldId: '',
|
||||
worldName: '',
|
||||
userIds: [],
|
||||
friendsInInstance: []
|
||||
}
|
||||
};
|
||||
},
|
||||
@@ -147,8 +176,37 @@
|
||||
this.getConfig();
|
||||
},
|
||||
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) {
|
||||
this.$emit('launch-game', location, shortName, desktop);
|
||||
this.$emit('launchGame', location, shortName, desktop);
|
||||
this.isVisible = false;
|
||||
},
|
||||
getConfig() {
|
||||
@@ -159,7 +217,7 @@
|
||||
},
|
||||
async initLaunchDialog() {
|
||||
const { tag, shortName } = this.launchDialogData;
|
||||
if (!utils.isRealInstance(tag)) {
|
||||
if (!isRealInstance(tag)) {
|
||||
return;
|
||||
}
|
||||
this.$nextTick(() => this.adjustDialogZ(this.$refs.launchDialog.$el));
|
||||
@@ -168,7 +226,7 @@
|
||||
D.secureOrShortName = shortName;
|
||||
D.shortUrl = '';
|
||||
D.shortName = shortName;
|
||||
const L = utils.parseLocation(tag);
|
||||
const L = parseLocation(tag);
|
||||
L.shortName = shortName;
|
||||
if (shortName) {
|
||||
D.shortUrl = `https://vrch.at/${shortName}`;
|
||||
@@ -178,7 +236,7 @@
|
||||
} else {
|
||||
D.location = L.worldId;
|
||||
}
|
||||
D.url = utils.getLaunchURL(L);
|
||||
D.url = getLaunchURL(L);
|
||||
if (!shortName) {
|
||||
const res = await instanceRequest.getInstanceShortName({
|
||||
worldId: L.worldId,
|
||||
@@ -193,14 +251,14 @@
|
||||
if (resLocation === this.launchDialog.tag) {
|
||||
const resShortName = res.json.shortName;
|
||||
const secureOrShortName = res.json.shortName || res.json.secureName;
|
||||
const parsedL = utils.parseLocation(resLocation);
|
||||
const parsedL = parseLocation(resLocation);
|
||||
parsedL.shortName = resShortName;
|
||||
this.launchDialog.shortName = resShortName;
|
||||
this.launchDialog.secureOrShortName = secureOrShortName;
|
||||
if (resShortName) {
|
||||
this.launchDialog.shortUrl = `https://vrch.at/${resShortName}`;
|
||||
}
|
||||
this.launchDialog.url = utils.getLaunchURL(parsedL);
|
||||
this.launchDialog.url = getLaunchURL(parsedL);
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1,13 +1,10 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
<safe-dialog
|
||||
ref="newInstanceDialog"
|
||||
:before-close="beforeDialogClose"
|
||||
:visible.sync="newInstanceDialog.visible"
|
||||
:title="$t('dialog.new_instance.header')"
|
||||
width="650px"
|
||||
append-to-body
|
||||
@mousedown.native="dialogMouseDown"
|
||||
@mouseup.native="dialogMouseUp">
|
||||
append-to-body>
|
||||
<el-tabs v-model="newInstanceDialog.selectedTab" type="card" @tab-click="newInstanceTabClick">
|
||||
<el-tab-pane :label="$t('dialog.new_instance.normal')">
|
||||
<el-form :model="newInstanceDialog" label-width="150px">
|
||||
@@ -486,27 +483,30 @@
|
||||
>{{ $t('dialog.new_instance.launch') }}</el-button
|
||||
>
|
||||
</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>
|
||||
|
||||
<script>
|
||||
import { groupRequest, instanceRequest } from '../../api';
|
||||
import { groupRequest, instanceRequest, worldRequest } from '../../api';
|
||||
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 InviteDialog from './InviteDialog/InviteDialog.vue';
|
||||
|
||||
export default {
|
||||
name: 'NewInstanceDialog',
|
||||
inject: [
|
||||
'API',
|
||||
'userImage',
|
||||
'userStatusClass',
|
||||
'beforeDialogClose',
|
||||
'dialogMouseDown',
|
||||
'dialogMouseUp',
|
||||
'showInviteDialog',
|
||||
'showLaunchDialog',
|
||||
'adjustDialogZ'
|
||||
],
|
||||
components: { InviteDialog },
|
||||
inject: ['API', 'userImage', 'userStatusClass', 'showLaunchDialog', 'adjustDialogZ'],
|
||||
props: {
|
||||
vipFriends: {
|
||||
type: Array,
|
||||
@@ -535,6 +535,18 @@
|
||||
newInstanceDialogLocationTag: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
inviteMessageTable: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
uploadImage: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
lastLocation: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -566,6 +578,14 @@
|
||||
groupRef: {},
|
||||
contentSettings: this.instanceContentSettings,
|
||||
selectedContentSettings: []
|
||||
},
|
||||
inviteDialog: {
|
||||
visible: false,
|
||||
loading: false,
|
||||
worldId: '',
|
||||
worldName: '',
|
||||
userIds: [],
|
||||
friendsInInstance: []
|
||||
}
|
||||
};
|
||||
},
|
||||
@@ -578,13 +598,42 @@
|
||||
this.initializeNewInstanceDialog();
|
||||
},
|
||||
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) {
|
||||
if (!utils.isRealInstance(tag)) {
|
||||
if (!isRealInstance(tag)) {
|
||||
return;
|
||||
}
|
||||
this.$nextTick(() => this.adjustDialogZ(this.$refs.newInstanceDialog.$el));
|
||||
const D = this.newInstanceDialog;
|
||||
const L = utils.parseLocation(tag);
|
||||
const L = parseLocation(tag);
|
||||
if (D.worldId === L.worldId) {
|
||||
// reopening dialog, keep last open instance
|
||||
D.visible = true;
|
||||
@@ -682,16 +731,16 @@
|
||||
} else {
|
||||
D.location = D.worldId;
|
||||
}
|
||||
const L = utils.parseLocation(D.location);
|
||||
const L = parseLocation(D.location);
|
||||
if (noChanges) {
|
||||
L.shortName = D.shortName;
|
||||
} else {
|
||||
D.shortName = '';
|
||||
}
|
||||
D.url = utils.getLaunchURL(L);
|
||||
D.url = getLaunchURL(L);
|
||||
},
|
||||
selfInvite(location) {
|
||||
const L = utils.parseLocation(location);
|
||||
const L = parseLocation(location);
|
||||
if (!L.isRealInstance) {
|
||||
return;
|
||||
}
|
||||
@@ -831,7 +880,7 @@
|
||||
this.saveNewInstanceDialog();
|
||||
},
|
||||
async copyInstanceUrl(location) {
|
||||
const L = utils.parseLocation(location);
|
||||
const L = parseLocation(location);
|
||||
const args = await instanceRequest.getInstanceShortName({
|
||||
worldId: L.worldId,
|
||||
instanceId: L.instanceId
|
||||
@@ -870,7 +919,7 @@
|
||||
}
|
||||
},
|
||||
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>
|
||||
<el-dialog
|
||||
<safe-dialog
|
||||
ref="dialog"
|
||||
:before-close="beforeDialogClose"
|
||||
:visible="visible"
|
||||
:title="$t('dialog.previous_instances.info')"
|
||||
width="800px"
|
||||
:fullscreen="fullscreen"
|
||||
destroy-on-close
|
||||
@mousedown.native="dialogMouseDown"
|
||||
@mouseup.native="dialogMouseUp"
|
||||
@close="$emit('update:visible', false)">
|
||||
<div style="display: flex; align-items: center; justify-content: space-between">
|
||||
<location :location="location.tag" style="font-size: 14px"></location>
|
||||
@@ -57,13 +54,14 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</data-tables>
|
||||
</el-dialog>
|
||||
</safe-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import utils from '../../../classes/utils';
|
||||
import database from '../../../service/database';
|
||||
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';
|
||||
|
||||
export default {
|
||||
@@ -71,7 +69,7 @@
|
||||
components: {
|
||||
Location
|
||||
},
|
||||
inject: ['adjustDialogZ', 'beforeDialogClose', 'dialogMouseDown', 'dialogMouseUp'],
|
||||
inject: ['adjustDialogZ'],
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
@@ -144,7 +142,7 @@
|
||||
init() {
|
||||
this.adjustDialogZ(this.$refs.dialog.$el);
|
||||
this.loading = true;
|
||||
this.location = utils.parseLocation(this.instanceId);
|
||||
this.location = parseLocation(this.instanceId);
|
||||
},
|
||||
refreshPreviousInstancesInfoTable() {
|
||||
database.getPlayersFromInstance(this.location.tag).then((data) => {
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
<safe-dialog
|
||||
ref="previousInstancesWorldDialog"
|
||||
:before-close="beforeDialogClose"
|
||||
:visible.sync="isVisible"
|
||||
:title="$t('dialog.previous_instances.header')"
|
||||
width="1000px"
|
||||
append-to-body
|
||||
@mousedown.native="dialogMouseDown"
|
||||
@mouseup.native="dialogMouseUp">
|
||||
append-to-body>
|
||||
<div style="display: flex; align-items: center; justify-content: space-between">
|
||||
<span style="font-size: 14px" v-text="previousInstancesWorldDialog.worldRef.name"></span>
|
||||
<el-input
|
||||
@@ -66,24 +63,17 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</data-tables>
|
||||
</el-dialog>
|
||||
</safe-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import utils from '../../../classes/utils';
|
||||
import { parseLocation } from '../../../composables/instance/utils';
|
||||
import database from '../../../service/database';
|
||||
|
||||
export default {
|
||||
name: 'PreviousInstancesWorldDialog',
|
||||
inject: [
|
||||
'API',
|
||||
'showLaunchDialog',
|
||||
'showPreviousInstancesInfoDialog',
|
||||
'adjustDialogZ',
|
||||
'beforeDialogClose',
|
||||
'dialogMouseDown',
|
||||
'dialogMouseUp'
|
||||
],
|
||||
inject: ['API', 'showLaunchDialog', 'showPreviousInstancesInfoDialog', 'adjustDialogZ'],
|
||||
props: {
|
||||
previousInstancesWorldDialog: {
|
||||
type: Object,
|
||||
@@ -149,7 +139,7 @@
|
||||
database.getpreviousInstancesByWorldId(D.worldRef).then((data) => {
|
||||
const array = [];
|
||||
for (const ref of data.values()) {
|
||||
ref.$location = utils.parseLocation(ref.location);
|
||||
ref.$location = parseLocation(ref.location);
|
||||
if (ref.time > 0) {
|
||||
ref.timer = utils.timeToText(ref.time);
|
||||
} 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>
|
||||
<el-dialog
|
||||
<safe-dialog
|
||||
ref="previousInstancesUserDialog"
|
||||
:before-close="beforeDialogClose"
|
||||
:visible.sync="isVisible"
|
||||
:title="$t('dialog.previous_instances.header')"
|
||||
width="1000px"
|
||||
@mousedown.native="dialogMouseDown"
|
||||
@mouseup.native="dialogMouseUp">
|
||||
append-to-body>
|
||||
<div style="display: flex; align-items: center; justify-content: space-between">
|
||||
<span style="font-size: 14px" v-text="previousInstancesUserDialog.userRef.displayName"></span>
|
||||
<el-input
|
||||
@@ -68,11 +66,12 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</data-tables>
|
||||
</el-dialog>
|
||||
</safe-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import utils from '../../../classes/utils';
|
||||
import { parseLocation } from '../../../composables/instance/utils';
|
||||
import database from '../../../service/database';
|
||||
import Location from '../../Location.vue';
|
||||
|
||||
@@ -81,14 +80,7 @@
|
||||
components: {
|
||||
Location
|
||||
},
|
||||
inject: [
|
||||
'beforeDialogClose',
|
||||
'dialogMouseDown',
|
||||
'dialogMouseUp',
|
||||
'adjustDialogZ',
|
||||
'showLaunchDialog',
|
||||
'showPreviousInstancesInfoDialog'
|
||||
],
|
||||
inject: ['adjustDialogZ', 'showLaunchDialog', 'showPreviousInstancesInfoDialog'],
|
||||
props: {
|
||||
previousInstancesUserDialog: {
|
||||
type: Object,
|
||||
@@ -176,7 +168,7 @@
|
||||
database.getpreviousInstancesByUserId(this.previousInstancesUserDialog.userRef).then((data) => {
|
||||
const array = [];
|
||||
for (const ref of data.values()) {
|
||||
ref.$location = utils.parseLocation(ref.location);
|
||||
ref.$location = parseLocation(ref.location);
|
||||
if (ref.time > 0) {
|
||||
ref.timer = utils.timeToText(ref.time);
|
||||
} 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>
|
||||
<el-dialog
|
||||
<safe-dialog
|
||||
ref="VRCXUpdateDialogRef"
|
||||
class="x-dialog"
|
||||
:before-close="beforeDialogClose"
|
||||
:visible.sync="VRCXUpdateDialog.visible"
|
||||
:title="t('dialog.vrcx_updater.header')"
|
||||
width="400px"
|
||||
@mousedown.native="dialogMouseDown"
|
||||
@mouseup.native="dialogMouseUp">
|
||||
width="400px">
|
||||
<div v-loading="checkingForVRCXUpdate" style="margin-top: 15px">
|
||||
<template v-if="updateInProgress">
|
||||
<el-progress :percentage="updateProgress" :format="updateProgressText"></el-progress>
|
||||
@@ -62,7 +59,7 @@
|
||||
{{ t('dialog.vrcx_updater.install') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</safe-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@@ -71,9 +68,6 @@
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
|
||||
const { t } = useI18n();
|
||||
const beforeDialogClose = inject('beforeDialogClose');
|
||||
const dialogMouseDown = inject('dialogMouseDown');
|
||||
const dialogMouseUp = inject('dialogMouseUp');
|
||||
const adjustDialogZ = inject('adjustDialogZ');
|
||||
|
||||
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>
|
||||
<el-dialog
|
||||
:before-close="beforeDialogClose"
|
||||
<safe-dialog
|
||||
:visible.sync="isVisible"
|
||||
:title="$t('dialog.set_world_tags.header')"
|
||||
width="400px"
|
||||
destroy-on-close
|
||||
append-to-body
|
||||
@mousedown.native="dialogMouseDown"
|
||||
@mouseup.native="dialogMouseUp">
|
||||
append-to-body>
|
||||
<el-checkbox v-model="setWorldTagsDialog.avatarScalingDisabled">
|
||||
{{ $t('dialog.set_world_tags.avatar_scaling_disabled') }}
|
||||
</el-checkbox>
|
||||
@@ -80,7 +77,7 @@
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</safe-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -88,7 +85,7 @@
|
||||
|
||||
export default {
|
||||
name: 'SetWorldTagsDialog',
|
||||
inject: ['beforeDialogClose', 'dialogMouseDown', 'dialogMouseUp', 'showWorldDialog'],
|
||||
inject: ['showWorldDialog'],
|
||||
props: {
|
||||
oldTags: {
|
||||
type: Array,
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:before-close="beforeDialogClose"
|
||||
<safe-dialog
|
||||
:visible.sync="isVisible"
|
||||
:title="$t('dialog.allowed_video_player_domains.header')"
|
||||
width="600px"
|
||||
destroy-on-close
|
||||
append-to-body
|
||||
@mousedown.native="dialogMouseDown"
|
||||
@mouseup.native="dialogMouseUp">
|
||||
append-to-body>
|
||||
<div>
|
||||
<el-input
|
||||
v-for="(domain, index) in urlList"
|
||||
@@ -31,7 +28,7 @@
|
||||
{{ $t('dialog.allowed_video_player_domains.save') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</safe-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -39,7 +36,6 @@
|
||||
|
||||
export default {
|
||||
name: 'WorldAllowedDomainsDialog',
|
||||
inject: ['beforeDialogClose', 'dialogMouseDown', 'dialogMouseUp'],
|
||||
props: {
|
||||
worldAllowedDomainsDialog: {
|
||||
type: Object,
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
<safe-dialog
|
||||
ref="worldDialog"
|
||||
:before-close="beforeDialogClose"
|
||||
class="x-dialog x-world-dialog"
|
||||
:visible.sync="isDialogVisible"
|
||||
:show-close="false"
|
||||
width="770px"
|
||||
@mousedown.native="dialogMouseDown"
|
||||
@mouseup.native="dialogMouseUp">
|
||||
width="770px">
|
||||
<div v-loading="worldDialog.loading">
|
||||
<div style="display: flex">
|
||||
<el-popover placement="right" width="500px" trigger="click">
|
||||
@@ -760,22 +757,49 @@
|
||||
:offline-friends="offlineFriends"
|
||||
:active-friends="activeFriends"
|
||||
:online-friends="onlineFriends"
|
||||
:vip-friends="vipFriends" />
|
||||
</el-dialog>
|
||||
:vip-friends="vipFriends"
|
||||
: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>
|
||||
|
||||
<script>
|
||||
import { favoriteRequest, imageRequest, miscRequest, userRequest, worldRequest } from '../../../api';
|
||||
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 WorldAllowedDomainsDialog from './WorldAllowedDomainsDialog.vue';
|
||||
import SetWorldTagsDialog from './SetWorldTagsDialog.vue';
|
||||
import PreviousInstancesWorldDialog from '../PreviousInstancesDialog/PreviousInstancesWorldDialog.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 {
|
||||
name: 'WorldDialog',
|
||||
components: { SetWorldTagsDialog, WorldAllowedDomainsDialog, PreviousInstancesWorldDialog, NewInstanceDialog },
|
||||
components: {
|
||||
PreviousImagesDialog,
|
||||
SetWorldTagsDialog,
|
||||
WorldAllowedDomainsDialog,
|
||||
PreviousInstancesWorldDialog,
|
||||
NewInstanceDialog,
|
||||
ChangeWorldImageDialog
|
||||
},
|
||||
inject: [
|
||||
'API',
|
||||
'showUserDialog',
|
||||
@@ -785,10 +809,6 @@
|
||||
'showPreviousInstancesInfoDialog',
|
||||
'showLaunchDialog',
|
||||
'showFullscreenImageDialog',
|
||||
'beforeDialogClose',
|
||||
'dialogMouseDown',
|
||||
'dialogMouseUp',
|
||||
'displayPreviousImages',
|
||||
'showWorldDialog',
|
||||
'showFavoriteDialog',
|
||||
'openExternalLink'
|
||||
@@ -808,6 +828,8 @@
|
||||
activeFriends: Array,
|
||||
onlineFriends: Array,
|
||||
vipFriends: Array,
|
||||
inviteMessageTable: Object,
|
||||
uploadImage: String,
|
||||
|
||||
// TODO: Remove
|
||||
updateInstanceInfo: Number
|
||||
@@ -826,7 +848,11 @@
|
||||
openFlg: false,
|
||||
worldRef: {}
|
||||
},
|
||||
newInstanceDialogLocationTag: ''
|
||||
newInstanceDialogLocationTag: '',
|
||||
changeWorldImageDialogVisible: false,
|
||||
previousImagesFileId: '',
|
||||
previousImagesDialogVisible: false,
|
||||
previousImagesTable: []
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -907,6 +933,51 @@
|
||||
}
|
||||
},
|
||||
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) {
|
||||
// trigger watcher
|
||||
this.newInstanceDialogLocationTag = '';
|
||||
@@ -946,26 +1017,30 @@
|
||||
});
|
||||
break;
|
||||
case 'Make Home':
|
||||
this.API.saveCurrentUser({
|
||||
homeLocation: D.id
|
||||
}).then((args) => {
|
||||
this.$message({
|
||||
message: 'Home world updated',
|
||||
type: 'success'
|
||||
userRequest
|
||||
.saveCurrentUser({
|
||||
homeLocation: D.id
|
||||
})
|
||||
.then((args) => {
|
||||
this.$message({
|
||||
message: 'Home world updated',
|
||||
type: 'success'
|
||||
});
|
||||
return args;
|
||||
});
|
||||
return args;
|
||||
});
|
||||
break;
|
||||
case 'Reset Home':
|
||||
this.API.saveCurrentUser({
|
||||
homeLocation: ''
|
||||
}).then((args) => {
|
||||
this.$message({
|
||||
message: 'Home world has been reset',
|
||||
type: 'success'
|
||||
userRequest
|
||||
.saveCurrentUser({
|
||||
homeLocation: ''
|
||||
})
|
||||
.then((args) => {
|
||||
this.$message({
|
||||
message: 'Home world has been reset',
|
||||
type: 'success'
|
||||
});
|
||||
return args;
|
||||
});
|
||||
return args;
|
||||
});
|
||||
break;
|
||||
case 'Publish':
|
||||
worldRequest
|
||||
@@ -1040,10 +1115,10 @@
|
||||
this.openExternalLink(this.replaceVrcPackageUrl(this.worldDialog.ref.unityPackageUrl));
|
||||
break;
|
||||
case 'Change Image':
|
||||
this.displayPreviousImages('World', 'Change');
|
||||
this.displayPreviousImages('Change');
|
||||
break;
|
||||
case 'Previous Images':
|
||||
this.displayPreviousImages('World', 'Display');
|
||||
this.displayPreviousImages('Display');
|
||||
break;
|
||||
case 'Refresh':
|
||||
this.showWorldDialog(D.id);
|
||||
@@ -1055,12 +1130,15 @@
|
||||
this.showFavoriteDialog('world', D.id);
|
||||
break;
|
||||
default:
|
||||
this.$emit('world-dialog-command', command);
|
||||
this.$emit('worldDialogCommand', command);
|
||||
break;
|
||||
}
|
||||
},
|
||||
replaceVrcPackageUrl(url) {
|
||||
_replaceVrcPackageUrl(url);
|
||||
},
|
||||
refreshInstancePlayerCount(tag) {
|
||||
this.$emit('refresh-instance-player-count', tag);
|
||||
_refreshInstancePlayerCount(tag);
|
||||
},
|
||||
onWorldMemoChange() {
|
||||
const worldId = this.worldDialog.id;
|
||||
@@ -1087,7 +1165,7 @@
|
||||
this.treeData = utils.buildTreeData(this.worldDialog.ref);
|
||||
},
|
||||
downloadAndSaveJson(fileName, data) {
|
||||
utils.downloadAndSaveJson(fileName, data);
|
||||
_downloadAndSaveJson(fileName, data);
|
||||
},
|
||||
copyWorldId() {
|
||||
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 = [
|
||||
{ name: '1280x720 (720p)', width: 1280, height: 720 },
|
||||
{ name: '1920x1080 (1080p Default)', width: '', height: '' },
|
||||
@@ -29,8 +13,4 @@ const VRChatCameraResolutions = [
|
||||
{ name: '7680x4320 (8K)', width: 7680, height: 4320 }
|
||||
];
|
||||
|
||||
export {
|
||||
getVRChatResolution,
|
||||
VRChatScreenshotResolutions,
|
||||
VRChatCameraResolutions
|
||||
};
|
||||
export { 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>
|
||||
import dayjs from 'dayjs';
|
||||
import { parseLocation } from '../../../composables/instance/utils';
|
||||
import database from '../../../service/database';
|
||||
import utils from '../../../classes/utils';
|
||||
import configRepository from '../../../service/config';
|
||||
@@ -379,7 +380,7 @@
|
||||
const timeString = utils.timeToText(param.data, true);
|
||||
const color = param.color;
|
||||
const name = param.name;
|
||||
const location = utils.parseLocation(instanceData.location);
|
||||
const location = parseLocation(instanceData.location);
|
||||
|
||||
return `
|
||||
<div style="display: flex; align-items: center;">
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:before-close="beforeDialogClose"
|
||||
:visible.sync="isDialogVisible"
|
||||
:title="$t('dialog.avatar_export.header')"
|
||||
width="650px"
|
||||
@mousedown.native="dialogMouseDown"
|
||||
@mouseup.native="dialogMouseUp">
|
||||
<safe-dialog :visible.sync="isDialogVisible" :title="$t('dialog.avatar_export.header')" width="650px">
|
||||
<el-checkbox-group
|
||||
v-model="exportSelectedOptions"
|
||||
style="margin-bottom: 10px"
|
||||
@@ -82,13 +76,13 @@
|
||||
readonly
|
||||
style="margin-top: 15px"
|
||||
@click.native="handleCopyAvatarExportData"></el-input>
|
||||
</el-dialog>
|
||||
</safe-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'AvatarExportDialog',
|
||||
inject: ['API', 'beforeDialogClose', 'dialogMouseDown', 'dialogMouseUp'],
|
||||
inject: ['API'],
|
||||
props: {
|
||||
avatarExportDialogVisible: Boolean,
|
||||
favoriteAvatars: Array,
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
<safe-dialog
|
||||
ref="avatarImportDialog"
|
||||
:before-close="beforeDialogClose"
|
||||
:visible.sync="isVisible"
|
||||
:title="$t('dialog.avatar_import.header')"
|
||||
width="650px"
|
||||
@mousedown.native="dialogMouseDown"
|
||||
@mouseup.native="dialogMouseUp">
|
||||
width="650px">
|
||||
<div style="display: flex; align-items: center; justify-content: space-between">
|
||||
<div style="font-size: 12px">{{ $t('dialog.avatar_import.description') }}</div>
|
||||
<div style="display: flex; align-items: center">
|
||||
@@ -171,7 +168,7 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</data-tables>
|
||||
</el-dialog>
|
||||
</safe-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -180,16 +177,7 @@
|
||||
|
||||
export default {
|
||||
name: 'AvatarImportDialog',
|
||||
inject: [
|
||||
'API',
|
||||
'beforeDialogClose',
|
||||
'dialogMouseDown',
|
||||
'dialogMouseUp',
|
||||
'adjustDialogZ',
|
||||
'showFullscreenImageDialog',
|
||||
'showUserDialog',
|
||||
'showAvatarDialog'
|
||||
],
|
||||
inject: ['API', 'adjustDialogZ', 'showFullscreenImageDialog', 'showUserDialog', 'showAvatarDialog'],
|
||||
props: {
|
||||
getLocalAvatarFavoriteGroupLength: Function,
|
||||
localAvatarFavoriteGroups: Array,
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:before-close="beforeDialogClose"
|
||||
<safe-dialog
|
||||
:visible.sync="isDialogVisible"
|
||||
class="x-dialog"
|
||||
:title="$t('dialog.friend_export.header')"
|
||||
width="650px"
|
||||
destroy-on-close
|
||||
@mousedown.native="dialogMouseDown"
|
||||
@mouseup.native="dialogMouseUp">
|
||||
destroy-on-close>
|
||||
<el-dropdown trigger="click" size="small" @click.native.stop>
|
||||
<el-button size="mini">
|
||||
<span v-if="friendExportFavoriteGroup">
|
||||
@@ -42,13 +39,13 @@
|
||||
readonly
|
||||
style="margin-top: 15px"
|
||||
@click.native="handleCopyFriendExportData"></el-input>
|
||||
</el-dialog>
|
||||
</safe-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'FriendExportDialog',
|
||||
inject: ['API', 'beforeDialogClose', 'dialogMouseDown', 'dialogMouseUp'],
|
||||
inject: ['API'],
|
||||
props: {
|
||||
friendExportDialogVisible: Boolean,
|
||||
favoriteFriends: Array
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
<safe-dialog
|
||||
ref="friendImportDialog"
|
||||
:before-close="beforeDialogClose"
|
||||
:visible.sync="isVisible"
|
||||
:title="$t('dialog.friend_import.header')"
|
||||
width="650px"
|
||||
@mousedown.native="dialogMouseDown"
|
||||
@mouseup.native="dialogMouseUp">
|
||||
width="650px">
|
||||
<div style="display: flex; align-items: center; justify-content: space-between">
|
||||
<div style="font-size: 12px">{{ $t('dialog.friend_import.description') }}</div>
|
||||
<div style="display: flex; align-items: center">
|
||||
@@ -122,7 +119,7 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</data-tables>
|
||||
</el-dialog>
|
||||
</safe-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -131,17 +128,7 @@
|
||||
|
||||
export default {
|
||||
name: 'FriendImportDialog',
|
||||
inject: [
|
||||
'API',
|
||||
'userImage',
|
||||
'userImageFull',
|
||||
'showFullscreenImageDialog',
|
||||
'showUserDialog',
|
||||
'beforeDialogClose',
|
||||
'dialogMouseDown',
|
||||
'dialogMouseUp',
|
||||
'adjustDialogZ'
|
||||
],
|
||||
inject: ['API', 'userImage', 'userImageFull', 'showFullscreenImageDialog', 'showUserDialog', 'adjustDialogZ'],
|
||||
props: {
|
||||
friendImportDialogVisible: {
|
||||
type: Boolean,
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:before-close="beforeDialogClose"
|
||||
:visible.sync="isDialogVisible"
|
||||
:title="$t('dialog.world_export.header')"
|
||||
width="650px"
|
||||
@mousedown.native="dialogMouseDown"
|
||||
@mouseup.native="dialogMouseUp">
|
||||
<safe-dialog :visible.sync="isDialogVisible" :title="$t('dialog.world_export.header')" width="650px">
|
||||
<el-checkbox-group
|
||||
v-model="exportSelectedOptions"
|
||||
style="margin-bottom: 10px"
|
||||
@@ -84,13 +78,13 @@
|
||||
readonly
|
||||
style="margin-top: 15px"
|
||||
@click.native="handleCopyWorldExportData"></el-input>
|
||||
</el-dialog>
|
||||
</safe-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'WorldExportDialog',
|
||||
inject: ['API', 'beforeDialogClose', 'dialogMouseDown', 'dialogMouseUp'],
|
||||
inject: ['API'],
|
||||
props: {
|
||||
favoriteWorlds: Array,
|
||||
worldExportDialogVisible: Boolean,
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
<safe-dialog
|
||||
ref="worldImportDialog"
|
||||
:before-close="beforeDialogClose"
|
||||
:visible.sync="isVisible"
|
||||
:title="$t('dialog.world_import.header')"
|
||||
width="650px"
|
||||
top="10vh"
|
||||
class="x-dialog"
|
||||
@mousedown.native="dialogMouseDown"
|
||||
@mouseup.native="dialogMouseUp">
|
||||
class="x-dialog">
|
||||
<div style="display: flex; align-items: center; justify-content: space-between">
|
||||
<div style="font-size: 12px">{{ $t('dialog.world_import.description') }}</div>
|
||||
<div style="display: flex; align-items: center">
|
||||
@@ -176,7 +173,7 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</data-tables>
|
||||
</el-dialog>
|
||||
</safe-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -185,16 +182,7 @@
|
||||
|
||||
export default {
|
||||
name: 'WorldImportDialog',
|
||||
inject: [
|
||||
'API',
|
||||
'beforeDialogClose',
|
||||
'dialogMouseDown',
|
||||
'dialogMouseUp',
|
||||
'showFullscreenImageDialog',
|
||||
'showUserDialog',
|
||||
'adjustDialogZ',
|
||||
'showWorldDialog'
|
||||
],
|
||||
inject: ['API', 'showFullscreenImageDialog', 'showUserDialog', 'adjustDialogZ', 'showWorldDialog'],
|
||||
props: {
|
||||
worldImportDialogVisible: Boolean,
|
||||
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>
|
||||
|
||||
<script>
|
||||
import removeConfusables, { removeWhitespace } from '../../service/confusables';
|
||||
import utils from '../../classes/utils';
|
||||
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 {
|
||||
name: 'FriendListTab',
|
||||
@@ -282,8 +284,7 @@
|
||||
'showFullscreenImageDialog',
|
||||
'showUserDialog',
|
||||
'statusClass',
|
||||
'openExternalLink',
|
||||
'languageClass'
|
||||
'openExternalLink'
|
||||
],
|
||||
props: {
|
||||
friends: {
|
||||
@@ -322,7 +323,7 @@
|
||||
friendsListLoading: false,
|
||||
friendsListLoadingProgress: '',
|
||||
friendsListSearchFilterVIP: false,
|
||||
// emm
|
||||
// TODO
|
||||
friendsListBulkUnfriendForceUpdate: 0
|
||||
};
|
||||
},
|
||||
@@ -336,6 +337,9 @@
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
languageClass(key) {
|
||||
return _languageClass(key);
|
||||
},
|
||||
friendsListSearchChange() {
|
||||
this.friendsListLoading = true;
|
||||
let query = '';
|
||||
@@ -505,7 +509,7 @@
|
||||
return utils.timeToText(val);
|
||||
},
|
||||
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